2010-01-18 9 views
7

a menudo tienen tareas de programación shell donde me encuentro con este patrón:¿La mejor manera de modificar un archivo cuando se usan tubos?

cat file | some_script > file 

Esto no es seguro - gato puede no haber leído en todo el archivo antes de some_script empieza a escribir en él. Realmente no quiero escribir el resultado en un archivo temporal (es lento, y no quiero la complicación adicional de pensar un nuevo nombre único).

Tal vez, hay un comando de shell estándar que almacenará en búfer una secuencia completa hasta que se alcance EOF. Algo como:

cat file | bufferUntilEOF | script > file 

Ideas?

+0

Um, xargs debería hacer el truco, ¿verdad? –

+0

No lo creo. Bueno, quizás sí, pero su documentación dice que el problema que resuelve es manejar casos donde se excede el límite del argumento del comando. No dice que almacena temporalmente todo stdin antes de abrir stdout. – user48956

+0

Creo que hay opciones para xargs que se ocupan del tamaño del almacenamiento en búfer. –

Respuesta

1

Usar un archivo temporal es IMO mejor que intentar almacenar en búfer los datos en la canalización.

Casi anula el propósito de las tuberías de protegerlas.

+0

Bueno, tal vez. Aunque suena como un argumento religioso. Sé que todos los archivos caben fácilmente en una pequeña porción de la memoria principal (mi script de shell funcionará sobre cada archivo fuente en un gran repositorio SVN). El archivo temporal lo hará funcionar el doble de lento que sea necesario (al menos dentro de Cygwin). – user48956

+0

Eso puede ser. Si su código va a ser siempre usado de la manera que usted espera, entonces tiene sentido hacer concesiones juiciosas ... –

+0

@stuartreynolds: Usar un archivo temporal NO lo hará funcionar más despacio, excepto quizás por algunos insignificantes tiempo constante para cambiar el nombre del archivo a su nombre original. – Juliano

3

Está buscando sponge.

+0

Parece una buena solución, excepto que no deseo que todos los usuarios de mis scripts instalen dependencias adicionales (ni compile ningún código). -¿No hay una alternativa usando utilidades estándar o funciones de shell incorporadas? – user48956

+1

No recomiendo esponja. Si falla un comando en su canalización (que no sea esponja) (por ejemplo, debido a un error de sintaxis, argumentos inválidos, etc.), borra el archivo y finaliza sin el archivo original y el archivo de destino. – Juliano

+0

/tmp puede montarse en la memoria (al menos en Linux). En este caso, espero que esto sea realmente rápido. Sin embargo, no estoy seguro sobre/tmp en Cygwin. ¿Cygwin mantiene eso en la memoria? – user48956

4

El uso de un archivo temporal es la solución correcta aquí. Cuando utiliza una redirección como '>', es manejada por el shell, y no importa cuántos comandos haya en su canalización, el shell puede borrar y sobreescribir el archivo de salida antes de ejecutar cualquier comando (durante la configuración de la tubería).

2

El uso de mktemp(1) o tempfile(1) le ahorra el gasto de tener que inventar un nombre de archivo único.

+0

vote up, excellent tool (s). – Anders

1

Creo que la mejor manera es usar un archivo temporal. Sin embargo, si desea otro enfoque, puede usar algo como awk para almacenar en búfer la entrada en la memoria antes de que su aplicación comience a recibir información. La siguiente secuencia de comandos almacenará en búfer toda la entrada en la matriz lines antes de que comience a mostrarla al siguiente consumidor en la tubería.

{ lines[NR] = $0; } 
END { 
    for (line_no=1; line_no<=NR; ++line_no) { 
     print lines[line_no]; 
    } 
} 

Puede colapsar en una sola línea si desea:

cat file | awk '{lines[NR]=$0;} END {for(i=1;i<=NR;++i) print lines[i];}' > file 

Con todo eso, aun así, recomendaría el uso de un archivo temporal para la salida y luego sobrescribir el archivo original con eso.

2

Como muchos otros, me gusta usar archivos temporales. Utilizo el ID de proceso shell como parte del nombre temporal, de modo que si se ejecutan varias copias del script al mismo tiempo, no entrarán en conflicto. Finalmente, solo sobrescribo el archivo original si la secuencia de comandos tiene éxito (usando el operador booleano en cortocircuito, es un poco denso pero muy agradable para líneas de comando simples). Poniendo todo eso en conjunto, se vería así:

some_script <file> smscrpt.$$ && mv smscrpt.$$ file 

Esto dejará el archivo temporal si el comando falla. Si desea limpiar en caso de error, se puede cambiar eso a:

some_script <file> smscrpt.$$ && mv smscrpt.$$ file || rm smscrpt.$$ 

Por cierto, me libró de la mala utilización de gato y lo reemplazó con redirección de entrada.

+0

Gracias - es un buen truco. Sin embargo, se perderá un archivo si algún_script falla.La necesidad de gestionar el caso: "(. Some_script < file > smscrpt $$ && mv smscrpt $$ archivo.) || \ rm -f smscrpt $$". Sin embargo, preferiría algo así como: "(some_script file "porque (i) es mucho más fácil de leer, (ii) no tengo que acordarme de revisar los errores (iii) creo que sería mucho más rápido con Cygwin debido al archivo lento Godawful acceso. – user48956

+2

@stuartreynolds - alguien más publicó sobre esponja y usted rechazó eso porque no es estándar. No hay nada estándar que haga lo que prefiera. –

+1

@klatchko - Creo que algo como Sponge * es * la respuesta que estoy buscando (con las advertencias que mencioné, no es realmente fácil para mí usarla ampliamente). OMI, si realmente no hay nada que haga lo que hace esponja, * y * la funcionalidad de esponja es fundamental para las secuencias de comandos de shell (el almacenamiento en búfer para evitar la corrupción de archivos suena bastante fundamental para mí), entonces probablemente debería ser parte de bash, o el estándar GNU conjunto de herramientas (en cuyo caso espero que alguien señale por qué no necesitamos esponja ... ¿alguien?). ¿Realmente * tengo que hacer un archivo temporal para hacer esto? – user48956

1

En respuesta a the OP's question above sobre el uso de sponge sin dependencias externas, y basándose en @D.Shawley's answer, puede tener el efecto de esponja con sólo una dependencia de gawk, que no es poco común en Unix o sistemas Unix:

cat foo | gawk -voutfn=foo '{lines[NR]=$0;} END {if(NR>0){print lines[1]>outfn;} for(i=2;i<=NR;++i) print lines[i] >> outfn;}' 

La comprobación de NR>0 consiste en truncar el archivo de entrada.

Para usar esto en un script de shell, cambie -voutfn=foo a -voutfn="$1" o la sintaxis que use su shell para los argumentos del nombre del archivo. Por ejemplo:

#!/bin/bash 
cat "$1" | gawk -voutfn="$1" '{lines[NR]=$0;} END {if(NR>0){print lines[1]>outfn;} for(i=2;i<=NR;++i) print lines[i] >> outfn;}' 

Tenga en cuenta que, a diferencia verdadera sponge, esto puede ser limitado al tamaño de la memoria RAM. sponge en realidad almacena temporalmente en un archivo temporal si es necesario.

Cuestiones relacionadas