Перенаправление stdout с помощью find -exec и без создания новой оболочки

У меня есть один скрипт, который только записывает данные в stdout. Мне нужно запустить его для нескольких файлов и создать другой выходной файл для каждого входного файла, и мне было интересно, как использовать для этого find -exec. Поэтому я в основном пробовал несколько вариантов этого (я заменил скрипт cat только для целей проверки):

find * -type f -exec cat "{}" > "{}.stdout" \;

но не смог заставить его работать, поскольку все данные записывались в файл, буквально названный {}.stdout.

В конце концов, я мог бы работать с:

find * -type f -exec sh -c "cat {} > {}.stdout" \;

Но в то время как эта последняя форма хорошо работает с cat, мой сценарий требует, чтобы переменные среды загружались через несколько сценариев инициализации, таким образом, я получаю:

find * -type f -exec sh -c "initscript1; initscript2; ...;  myscript {} > {}.stdout" \;

Что кажется пустой тратой, потому что у меня есть все, что уже было инициализировано в моей текущей оболочке.

Есть ли лучший способ сделать это с find? Другие однострочные линии приветствуются.

Ответ 1

Простым решением было бы разместить обертку вокруг вашего script:

#!/bin/sh

myscript "$1" > "$1.stdout"

Назовите его myscript2 и вызовите его с помощью find:

find . -type f -exec myscript2 {} \;

Обратите внимание, что, хотя большинство реализаций find позволяют делать то, что вы сделали, технически поведение find не указано, если вы используете {} более одного раза в списке аргументов -exec.

Ответ 2

Вы можете сделать это с помощью eval. Это может быть уродливо, но для этого нужно сделать оболочку script. Кроме того, все это на одной линии. Например

find -type f -exec bash -c "eval md5sum {}  > {}.sum " \;

Ответ 3

Если вы экспортируете переменные среды, они уже будут присутствовать в дочерней оболочке (если вы используете bash -c вместо sh -c, а ваша родительская оболочка сама является bash, то вы также можете экспортировать функции в родительскую оболочку и использовать их в дочернем элементе, см. export -f).

Кроме того, используя -exec ... {} +, вы можете ограничить количество оболочек до наименьшего возможного числа, необходимого для передачи всех аргументов в командной строке:

set -a # turn on automatic export of all variables
source initscript1
source initscript2

# pass as many filenames as possible to each sh -c, iterating over them directly
find * -name '*.stdout' -prune -o -type f \
  -exec sh -c 'for arg; do myscript "$arg" > "${arg}.stdout"' _ {} +

В качестве альтернативы вы можете просто выполнить выполнение в текущей оболочке напрямую:

while IFS= read -r -d '' filename; do
  myscript "$filename" >"${filename}.out"
done < <(find * -name '*.stdout' -prune -o -type f -print0)

См. UsingFind обсуждение безопасного и правильного выполнения массовых действий через find; и BashFAQ # 24 обсуждать использование подстановки процесса (синтаксис <(...)), чтобы гарантировать, что операции выполняются в родительской оболочке.