¿Por qué redirigir la salida de un archivo a sí mismo produce un archivo en blanco?

19

¿Por qué redirigir la salida de un archivo a sí mismo produce un archivo en blanco?

Dicho en Bash, ¿por qué

less foo.txt > foo.txt

y

fold foo.txt > foo.txt

producir un vacío foo.txt? Como un apéndice como less eggs.py >> eggs.pyproduce dos copias del texto eggs.py, se podría esperar que una sobrescritura produzca una copia del texto.

Tenga en cuenta que no estoy diciendo que esto sea un error, es más probable que sea un puntero a algo profundo sobre Unix.

Seewalker
fuente
Abordado en el canónico de U&L ¿Cuáles son los operadores de control y redirección de shell? pregunta.
Scott

Respuestas:

20

Cuando lo usa >, el archivo se abre en modo de truncamiento, por lo que su contenido se elimina antes de que el comando intente leerlo.

Cuando lo usa >>, el archivo se abre en modo anexar para que se conserven los datos existentes. Sin embargo, todavía es bastante arriesgado usar el mismo archivo como entrada y salida en este caso. Si el archivo es lo suficientemente grande como para no ajustarse al tamaño del búfer de entrada de lectura, su tamaño puede crecer indefinidamente hasta que el sistema de archivos esté lleno (o se alcance su cuota de disco).

Si desea utilizar un archivo como entrada y salida con un comando que no admite la modificación en el lugar, puede utilizar un par de soluciones:

  • Utilice un archivo intermediario y sobrescriba el original cuando haya terminado y solo si no se produjo ningún error al ejecutar la utilidad (esta es la forma más segura y común).

    fold foo.txt > fold.txt.$$ && mv fold.txt.$$ foo.txt
  • Evite el archivo intermediario a expensas de una posible pérdida parcial o completa de datos en caso de que ocurra un error o una interrupción. En este ejemplo, el contenido de foo.txtse pasa como entrada a una subshell (dentro de los paréntesis) antes de eliminar el archivo. El inodo anterior permanece vivo ya que la subshell lo mantiene abierto mientras lee datos. El archivo escrito por la utilidad interna (aquí fold) mientras tiene el mismo nombre (foo.txt) apunta a un inodo diferente porque la entrada de directorio anterior se ha eliminado técnicamente, hay dos "archivos" diferentes con el mismo nombre durante el proceso. Cuando finaliza la subshell, se libera el inodo anterior y se pierden sus datos. Tenga cuidado de asegurarse de tener suficiente espacio para almacenar temporalmente tanto el archivo antiguo como el nuevo al mismo tiempo, de lo contrario perderá datos.

    (rm foo.txt; fold > foo.txt) < foo.txt
jlliagre
fuente
3
spongefrom moreutils también puede ayudar. fold foo.txt | sponge foo.txt- O fold foo.txt | sponge !$también debería hacerlo.
slhck
@slhck De hecho, la esponja también podría hacer el trabajo. Sin embargo, al no estar especificado por POSIX ni por la corriente principal en sistemas operativos Unix, es poco probable que esté presente.
jlliagre
Sin embargo, no es que no pueda hacerse presente;)
slhck
7

El shell abre el archivo para que lo escriba antes de que la aplicación tenga la oportunidad de leerlo. Abrir el archivo para escribir lo trunca.

Ignacio Vazquez-Abrams
fuente
0

En bash, el operador de redirección de flujo se ... > foo.txtvacía foo.txt antes de evaluar el operando izquierdo .

Uno podría usar la sustitución de comandos e imprimir su resultado como una solución alternativa. Esta solución toma menos caracteres adicionales que en otras respuestas:

printf "%s\n" "$(less foo.txt)" > foo.txt

Cuidado: este comando no conserva ninguna línea nueva de seguimiento foo.txt. Echa un vistazo a la sección de comentarios a continuación para obtener más información.

Aquí, la subshell $(...)se evalúa antes que el operador de redirección de flujo >, de ahí la preservación de la información.

Louis-Jacob Lebel
fuente
@KamilMaciorowski: En realidad, la hay tmp=$(cmd; printf q);  printf '%s' "${tmp%q}". Pero se perdió otro problema con esta respuesta: dice "subshell" cuando significa "sustitución de comando". Sí, las sustituciones de comandos son generalmente subcapas, pero no al revés, y las subcapas, en general, no son de ayuda para este problema.
Scott
@KamilMaciorowski Me siento tan mal por perder todo esto. Gracias por señalar todo esto. Para su (4) punto: ¿serían las comillas inversas el truco, es decir, preservar las líneas nuevas?
Louis-Jacob Lebel
@ Scott, gracias por tu respuesta. Cambié "subshell" por "sustitución de comandos". Por cierto, me pregunto cuál es la diferencia exacta entre los dos.
Louis-Jacob Lebel
No, las comillas inversas (backticks) también eliminan los caracteres de nueva línea.
Kamil Maciorowski
Bien entonces, agregué un mensaje de advertencia por ahora. Lo eliminaré si encuentro una solución.
Louis-Jacob Lebel