¿Replicar la estructura de directorios aplicando un comando a cada archivo en lugar de simplemente copiarlo?

¿Replicar la estructura de directorios aplicando un comando a cada archivo en lugar de simplemente copiarlo?

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_greppedaplicando un comando a cada uno como si en lugar de cp... grep ERRORdigamos, 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 findtuberíaxargscasisuficiente, 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 tartiene una opción --to-commandpara 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 ...dirnameconstrucció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/{}'

información relacionada