как передать результат `find` в виде списка файлов?

11

Ситуация такова, у меня есть MP3-плеер, mpg321который принимает список файлов в качестве аргумента. Я храню свою музыку в каталоге под названием «музыка», в котором есть еще несколько каталогов. Я просто хочу сыграть все из них, поэтому я запускаю программу с

mpg321 $(find /music -iname "*\.mp3")

, Проблема в том, что в некоторых именах файлов есть пробелы, и программа разбивает эти имена на более мелкие части и жалуется на отсутствующие файлы. Завершение результата findв кавычки

mpg321 "$(find /music -iname "*\.mp3")"

не помогает, потому что все станет одним большим «именем файла», которое явно не найдено.

Как я могу это сделать тогда? Если это имеет значение, я использую bash, но скоро переключусь на zsh.

phunehehe
источник

Ответы:

17

Попробуйте использовать find -print0или -printfoption в сочетании с xargsтаким:

find /music -iname "*\.mp3" -print0 | xargs -0 mpg321

Как это работает, объясняется на странице руководства find :

-print0

Правда; напечатайте полное имя файла на стандартном выводе, за которым следует нулевой символ (вместо символа новой строки, который используется -print). Это позволяет правильно интерпретировать имена файлов, содержащие символы новой строки или другие типы пробелов, программами, обрабатывающими результаты поиска. Эта опция соответствует опции -0 в xargs.

Стивен Д
источник
Извините за все редактирование. Кажется, я помню, что шаблон -print0 xarg -0 не работает на других типах пробелов, но я не смог найти пример.
Стивен Д
о да, это работает! но я все еще удивляюсь, почему mpg321 $(find /music -iname "*\.mp3" -print0)нет?
phunehehe
1
Что ж, я считаю, что это не работает по двум причинам: (1) имена файлов разделены нулевыми символами, чего совсем не ожидает mpg321 и (2) xargs выполняет работу по удалению других пробелов, а не находить.
Стивен Д
2
@phunehehe, @Steven: не mpg321имеет к этому никакого отношения, это оболочка, которая разбивает вывод findна отдельные аргументы. И -print0 | xargs -0будет работать с каждым возможным именем файла.
Жиль "ТАК - перестань быть злым"
8
find /music -iname "*\.mp3" -exec mpg123 {} +

С GNU find вы также можете использовать -print0иxargs -0 , но нет смысла изучать еще один инструмент. -exec ... {} +Синтаксис получает мало упоминания , потому что Linux приобрел позже , чем -print0, но нет никаких причин , чтобы не использовать его сейчас.

С zsh или bash 4 это намного проще:

mpg123 **/*.[Mm][Pp]3

Только в zsh вы можете сделать (часть a) шаблон без учета регистра:

mpg123 (#i)**/*.mp3
Жиль "ТАК - прекрати быть злым"
источник
+1 к этому, "find -print0" - ужасный хак.
p-static
2

Я думаю, что решение Стивена лучше, но другой способ - использовать -Iфлаг xargs , который позволяет вам указать строку, которая затем будет заменена в команде аргументом (вместо простого добавления аргумента в конец команды). Вы можете использовать это, чтобы процитировать аргумент:

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"
Михаил Мрозек
источник
Я не понимаю этот ответ ... Вы используете -0флаг Xargs без Find -print0. Кроме того, -Iфлаг Xargs имеет ряд последствий. В отличие от простого, xargsкоторый будет сжимать все строки из stdin в одну команду, он xargs -Iбудет выполняться mpg321потенциально сотни или тысячи раз (по одному разу для каждого файла), что, конечно, не является целью. Также имейте в виду, что указывать foo не нужно, так как xargsэто делается внутри. Обратите внимание, что если вы сожмете все имена файлов в строку и отправите их xargs -I, они не откроются.
6
1

Обычно лучше напрямую, -exec ${tgt_process} \{\} +но если по какой-либо причине вам необходимо получить надежно разграниченный список имен файлов в файле или потоке find, то вы можете сделать это:

find -exec sh -c 'printf "///%s///\n" "$@"' -- \{\} +

Из этого вы получаете две уникальные строки. В начале каждого имени файла находится строка, \n///а в конце каждого имени файла - строка ///\n. Эти две строки не встречаются нигде в findвыходных данных, кроме как в тех позициях, независимо от того, какие символы содержатся в именах файлов.

Кроме того, вышеприведенное использование является базовым переносимым POSIX и может использоваться практически для любой системы Unix. Это не относится к использованию нулевого байтового разделителя - несмотря на его удобство - рекомендованного некоторыми другими.

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

find ... + | sed 's|'\''|&"&"&|g;s|///|'\''|g'

Это выводит должным образом экранированный массив имен файлов независимо от того, какими могут быть их составные символы. Теперь вам остается только надеяться, что ваше приложение на принимающей стороне не будет портить его.

mikeserv
источник
0

Еще один способ сделать это - экранировать все специальные символы в именах файлов. Например:

find /music -iname "*\.mp3" | sed 's!\([] \*\$\/&[]\)!\\\1!g' | xargs mpg321

Это в основном передаст правильно экранированные имена файлов в xargs для выполнения, и никаких проблем не возникнет.

Приябрата Патнаик
источник
1
Это все еще ломается на новые строки в именах файлов. И вы могли бы просто sed 's|.|\\&|g'- это то, что POSIX рекомендует в любом случае.
mikeserv