попытка изучить сценарии Bash, я хочу выполнить некоторую команду на всех файлах ниже моего текущего каталога, которые удовлетворяют определенное условие. Используя
find -name *.flac
Конкретно я хочу преобразовать .flac
кому: .mp3
. Я могу найти все файлы. Однако я не вижу различия в выполнении команды с помощью любого опция -exec
для find
и использование xargs
. Например.
find -name *.flac | xargs -i ffmpeg -i {} {}.mp3
по сравнению с
find -name *.flac -exec ffmpeg -i {} {}.mp3 \;
Кто-то может указать на различие? Что такое лучший praticice? Каковы преимущества / недостатки?
Также: Если бы я хотел одновременно удалить исходный файл, как я включил бы вторую команду вышеупомянутый код?
Если Вы не намного более знакомы с xargs
чем -exec
, Вы, вероятно, захотите использовать -exec
когда Вы используете find
.
С тех пор xargs
отдельная программа, называние ее, вероятно, будет незначительно менее эффективным, чем использование -exec
, который является функцией find
программа. Мы обычно не хотим называть дополнительную программу, если она не предоставляет дополнительного преимущества с точки зрения надежности, производительности или удобочитаемости. С тех пор find ... -exec ...
обеспечивает способность к командам выполнения со списком аргументов (как xargs
делает), если это возможно, нет действительно никакого преимущества использования xargs
с find
-exec
. В случае ffmpeg
, мы должны указать входные и выходные файлы, таким образом, мы не можем сделать увеличение производительности с помощью любого метода для построения списка аргументов, и с xargs
удаление нелогичного исходного расширения файла является более трудным.
xargs
делаетПримечание: Подробный флаг (который печатает созданную команду с ее аргументами) в xargs
-t
, и интерактивный флаг (который заставляет пользователя быть предложенным подтверждение воздействовать на каждый аргумент) -p
. Можно найти оба из них полезными для понимания и тестирования его поведения.
xargs
попытки повернуть его STDIN (обычно STDOUT предыдущей команды, которая была передана по каналу к нему) в список аргументов некоторой команде.
command1 | xargs command2 [output of command1 will be appended here]
Начиная с STDOUT или STDIN просто поток текста (это также, почему Вы не должны анализировать вывод ls
), xargs
легко сбит с толку. Это читает аргументы, как разграничиваемые пробелами или новыми строками. Именам файлов позволяют содержать пробелы и могут даже содержать новые строки, и такие имена файлов вызовут неожиданное поведение. Скажем, Вам назвали файл foo bar
. Когда список, содержащий это имя файла, передается по каналу к xargs
, это пытается работать на данной команде foo
и на bar
.
Та же проблема происходит, когда Вы вводите command foo bar
, и Вы знаете, что можно избежать его путем заключения в кавычки пространства или целого имени, например, command foo\ bar
или command "foo bar"
, но даже если мы можем заключить в кавычки список, переданный xargs
мы обычно не хотим, потому что мы не хотим, чтобы целый список рассматривался как отдельный аргумент. Стандартное решение этого состоит в том, чтобы использовать нулевой символ в качестве разделителя, так как имена файлов не могут содержать его:
find path test(s) -print0 | xargs -0 command
Это вызывает find
добавлять нулевой символ к каждому имени файла вместо пространства, и xargs
рассматривать только нулевой символ как разделитель.
Проблемы могут все еще произойти, если команда не принимает несколько аргументов или если список аргументов чрезвычайно длинен.
В этом случае Вы используете ffmpeg
, который ожидает, что входные файлы будут указаны сначала, и выходные файлы, которые будут указаны в последний раз. Мы можем сказать ffmpeg
какие файлы (файлы) использовать в качестве входа явно с -i
флаг, но мы должны дать выходное имя файла (от которого обычно предполагается формат, хотя мы можем также указать его), также. Так, для построения подходящих команд необходимо использовать опцию строки замены (-I
или -i
) из xargs
указывать обоих входные и выходные файлы:
... | xargs -I{} command {} {}.out
(в документации говорится это -i
удерживается от использования с этой целью, и мы должны использовать -I
вместо этого, но я не уверен почему. При использовании -I
, необходимо указать замену ({}
обычно используется), сразу после опции. С -i
можно опустить указывать замену, но {}
понят по умолчанию.)
-I
опция заставляет список команд быть разделенным только на новых строках, не пробелах, поэтому если Вы уверены, что Ваши имена файлов не будут содержать новые строки, Вы не должны использовать -print0 | xargs -0
когда Вы используете -I
. Если Вы не уверены, можно все еще использовать более безопасный синтаксис:
find -name "*.flac" -print0 | xargs -0I{} ffmpeg -i {} {}.mp3
Однако выигрыш в производительности xargs
(который позволяет нам работать, команда однажды со списком аргументов) потерян здесь, с тех пор ffmpeg
должен быть выполнен однажды для каждой пары входных и выходных файлов (Вы видите это легко путем предварительного ожидания echo
кому: ffmpeg
протестировать вышеупомянутую команду). Это также производит нелогичное имя файла и не позволяет Вам выполнять несколько команд. Чтобы сделать последнего, можно звонить bash
, как в ответе десерта:
... | xargs -I{} bash -c 'ffmpeg -i {} {}.mp3 && rm {}'
но переименование хитро.
-exec
отличаетсяКогда Вы используете -exec
опция к find
, найденные файлы передаются как аргументы команде после -exec
. Они не превращены в текст. С синтаксисом:
find ... -exec command {} \;
command
выполняется однажды для каждого найденного файла. С синтаксисом
find ... -exec command {} +
список аргументов создается из найденных файлов так, чтобы мы могли выполнить команду только однажды (или только так же много раз как требуется) на нескольких файлах, дав выигрыш в производительности, обеспеченный xargs
. Однако, так как аргументы имени файла не создаются из потока текста, с помощью -exec
не имеет проблемы xargs
имеет повреждения на пробелах и других специальных символах.
С ffmpeg
, мы не можем использовать +
по той же причине как xargs
не дал выигрыша в производительности; так как мы должны указать оба ввода и вывода, команда должна быть выполнена на каждом файле индивидуально. Мы должны использовать некоторую форму
find -name "*.flac" -exec ffmpeg -i {} {}.out \;
Это, снова, даст Вам скорее нелогично именованный файл, как ответ десерта объясняет, таким образом, можно хотеть разделить его, поскольку ответ десерта объясняет, как сделать с обработкой строк (не легко выполненный xargs
; другая причина использовать -exec
). Это также объясняет, как выполнить несколько команд на файле так, чтобы можно было безопасно удалить исходный файл после успешного преобразования.
Вместо того, чтобы повторить рекомендацию десерта, с которой я соглашаюсь, я предложу альтернативу find
, который позволяет подобную гибкость выполнению bash -c
после -exec
; удар for
цикл:
shopt -s globstar # allow recursive globbing with **
for f in ./**/*.flac; do # for all files ending with .flac
# convert them, stripping the original extension from the new filename
echo ffmpeg -i "$f" "${f%.flac}.mp3" &&
echo rm -v "$f" # if that succeeded, delete the original
done
shopt -u globstar # turn recursive globbing off
Удалите echo
es после тестирования для фактической работы на файлы.
ffmpeg
не распознает --
отметить конец опций, так для предотвращения начала имен файлов -
будучи интерпретированным как опции, мы используем ./
указать на текущий каталог вместо того, чтобы запуститься с **
, так, чтобы все пути начались ./
вместо произвольных имен файлов. Это означает, что мы не должны использовать --
с rm
(который действительно распознает его), также.
Примечание: необходимо заключить Ваш в кавычки -name
проверяемое выражение, если это содержит какие-либо подстановочные символы, иначе оболочка, развернет их, если возможный (т.е. если они соответствуют любым файлам в текущем каталоге), прежде чем они будут переданы find
, так во-первых использовать
find -name "*.flac"
предотвратить неожиданное поведение.
Обычно каждый пытается назвать как можно меньше команд, но в Вашем случае я думаю, что это - вопрос вкуса – я пошел бы с -exec
, использование его как так:
find . -name '*.flac' -exec bash -c 'ffmpeg -i "$0" "${0%flac}mp3" && rm "$0"' {} \;
Прием должен звонить bash
с -c
опция, этот путь можно не только выполнить несколько команд, но также и использовать Замену Параметра Bash для удаления flac
при окончании от имен файлов – я предполагаю, что Вы действительно не хотите заканчивать с файлами под названием filename.flac.mp3, не так ли?
bash -c '…' {}
– выполните команду (команды) …
в bash
с именем файла как первый аргумент (доступный с $0
)${0%flac}
– полоса flac
от конца имени файла&& rm "$0"
– только если предыдущая команда, за которой следуют, удалите исходный файлКак Zanna и десерт, которому уже отвечают -exec
должен быть предпочтен когда xargs
не необходимо ("Мы обычно не хотим называть дополнительную программу, если она не предоставляет дополнительного преимущества с точки зрения надежности, производительности или удобочитаемости".)
В то время как это полностью корректно, я хочу добавить это xargs
в сочетании с -P
флаг может предоставить существенное преимущество с точки зрения производительности.
xargs
породит процессы в параллельной многопоточности включения, подобной, но более гибкий, чем parallel
команда.
-P max-procs, --max-procs=max-procs
Run up to max-procs processes at a time; the default is 1. If max-procs is 0, xargs will run as many processes as possible at a time. Use the -n option or the -L option with -P; other‐
wise chances are that only one exec will be done.
[...]
Это особенно помогает с с процессами, которые не работают многопоточный собой. В Вашем случае ffmpeg
будет заботиться о многопоточности, таким образом, она не поможет или будет даже иметь отрицательный эффект на производительность.
find . -name "*.ext" -print0 | xargs -0 -i -P 20 command -in {} -out {}.out