Procesar un solo archivo como entrada y salida a través de tuberías

Procesar un solo archivo como entrada y salida a través de tuberías

Buenas noches,

Me gustaría filtrar el contenido de un archivo con algunos comandos canalizados y luego escribir el resultado en el mismo archivo. Lo sé, no puedo hacer eso como lo escribí. Esperar …

Este es el fragmento de script bash que tengo.

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"

Así que pensé que podría tener éxito utilizando la sustitución de procesos. Entonces escribí:

grep '^[a-zA-Z.:]' < <(cat "$filepath") | …

Esto tampoco solucionó nada. Esperaba que la sustitución del proceso «guardara» el contenido de mi archivo de entrada en algún lugar, como en un archivo temporal. Parece que tampoco he entendido la sustitución de procesos.

Leí hilos sobre la edición "in situ", pero estos artículos resaltaron opciones especiales de algunos binarios como sed -io, sort -opero necesito una solución general (quiero decir, tiene que adaptarse a cualquier comando canalizado).

Primero, ¿por qué las 'tuberías estándar' no pueden hacer esto? ¿Qué está sucediendo debajo? :/¿Y cómo debería solucionar mi problema? ¿Alguien podría por favor?explicaryo ¿de qué se trata todo esto?

Gracias.

Respuesta1

Como se ha mencionado, la esponja demásutilses genial. Utilizo este script para emular y evitar la dependencia de moreutils:

#!/bin/sh -e
#Soak up input and tee it to arguments
st=0; tmpf=
tmpf="`mktemp`" && exec 3<>"$tmpf" || st="$?"
rm -f "$tmpf" #remove it even if exec failed; noop if mktemp failed
[ "$st" = 0 ] || exit "$st"
cat >&3
</dev/fd/3 tee "$@" >/dev/null

Puedes usarlo así:

grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u | sponge "$filepath" 

No puede hacer esto con una redirección de salida simple porque las redirecciones tienen lugar antes de que se inicien los comandos y una redirección de salida trunca el archivo de salida.

En otras palabras, cuando se inicia grep (el primer comando simple de la canalización), la última redirección ya ha truncado el archivo de entrada/salida.

En realidad, hasta donde yo sé, no existen utilidades estándar de UNIX que realicen una verdadera edición in situ. sed -isólo lo emula con un archivo temporal. Supongo que la razón es que el verdadero filtrado local puede dañar fácilmente el archivo si falla un paso de la canalización.

En cuanto a lo que sucede debajo, ambos |usan <()tuberías del sistema que pasan IO un búfer a la vez. El mecanismo no crea archivos temporales (no archivos reales (sistema de archivos) de todos modos) y trata de evitar mantener toda la entrada en la memoria a la vez.

Respuesta2

Si desea ingresar y enviar al mismo archivo, puede intentaresponja. Como dice su descripción:

sponge reads standard input and writes it out to the specified file. 
Unlike a shell redirect, sponge soaks up all its input before writing 
the output file. This allows constructing pipelines that read from and 
write to the same file.

Entonces puedes tener algo como sed '...' file | grep '...' | sponge [-a] filerecibir información dearchivoy saliendo al mismoarchivo.


Por otro lado, utilizar archivos temporales también es una excelente manera de trabajar con el mismo archivo para entrada y salida. Puede inicializar sus archivos temporales de la siguiente manera:

tempfile=`mktemp tempFile.XXXX` # You can replace "tempFile" with any name you want

Esto crea un archivo temporal llamado "tempFile" en el directorio donde se ejecuta este script, con la extensión "XXXX" donde las x se reemplazan con una combinación del número de proceso actual y letras aleatorias (por ejemplo, tempFile.AVm7).

Ahora puedes modificar tu canalización (o cualquier comando canalizado) de la siguiente manera:

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$tempfile"

Después del filtro, puede mover su archivo temporal a su archivo original de la siguiente manera:

mv "$tempfile" "$filepath"

Esto elimina su archivo temporal y usted permanece con el archivo original filtrado. Pero, a veces, puedes terminar creando muchos archivos temporales que quizás no necesites y no hayas destruido, por lo que es una buena idea limpiar tu directorio eliminando todos los archivos temporales después de que finalice el script si ya no los necesitas. . Puedes escribir una rutina para eso de la siguiente manera:

remove_temp_files() {
    rm `find . -name "tempFile.????"`
}

Luego, puede simplemente invocar su rutina remove_temp_filesal final de su secuencia de comandos, eliminando todos y cada uno de los archivos temporales que se crearon en el formato descrito anteriormente.

Respuesta3

UsandoAquí-DocumentoySustitución de comandoes la forma estándar de proceder en este caso:

grep '^[a-zA-Z.:]' <<IN \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"
$(cat -- "$filepath")
IN

Para otras preguntas, se explicaron en muchas preguntas antes:

información relacionada