¿Cómo ejecuto cualquier comando editando su archivo (argumento) "en su lugar" usando bash?

110

Tengo un archivo temp.txt, que quiero ordenar con el sortcomando en bash.

Quiero que los resultados ordenados reemplacen el archivo original.

Esto no funciona, por ejemplo (obtengo un archivo vacío):

sortx temp.txt > temp.txt

¿Se puede hacer esto en una línea sin recurrir a la copia en archivos temporales?


EDITAR: La -oopción es muy buena para sort. Usé sorten mi pregunta como ejemplo. Me encuentro con el mismo problema con otros comandos:

uniq temp.txt > temp.txt.

¿Existe una mejor solución general?

jm.
fuente
También vea serverfault.com/a/547331/313521
Comodín

Respuestas:

171
sort temp.txt -o temp.txt
daniels
fuente
3
Esta es una respuesta. De hecho, me preguntaba si existe una solución genérica a este problema. Por ejemplo, si quiero encontrar todas las líneas UNIQ en un archivo "en su lugar", no puedo hacer -o
jm.
No es genérico, pero puede usar -u con GNU sort para encontrar líneas únicas
James
¿Alguien ha resuelto el problema para permitir, por ejemplo sort --inplace *.txt? Eso sería increíblemente genial
mira el
@sehe Prueba esto:find . -name \*.txt -exec sort {} -o {} \;
Keith Gaughan
29

A sortnecesita ver todas las entradas antes de que pueda comenzar a generar. Por esta razón, el sortprograma puede ofrecer fácilmente una opción para modificar un archivo en el lugar:

sort temp.txt -o temp.txt

Específicamente, la documentación de GNUsort dice:

Normalmente, sort lee todas las entradas antes de abrir el archivo de salida, por lo que puede ordenar de forma segura un archivo en su lugar utilizando comandos como sort -o F Fy cat F | sort -o F. Sin embargo, sortcon --merge( -m) puede abrir el archivo de salida antes de leer toda la entrada, por lo que un comando como cat F | sort -m -o F - Gno es seguro, ya que sort podría comenzar a escribir Fantes de catque termine de leerlo.

Mientras que la documentación de BSD sortdice:

Si [el] archivo de salida es uno de los archivos de entrada, sort lo copia en un archivo temporal antes de ordenar y escribir la salida en [el] archivo de salida.

Los comandos como uniqpueden comenzar a escribir la salida antes de que terminen de leer la entrada. Por lo general, estos comandos no admiten la edición en el lugar (y sería más difícil para ellos admitir esta función).

Por lo general, soluciona esto con un archivo temporal, o si absolutamente desea evitar tener un archivo intermedio, puede usar un búfer para almacenar el resultado completo antes de escribirlo. Por ejemplo, con perl:

uniq temp.txt | perl -e 'undef $/; $_ = <>; open(OUT,">temp.txt"); print OUT;'

Aquí, la parte de perl lee la salida completa de una uniqvariable $_y luego sobrescribe el archivo original con estos datos. Puede hacer lo mismo en el lenguaje de secuencias de comandos que elija, tal vez incluso en Bash. Pero tenga en cuenta que necesitará suficiente memoria para almacenar el archivo completo, esto no es recomendable cuando se trabaja con archivos grandes.

Bruno De Fraine
fuente
19

Aquí hay un enfoque más general, funciona con uniq, sort y otras cosas.

{ rm file && uniq > file; } < file
Wor
fuente
14
Otro enfoque genérico, la spongede los moreutils: cat file |frobnicate |sponge file.
Tobu
3
@Tobu: ¿por qué no enviar eso como una respuesta separada?
Flimm
1
Probablemente sea bueno tener en cuenta que esto no necesariamente preserva los permisos de los archivos. Su umask dicta cuáles serán los nuevos permisos.
WOR
1
Complicado. ¿Puede explicar cómo funciona exactamente?
patryk.beza
2
@ patryk.beza: En orden: El FD de entrada se abre desde el archivo original; se elimina la entrada de directorio original; se procesa la redirección, creando un nuevo archivo vacío con el mismo nombre que el anterior; luego se ejecuta el comando.
Charles Duffy
10

El comentario de Tobu sobre la esponja merece ser una respuesta por derecho propio.

Para citar de la página de inicio de moreutils :

Probablemente la herramienta de propósito más general en moreutils hasta ahora es sponge (1), que le permite hacer cosas como esta:

% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd

Sin embargo, spongesufre el mismo problema que comenta Steve Jessop aquí. Si alguno de los comandos de la canalización anterior spongefalla, se sobrescribirá el archivo original.

$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found

Uh-oh, my-important-filese ha ido.

Sean
fuente
1
Sponge sabe que se usará para reemplazar el archivo de entrada e inicialmente crea un archivo temporal para evitar una condición de carrera. Para que esto funcione, la esponja debe ser el último elemento en la canalización y se le debe permitir crear el archivo de salida en sí mismo (a diferencia de la redirección de salida a nivel de shell, por ejemplo). Por cierto: parece que una solución de código fuente fácil para el caso de 'falla' sería no cambiar el nombre del archivo temporal en el caso de una falla en la tubería (no sé por qué la esponja no tiene esa opción).
Brent Bradburn
Creo que si agrega set -o pipefailal comienzo de su secuencia de comandos, el error en mistyped_command my-important-fileharía que la secuencia de comandos salga inmediatamente, antes de ejecutarse sponge, preservando así el archivo importante.
Elouan Keryell-Even
6

