find exec '{}' недоступен после>

8

Exec позволяет нам передавать все аргументы одновременно {} +или передавать их по одному{} \;

Теперь предположим, что я хочу переименовать все в формате JPEG , без проблем это сделать:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec mv '{}' '{}'.new \;

Но если мне нужно перенаправить вывод, '{}'не доступен после перенаправления.

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjpeg -quality 80 '{}' > optimized_'{}' \;

Это не сработает. Я должен был бы использовать цикл for, сохраняя результаты поиска в переменную перед использованием. Допустим, это громоздко.

for f in `find . \( -name '*.jpg' -o -name '*.jpeg' \)`; do cjpeg -quality 80 $f > optimized_$f; done;

Так есть ли лучший способ?

Buzut
источник
Нет ли >пропущенного в третьем примере кода?
Чороба
1
Даже ваша первая строка нестандартна и, следовательно, непереносима. Старайтесь избегать командных строк, где они {}появляются в более длинных строках, так как такие строки обычно не раскрываются.
Шили
Этот первый пример не делает то, что вы говорите.
Ctrl-Alt-Delor
1
Вы исправили свой вопрос: я бы больше не менял его.
Ctrl-Alt-Delor
1
Как бы то ни было, основная причина того, что ваше перенаправление не работает так, как вы хотели, состоит в том, что оно обрабатывается оболочкой, из которой вы запускаете find, один раз и применяется к самой findкоманде. В {}этом контексте не имеет особого значения. Перенаправление не является аргументом findи, конечно же, не является частью -execпредложения.
Джон Боллинджер

Ответы:

13

Вы можете использовать bash -cв find -execкоманде и использовать позиционный параметр с командой bash:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec bash -c 'cjpeg -quality 80 "$1" > "$(dirname "$1")/optimized_$(basename "$1")"' sh {} \;

Этот способ {}обеспечен $1.

shПеред {}говорит внутренняя оболочка его «имя», то строка , используемая здесь используется, например , в сообщениях об ошибках. Это обсуждается больше в этом ответе на stackoverflow .

Олив
источник
8

У вас есть ответ ( https://unix.stackexchange.com/a/481687/4778 ), но вот почему.

Перенаправление >, а также каналы |и $расширение выполняются оболочкой до выполнения команды. Поэтому stdout перенаправляется до того optimized_{}, как findбудет запущен.

Ctrl-Alt-Делор
источник
3

Перенаправление должно быть заключено в кавычки, чтобы текущая оболочка не интерпретировала его.
Но его цитирование также позволит избежать перенаправления вывода команды.
Известное решение для этого заключается в вызове оболочки:

find . -name '*.jpg' -exec sh -c 'echo "$1" >"$1".new' called_shell '{}' \;

В этом случае redirection ( >) заключена в кавычки в текущей оболочке и работает правильно внутри вызываемой оболочки. called_shellИспользуется в качестве $0параметра (имя) ребенка оболочки ( sh).

Это хорошо работает, если к имени файла добавляется суффикс, но не при использовании префикса. Для того чтобы префикс работал, вам нужно удалить и ./найти префикс перед именами файлов ${1#./}и использовать эту -execdirопцию.

Вы можете (или не может) необходимо использовать -inameпараметр , чтобы файлы с именем *.JPGили *.JpGили другие варианты также включены.

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     cjpeg -quality 80 "$1" > optimized_"${1#./}"
     ' called_shell '{}' \;

И вы можете (или не можете) также вызывать оболочку один раз для каждого каталога, а не один раз для каждого файла, добавив loop ( for f do … ; done) и a +в конце:

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     for f; do cjpeg -quality 80 "$f" > optimized_"${f#./}"; done
     ' called_shell '{}' \+

И, наконец, поскольку cjpegесть возможность прямой записи в файл, перенаправления можно избежать как:

find . \( -iname '*.jpg' -o -iname '*.jpeg' \) -execdir sh -c '
     for f; do cjpeg -quality 80 "$f" -outfile optimized_"${f#./}"; done
     ' called_shell '{}' \+
Исаак
источник
3

cjpegимеет опцию, которая позволяет вам писать в именованный файл, а не стандартный вывод. Если ваша версия findподдерживает этот -execdirпараметр, вы можете воспользоваться этим, чтобы сделать перенаправление ненужным.

find . \( -name '*.jpg' -o -name '*.jpeg' \) \
  -execdir cjpeg -quality 80 -outfile optimized_'{}' '{}' \;

Примечание: это фактически предполагает версию BSD find, которая, по-видимому, ./удаляет начальную букву из имени файла при расширении до {}. (Или наоборот, GNU find добавляет ./ к названию. Нет никакого стандарта, чтобы сказать, какое поведение является "правильным".)

chepner
источник
Если вы findподдерживаете -execdir, вы можете использовать это вместо -exec. Это заставляет команду выполняться в каталоге, где файл был найден, и {}станет aa.jpgвместо ./t2/aa.jpg.
chepner
Тем не менее по ошибке: cjpeg: can't open optimized_./aa.jpg.
Исаак
Хм, похоже, это разница между GNU findи BSD find. (Опасности использования нестандартных расширений.)
chepner
Имя файла без начального, ./кажется, более склонно к ошибкам. В этом ответе предлагается разумное решение .
Исаак
1

Создайте скрипт cjq80:

#!/bin/bash
cjpeg -quality 80 "$1" > "${1%/*}"/optimized_"${1##*/}"

Сделайте это исполняемым

chmod u+x cjq80

И используйте его в -exec:

find . \( -name '*.jpg' -o -name '*.jpeg' \) -exec cjq80 '{}' \;
choroba
источник
Я хоть и об этом, но это не очень удобно. Тем более, что мне пришлось включить это в процесс сборки
Buzut
Просто добавьте сценарий к другим сценариям сборки, я считаю, что он более читабелен, чем дважды вложенный bash -c.
Чороба
Ну, это вопрос вкуса. Теперь каждый параметр указан, поэтому, если кто-то сталкивается с той же проблемой, он выберет для себя :)
Buzut