Реплицировать структуру каталогов, применяя команду к каждому файлу вместо простого его копирования?

Реплицировать структуру каталогов, применяя команду к каждому файлу вместо простого его копирования?

Со временем я снова и снова сталкивался с одной и той же картиной: у меня есть некая структура каталогов:

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

И я хочу «скопировать» файлы в другой каталог, скажем, example_greppedприменив команду к каждому из них, как будто вместо cp, скажем, grep ERRORтак, чтобы в итоге у меня получилась папка с той же структурой, но с файлами, отфильтрованными через grep.

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

Тот же шаблон для преобразования медиафайлов (FLAC в MP3, PNG в JPG), и на этот раз при преобразовании различных форматов схем в рамках процесса сборки.

Есть ли какая-то общая команда, которую я мог бы использовать? Что-то вроде foobar example example_grepped --command 'grep ERROR'или foobar flacs mp3s --command 'ffmpeg -i {} {}.mp3'?

Может быть, какой-то непонятный xargsфлаг? ( findпропущенный через трубу флаг)xargsпочти(достаточно, но большинство, если не все команды, ожидают, что структура каталогов уже существует.)

решение1

Самый близкий ответ, который я могу найти без отдельного пересоздания структуры каталогов, это использоватьустановить:

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

К сожалению, вышеизложенное может работать только в том случае, если ваша команда может вывести свой результат в STDOUT.

решение2

Другой способ подойти к этому — использовать программу, которая в любом случае делает рекурсивные копии. Я проверил rsync, но не смог найти опцию обратного вызова при беглом взгляде. Но в gnu tarесть опция --to-command, для которой вы можете указать команду для запуска, которая помещает входные данные файла в stdin. Но как тогда создать файл? Ну, вызванная команда находит текущее имя файла в $TAR_FILENAME.

Если собрать все вместе, то основной призыв таков:

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

где сценарий может быть чем-то вроде

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

Другой способ решения этой проблемы — обернуть tar pipe в скрипт, который запустит команду в командной строке. Однако экранирование конструкции mkdir ...dirnameбудет немного сложным.

решение3

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

Этот скрипт также работает с именами каталогов и файлов, содержащими пробелы.

Запустите этот скрипт там, где находится исходный каталог («пример»).

решение4

Используя GNU Parallel вы можете сделать что-то вроде этого:

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

Связанный контент