Управлять именем файла, переданным из команды поиска

10

Я относительно новичок в Bash и пытаюсь сделать что-то, что на первый взгляд кажется довольно простым - запустите find по иерархии каталогов, чтобы получить все файлы * .wma, канал, который выводит команду, где я конвертирую их в mp3 и сохраните преобразованный файл как .mp3. Я думал, что команда должна выглядеть следующим образом (я перестал использовать команду преобразования звука и вместо этого использую echo для иллюстрации):

$ find ./ -name '*.wma' -type f -print0 | xargs -0 -I f echo ${f%.*}.mp3

Насколько я понимаю, аргумент -print0 позволит мне обрабатывать имена файлов с пробелами (что многие из них делают, поскольку они являются музыкальными файлами). Затем я ожидаю (в результате xargs), что каждый путь к файлу из find будет записан в f, и что с использованием совпадения / удаления подстроки в конце строки, что я должен повторить исходный путь к файлу с mp3 расширение вместо WMA. Однако вместо этого результата я вижу следующее:

*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
...

Таким образом, мой вопрос (помимо конкретного «что я здесь делаю неправильно») заключается в следующем: нужно ли обрабатывать значения, являющиеся результатом операции конвейера, в операциях манипуляции со строками иначе, чем значения, которые являются результатом присваивания переменной ?

Говард Диркинг
источник
1
Там нет причин для использования xargsс find. Он поставляется с -execопцией. Вы можете просто добавить команду, которую собираетесь использовать, к своему вопросу, и кто-то может показать вам правильную findкоманду?
ixtmixilix
да (как я отметил ниже), до тех пор, пока я все еще могу выполнять манипуляции со строками для каждого результата команды поиска (например, {}члена)
Говард Диркинг,
на самом деле есть крайние случаи, когда xargsэто более подходит, чем exec. См. Этот столбец стека stackoverflow.com/questions/896808/find-exec-cmd-vs-xargs для конкретного случая.
d34dh0r53
@ d34dh0r53 Какой крайний случай? Нить, на которую вы ссылаетесь, не указывает ни на что.
Жиль "ТАК - перестань быть злым"

Ответы:

8

Как уже определены другие ответы, ${f%.*}расширяется оболочкой, прежде чем она запускает xargsкоманду. Это расширение необходимо выполнить один раз для каждого имени файла с переменной оболочки, для которой fзадано имя файла (передача -I fне делает этого: xargsне имеет понятия о переменной оболочки, она ищет строку fв команде, так что если вы используется, например, xargs -I e echo …он будет выполнять команды, такие как ./somedir/somefile.wmacho .mp3).

Продолжая этот подход, скажите, xargsчтобы вызвать оболочку, которая может выполнить расширение. Лучше сказать, find- xargsэто в значительной степени устаревший инструмент, и его трудно использовать правильно, поскольку современные версии find имеют конструкцию, которая выполняет ту же работу (и более) с меньшим количеством трудностей. Вместо того find … -print0 | xargs -0 command …, чтобы бежать find … -exec command … {} +.

find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +

Аргумент _находится $0в оболочке; имена файлов передаются как позиционные аргументы, которые for f; do …зацикливаются. Более простая версия этой команды выполняет отдельную оболочку для каждого файла, что эквивалентно, но немного медленнее:

find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;

Вам на самом деле не нужно использовать findздесь, если вы используете достаточно новую оболочку (ksh93, bash ≥4.0 или zsh). В bash, вставьте shopt -s globstarваш, .bashrcчтобы активировать **/шаблон glob для повторения в подкаталогах (в ksh, это set -o globstar). Тогда вы можете запустить

for f in **/*.wma; do
  echo "${f%.*}.mp3"
done

(Если у вас есть вызванные каталоги *.wma, добавьте [ -f "$f" ] || continueв начале цикла.)

Жиль "ТАК - перестань быть злым"
источник
отличные объяснения, а также указали мне направление, которое я даже не осознал (обновление оболочки bash для получения функциональности globstar) - в конце концов, рекурсивное сглаживание было решением, которое делало команду простой и выполняло все, что я пытался сделать , Спасибо!
Говард Диркинг
6

В случае вашего решения оценка $ {f%. *}. Mp3 выполняется в оболочке, в которую вы вызываете всю команду, а не в оболочке, разветвленной xargs . И в вашей оболочке нет переменной f , она заменяется пустой строкой.

Решение с использованием xargs с -I f :

% cat 1.sh 
#!/bin/sh
f=$1
echo ${f%.*}.mp3
% /bin/ls -1
1.sh
aaa.wma
bbbb.wma
ccccc.wma
% find ./ -name '*.wma' -type f -print0 | xargs -0 -I f ./1.sh f
./aaa.mp3
./ccccc.mp3
./bbbb.mp3

Но я бы сделал это так:

% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'
0xFF
источник