¿Es la redirección con `>>` equivalente a `>` cuando el archivo de destino aún no existe?

80

Considere una concha como Bash o sh. La diferencia básica entre >y se >>manifiesta en un caso cuando existe el archivo de destino:

  • > trunca el archivo a tamaño cero, luego escribe;
  • >> no se trunca, escribe (agrega) al final del archivo.

Si el archivo no existe, se crea con tamaño cero; luego escrito a. Esto es cierto para ambos operadores. Puede parecer que los operadores son equivalentes cuando el archivo de destino aún no existe.

¿Son realmente?

Kamil Maciorowski
fuente

Respuestas:

107

tl; dr

No. >>es esencialmente "siempre buscar el final del archivo" mientras >mantiene un puntero a la última ubicación escrita.


Respuesta completa

(Nota: todas mis pruebas realizadas en Debian GNU / Linux 9).

Otra diferencia

No, no son equivalentes. Hay otra diferencia Puede manifestarse independientemente de si el archivo de destino existía antes o no.

Para observarlo, ejecute un proceso que genere datos y redirija a un archivo con >o >>(por ejemplo pv -L 10k /dev/urandom > blob). Deje que se ejecute y cambie el tamaño del archivo (por ejemplo, con truncate). Verá que >mantiene su desplazamiento (creciente) mientras >>siempre se agrega al final.

  • Si trunca el archivo a un tamaño más pequeño (puede ser de tamaño cero)
    • >no le importará, escribirá en el desplazamiento deseado como si nada hubiera pasado; justo después de truncar el desplazamiento está más allá del final del archivo, esto hará que el archivo recupere su tamaño anterior y crezca aún más, los datos faltantes se rellenarán con ceros (de manera dispersa, si es posible);
    • >> se agregará al nuevo final, el archivo crecerá desde su tamaño truncado.
  • Si amplías el archivo
    • >no le importará, escribirá en el desplazamiento deseado como si nada hubiera pasado; justo después de cambiar el tamaño, el desplazamiento está en algún lugar dentro del archivo, esto hará que el archivo deje de crecer por un tiempo, hasta que el desplazamiento llegue al nuevo final, entonces el archivo crecerá normalmente;
    • >> se agregará al nuevo final, el archivo crecerá desde su tamaño ampliado.

Otro ejemplo es agregar (con un aparte >>) algo extra cuando el proceso de generación de datos se está ejecutando y escribiendo en el archivo. Esto es similar a agrandar el archivo.

  • El proceso de generación con >escribirá en su desplazamiento deseado y eventualmente sobrescribirá los datos adicionales.
  • El proceso de generación con >>omitirá los nuevos datos y los agregará más allá (puede ocurrir una condición de carrera, las dos transmisiones pueden intercalarse, aún no se deben sobrescribir los datos).

Ejemplo

¿Importa en la práctica? Hay esta pregunta :

Estoy ejecutando un proceso que produce una gran cantidad de resultados en stdout. Enviarlo todo a un archivo [...] ¿Puedo usar algún tipo de programa de rotación de registros?

Esta respuesta dice que la solución es logrotatecon la copytruncateopción que actúa así:

Trunca el archivo de registro original en su lugar después de crear una copia, en lugar de mover el archivo de registro anterior y, opcionalmente, crear uno nuevo.

De acuerdo con lo que escribí anteriormente, la redirección con >hará que el registro truncado sea grande en poco tiempo. La escasez salvará el día, no debe desperdiciarse un espacio significativo en el disco. Sin embargo, cada registro consecutivo tendrá cada vez más ceros a la izquierda que son completamente innecesarios.

Pero si logrotatecrea copias sin preservar la escasez, estos ceros iniciales necesitarán más y más espacio en disco cada vez que se haga una copia. No he investigado el comportamiento de la herramienta, puede ser lo suficientemente inteligente con escasez o compresión sobre la marcha (si la compresión está habilitada). Aún así, los ceros solo pueden causar problemas o ser neutrales en el mejor de los casos; Nada bueno en ellos.

En este caso, usar en >>lugar de >es significativamente mejor, incluso si el archivo de destino está a punto de crearse todavía.


Actuación

Como podemos ver, los dos operadores actúan de manera diferente no solo cuando comienzan sino también más tarde. Esto puede causar alguna diferencia (¿sutil?) De rendimiento. Por ahora no tengo resultados de prueba significativos para respaldarlo o refutarlo, pero creo que no debería suponer automáticamente que su rendimiento es el mismo en general.

Kamil Maciorowski
fuente
99
Por >>lo tanto, es esencialmente "siempre buscar el final del archivo" mientras se >mantiene un puntero a la última ubicación escrita. Parece que también puede haber una sutil diferencia de rendimiento en la forma en que funcionan ...
Mokubai
10
En el nivel de llamada del sistema, >>usa el O_APPENDindicador paraopen() . Y en realidad, >usa O_TRUNC, mientras >>que no. La combinación de O_TRUNC | O_APPENDtambién sería posible, el lenguaje de shell simplemente no proporciona esa característica.
ilkkachu
3
@jjmontes, la fuente estándar sería POSIX: pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/… pero, por supuesto, el manual de Bash también tiene descripciones sobre los operadores de redireccionamiento, incluidos los no estándar que admite: gnu.org/ software / bash / manual / html_node / Redirections.html
ilkkachu
2
@ilkkachu Encontré que esto es de interés, ya que explica los detalles sobre O_APPEND que me preguntaba después de tu comentario :): stackoverflow.com/questions/1154446/…
jjmontes
1
@Mokubai, cualquier sistema operativo sano tendría la longitud del archivo a mano cuando esté abierto, y comprobar una bandera y mover el desplazamiento hasta el final debería desaparecer en todas las demás contabilidad. Sin embargo, tratar de emular O_APPENDcon un lseek()antes de cada uno write()sería diferente, habría una sobrecarga de llamadas al sistema adicional. (Y, por supuesto, no funcionaría, ya que otro proceso podría estar write()en el medio.)
ilkkachu