
Con el tiempo, me he encontrado con el mismo patrón una y otra vez: tengo algún tipo de estructura de directorios:
example/
├── a
│ └── c
│ ├── d.txt (120k)
│ └── e.txt (60k)
└── b
└── f.txt (280k)
Y quiero "copiar" los archivos a otro directorio, digamos, example_grepped
aplicando un comando a cada uno como si en lugar de cp
... grep ERROR
digamos, así que termino con una carpeta con la misma estructura pero con archivos filtrados a través del grep
.
example_grepped/
├── a
│ └── c
│ ├── d.txt (1k)
│ └── e.txt (0b)
└── b
└── f.txt (12k)
El mismo patrón para convertir archivos multimedia (FLAC a MP3, PNG a JPG), y esta vez al convertir diferentes formatos de esquema como parte de un proceso de compilación.
¿Existe algún comando genérico que pueda usar? Algo como foobar example example_grepped --command 'grep ERROR'
o foobar flacs mp3s --command 'ffmpeg -i {} {}.mp3'
?
¿Quizás una bandera oscura xargs
? ( una find
tuberíaxargs
casisuficiente, pero la mayoría, si no todos, los comandos esperan que la estructura del directorio ya exista).
Respuesta1
La respuesta más cercana que puedo encontrar sin recrear por separado la estructura del directorio es usarinstalar:
cd example
find . -type f -exec sh -c 'grep ERROR {} | install -D /dev/stdin /tmp/example_grepped/{}' \;
Desafortunadamente, lo anterior solo puede funcionar si su comando puede enviar su resultado a STDOUT.
Respuesta2
Otra forma de abordar esto es utilizar un programa que realice copias recursivas de todos modos. Revisé rsync
, pero no pude encontrar una opción de devolución de llamada en un vistazo rápido. Pero gnu tar
tiene una opción --to-command
para la cual puede proporcionar un comando para ejecutar que obtenga la entrada del archivo en stdin
. Pero, ¿cómo crear el archivo entonces? Bueno, el comando llamado busca el nombre del archivo actual en $TAR_FILENAME
.
Poniéndolo todo junto, la llamada básica es
tar cf - example | tar xf - --to-command="./script example_grepped 'grep-pattern'"
donde el guión podría ser algo como
#!/bin/bash
mkdir -p $(dirname "$1/$TAR_FILENAME")
grep '$2' >"$1/$TAR_FILENAME"
exit 0
Otra forma de abordar esto sería envolver la tubería de alquitrán en un script que haga que el comando se ejecute en la línea de comando. Sin embargo, escapar de la mkdir ...dirname
construcción será un poco desafiante.
Respuesta3
#!/bin/bash
filter() {
local target_root="${@: -1}"
target_path=$(sed -E "s/[^/]*/$target_root/" <<< "$1")
target_dir=$(dirname "$target_path")
mkdir -p "$target_dir"
if [[ -f $1 ]]; then
# do your grep thing here
grep burger "$1" > "$target_path"
fi
}
export -f filter
source_root="example"
target_root="example_grepped"
find "$source_root/" -print0 | xargs -0 -I content bash -c "filter 'content' '$target_root'"
Este script también funciona con nombres de directorios y archivos que contienen espacios.
Ejecute este script donde se encuentra el directorio fuente ("ejemplo").
Respuesta4
Usando GNU Parallel puedes hacer algo como esto:
cd src
find . -type f | parallel 'mkdir -p ../dst/{//}; dostuff --input {} --output ../dst/{}'