Verzeichnisstruktur replizieren, indem auf jede Datei ein Befehl angewendet wird, anstatt sie einfach zu kopieren?

Verzeichnisstruktur replizieren, indem auf jede Datei ein Befehl angewendet wird, anstatt sie einfach zu kopieren?

Im Laufe der Zeit bin ich immer wieder auf dasselbe Muster gestoßen: Ich habe eine Art Verzeichnisstruktur:

example/
├── a
│   └── c
│       ├── d.txt (120k)
│       └── e.txt (60k)
└── b
    └── f.txt (280k)

Und ich möchte die Dateien beispielsweise in ein anderes Verzeichnis „kopieren“ und dabei example_greppedauf jede Datei einen Befehl anwenden, als ob er anstelle von cp– sagen wir, auftritt grep ERROR, sodass ich beispielsweise am Ende einen Ordner mit derselben Struktur habe, aber mit Dateien, die durch gefiltert wurden grep.

example_grepped/
├── a
│   └── c
│       ├── d.txt (1k)
│       └── e.txt (0b)
└── b
    └── f.txt (12k)

Dasselbe Muster für die Konvertierung von Mediendateien (FLACs in MP3s, PNGs in JPGs) und dieses Mal auch für die Konvertierung verschiedener Schemaformate als Teil eines Build-Prozesses.

Gibt es einen allgemeinen Befehl, den ich verwenden könnte? So etwas wie foobar example example_grepped --command 'grep ERROR'oder foobar flacs mp3s --command 'ffmpeg -i {} {}.mp3'?

Eine obskure xargsFlagge vielleicht? (ein finddurchgereichtes Zeichen xargswürdefastreichen aus, aber die meisten, wenn nicht alle Befehle setzen voraus, dass die Verzeichnisstruktur bereits vorhanden ist.)

Antwort1

Die beste Antwort, die ich finden kann, ohne die Verzeichnisstruktur separat neu zu erstellen, ist die VerwendungInstallieren:

cd example
find . -type f -exec sh -c 'grep ERROR {} | install -D /dev/stdin /tmp/example_grepped/{}' \;

Leider kann das oben genannte nur funktionieren, wenn Ihr Befehl sein Ergebnis an STDOUT ausgeben kann.

Antwort2

Eine andere Möglichkeit, dies zu erreichen, besteht darin, ein Programm zu verwenden, das ohnehin rekursive Kopien erstellt. Ich habe nachgesehen rsync, konnte aber auf den ersten Blick keine Rückrufoption finden. Aber GNU tarhat eine Option --to-command, für die Sie einen auszuführenden Befehl angeben können, der die Eingabe der Datei in abruft stdin. Aber wie lässt sich die Datei dann erstellen? Nun, der aufgerufene Befehl findet den aktuellen Dateinamen in $TAR_FILENAME.

Zusammengefasst lautet der grundlegende Aufruf

tar cf - example | tar xf - --to-command="./script example_grepped 'grep-pattern'"

wobei das Skript etwa so lauten könnte:

#!/bin/bash
mkdir -p $(dirname "$1/$TAR_FILENAME")
grep '$2' >"$1/$TAR_FILENAME"
exit 0

Eine andere Möglichkeit, dies zu erreichen, besteht darin, die Tar-Pipe in ein Skript einzubinden, das den Befehl auf der Befehlszeile ausführt. Allerdings mkdir ...dirnamewird das Entkommen für die Konstruktion etwas schwierig sein.

Antwort3

#!/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'"

Dieses Skript funktioniert auch mit Verzeichnis- und Dateinamen, die Leerzeichen enthalten.

Führen Sie dieses Skript dort aus, wo sich das Quellverzeichnis („Beispiel“) befindet.

Antwort4

Mit GNU Parallel können Sie Folgendes tun:

cd src
find . -type f | parallel 'mkdir -p ../dst/{//}; dostuff --input {} --output ../dst/{}'

verwandte Informationen