tuberias salidas salida redirigir redireccionamiento guardar filtros fichero ejemplos comandos comando archivo linux bash

linux - salidas - Problema con la redirección de salida Bash



redirigir salidas linux (11)

Estaba intentando eliminar todas las líneas de un archivo, excepto la última línea, pero el siguiente comando no funcionó, aunque file.txt no está vacío.

$cat file.txt |tail -1 > file.txt $cat file.txt

¿Por que es esto entonces?


Antes de que se ejecute ''cat'', Bash ya ha abierto ''file.txt'' para escribir, borrando sus contenidos.

En general, no escriba en los archivos que está leyendo en la misma declaración. Esto puede solucionarse escribiendo en un archivo diferente, como se muestra arriba:

$cat file.txt | tail -1 >anotherfile.txt $mv anotherfile.txt file.txt o usando una utilidad como esponja de moreutils :

$cat file.txt | tail -1 | sponge file.txt Esto funciona porque esponja espera hasta que su flujo de entrada haya terminado antes de abrir su archivo de salida.


Como dice Lewis Baumstark, no le gusta que esté escribiendo con el mismo nombre de archivo.

Esto se debe a que el shell abre "file.txt" y lo trunca para hacer la redirección antes de ejecutar "cat file.txt". Entonces, tienes que

tail -1 file.txt > file2.txt; mv file2.txt file.txt


Cuando envía su cadena de comandos a bash, hace lo siguiente:

  1. Crea una tubería de E / S.
  2. Inicia "/ usr / bin / tail -1", leyendo desde el conducto y escribiendo en file.txt.
  3. Inicia "/ usr / bin / cat file.txt", escribiendo en la tubería.

Para cuando ''gato'' comienza a leer, ''archivo.txt'' ya ha sido truncado por ''cola''.

Todo esto forma parte del diseño de Unix y del entorno de shell, y se remonta hasta el shell Bourne original. Es una característica, no un error.


Esto funciona muy bien en un shell de Linux:

replace_with_filter() { local filename="$1"; shift local dd_output byte_count filter_status dd_status dd_output=$("$@" <"$filename" | dd conv=notrunc of="$filename" 2>&1; echo "${PIPESTATUS[@]}") { read; read; read -r byte_count _; read filter_status dd_status; } <<<"$dd_output" (( filter_status > 0 )) && return "$filter_status" (( dd_status > 0 )) && return "$dd_status" dd bs=1 seek="$byte_count" if=/dev/null of="$filename" } replace_with_filter file.txt tail -1

La opción "notrunc" de dd se usa para escribir los contenidos filtrados en su lugar, mientras que dd se necesita nuevamente (con un conteo de bytes) para truncar realmente el archivo. Si el nuevo tamaño de archivo es mayor o igual que el tamaño del archivo anterior, la segunda invocación de dd no es necesaria.

Las ventajas de esto sobre un método de copia de archivo son: 1) no es necesario espacio en disco adicional, 2) rendimiento más rápido en archivos grandes, y 3) shell puro (que no sea dd).


Parece que no le gusta el hecho de que lo está escribiendo de nuevo en el mismo nombre de archivo. Si haces lo siguiente, funciona:

$cat file.txt | tail -1 > anotherfile.txt


Puede usar sed para eliminar todas las líneas, pero la última de un archivo:

sed -i ''$!d'' file

  • -i le dice a sed que reemplace el archivo en su lugar; de lo contrario, el resultado escribiría en STDOUT.
  • $ es la dirección que coincide con la última línea del archivo.
  • d es el comando de eliminar. En este caso, es negado por ! , por lo que todas las líneas que no coincidan con la dirección serán eliminadas.

Redireccionar desde un archivo a través de una tubería hasta el mismo archivo no es seguro; Si file.txt es sobreescrito por el shell al configurar la última etapa de la tubería antes de que tail comience a leer la primera etapa, terminará con salida vacía.

Haga lo siguiente en su lugar:

tail -1 file.txt >file.txt.new && mv file.txt.new file.txt

... bueno, en realidad, no hagas eso en el código de producción; especialmente si se encuentra en un entorno sensible a la seguridad y se ejecuta como root, lo siguiente es más apropiado:

tempfile="$(mktemp file.txt.XXXXXX)" chown --reference=file.txt -- "$tempfile" chmod --reference=file.txt -- "$tempfile" tail -1 file.txt >"$tempfile" && mv -- "$tempfile" file.txt

Otro enfoque (evitar archivos temporales, a menos que <<< los cree implícitamente en su plataforma) es el siguiente:

lastline="$(tail -1 file.txt)"; cat >file.txt <<<"$lastline"

(La implementación anterior es específica de bash, pero funciona en casos donde echo no lo hace, como cuando la última línea contiene "version ", por ejemplo).

Finalmente, uno puede usar esponja de moreutils :

tail -1 file.txt | sponge file.txt


Solo para este caso es posible usar

cat < file.txt | (rm file.txt; tail -1 > file.txt) Eso abrirá "file.txt" justo antes de la conexión "cat" con subshell en "(...)". "rm file.txt" eliminará la referencia del disco antes de que subshell lo abra para escribir para "cola", pero el contenido seguirá estando disponible a través del descriptor abierto que se pasa a "cat" hasta que se cierre stdin. Así que es mejor asegurarse de que este comando termine o se pierda el contenido de "file.txt"


tmp = $ (cola -1 archivo.txt); echo $ tmp> file.txt;


tail -1 > file.txt sobrescribirá su archivo, haciendo que cat lea un archivo vacío porque la re-escritura ocurrirá antes de que se ejecute cualquiera de los comandos en su canalización.


echo "$(tail -1 file.txt)" > file.txt