Aquí tienes, una línea:

sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt

Técnicamente, no se puede copiar a un archivo temporal y el comando 'mv' debería ser instantáneo.

davr
fuente
6
Hm. Todavía llamaría a temp.txt.sort un archivo temporal.
JesperE
5
Este código es arriesgado, porque si la clasificación falla por cualquier motivo sin completar su trabajo, el original se sobrescribe.
Steve Jessop
1
La falta de espacio en el disco es una causa plausible o una señal (el usuario presiona CTRL-C).
Steve Jessop
5
si desea utilizar algo como esto, utilice && (lógico y) en lugar de; porque usarlo asegurará que si un comando falla, el siguiente no se ejecutará. por ejemplo: cp backup.tar /root/backup.tar && rm backup.tar si no tiene derechos para copiar, estará seguro ya que el archivo no se eliminará
daniels
1
cambié mi respuesta para tener en cuenta sus sugerencias, gracias
davr
4

Me gusta la sort file -o filerespuesta, pero no quiero escribir el mismo nombre de archivo dos veces.

Usando la expansión del historial de BASH :

$ sort file -o !#^

agarra el primer argumento de la línea actual cuando presiona enter.

Una clasificación única en el lugar:

$ sort -u -o file !#$

toma el último argumento de la línea actual.

johnnyB
fuente
3

Muchos han mencionado la opción -o . Aquí está la parte de la página de manual.

Desde la página del manual:

   -o output-file
          Write output to output-file instead of to the  standard  output.
          If  output-file  is  one of the input files, sort copies it to a
          temporary file before sorting and writing the output to  output-
          file.
epatel
fuente
3

Esto tendría una gran limitación de memoria, pero podría usar awk para almacenar los datos intermedios en la memoria y luego volver a escribirlos.

uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt
JayG
fuente
Creo que es posible la >trunca el archivo antes de que el comando ( uniqen este caso) lo lee.
Martin
3

Una alternativa a spongelas más comunes sed:

sed -ni r<(command file) file

Funciona para cualquier comando ( sort, uniq, tac, ...) y los usos de la muy conocida sed's -iopción (editar archivos en el lugar).

Advertencia: intente command fileprimero porque editar archivos en el lugar no es seguro por naturaleza.


Explicación

En primer lugar, usted está diciendo sedque no se imprima el (originales) líneas ( -nopción ), y con la ayuda de la sed's rcomando y bash' s Sustitución de proceso , el contenido generado por <(command file)será la salida guardada en su lugar .


Haciendo las cosas aún más fáciles

Puede envolver esta solución en una función:

ip_cmd() { # in place command
    CMD=${1:?You must specify a command}
    FILE=${2:?You must specify a file}
    sed -ni r<("$CMD" "$FILE") "$FILE"
}

Ejemplo

$ cat file
d
b
c
b
a

$ ip_cmd sort file
$ cat file
a
b
b
c
d

$ ip_cmd uniq file
$ cat file
a
b
c
d

$ ip_cmd tac file
$ cat file
d
c
b
a

$ ip_cmd
bash: 1: You must specify a command
$ ip_cmd uniq
bash: 2: You must specify a file
whoan
fuente
1

Usa el argumento --output=o-o

Probé en FreeBSD:

sort temp.txt -otemp.txt
sammyo
fuente
Aunque es correcto, es simplemente un duplicado de esta respuesta
whoan
1

Para agregar la uniqcapacidad, ¿cuáles son las desventajas de:

sort inputfile | uniq | sort -o inputfile
jaspe
fuente
1

Lea sobre el editor no interactivo, ex.

Delgado
fuente
je, esa es una idea totalmente malvada. Me gusta.
David Mackintosh
0

Si insiste en usar el sortprograma, debe usar un archivo intermedio; no creo que sorttenga una opción para ordenar en la memoria. Cualquier otro truco con stdin / stdout fallará a menos que pueda garantizar que el tamaño del búfer para stdin de sort es lo suficientemente grande para caber en todo el archivo.

Editar: la culpa es mía. sort temp.txt -o temp.txtfunciona excelente.

JesperE
fuente
Leí la Q también como si estuviera "en el lugar", pero la segunda lectura me hizo creer que realmente no la estaba pidiendo
epatel
0

Otra solución:

uniq file 1<> file
Antonio Lebrón
fuente
Sin embargo, debe tenerse en cuenta que el <>truco solo funciona en este caso porque uniqes especial porque solo copia las líneas de entrada en las líneas de salida, dejando algunas en el camino. Si sedse usó otro comando (por ejemplo ) que cambiaría la entrada (por ejemplo, cambiaría cada aen aa), entonces puede anular filede formas que no tienen ningún sentido e incluso hacer un ciclo infinito, siempre que la entrada sea lo suficientemente grande (más de un búfer de lectura única).
David