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


11

Ситуація полягає в тому, що у мене є MP3-програвач, mpg321який приймає список файлів як аргумент. Я зберігаю свою музику в каталозі під назвою "музика", в якому є ще кілька каталогів. Я просто хочу зіграти їх усіх, тому запускаю програму

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

. Проблема полягає в тому, що деякі імена файлів містять пробіли, і програма розбиває ці імена на менші частини та скаржиться на відсутні файли. Загортання результату findв лапки

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

не допомагає, тому що всі стануть одним великим "ім'ям файлу", якого, очевидно, не знайдено.

Як я можу це зробити тоді? Якщо це має значення, я використовую bash, але незабаром переходимо zsh.

Відповіді:


17

Спробуйте скористатися знахідкою -print0або -printfопцією в поєднанні з xargsтаким:

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

Як це працює, пояснюється на сторінці посібника пошуку :

-принт0

Правда; надрукуйте повне ім’я файлу на стандартному виході з подальшим нульовим символом (замість символу нового рядка, який використовує -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 ви також можете використовувати -print0таxargs -0 , але вивчити ще один інструмент мало сенсу. -exec ... {} +Синтаксис отримує мало згадки , бо Linux придбав пізніше , ніж -print0, але немає ніяких причин , щоб не використовувати його зараз.

З zsh або bash 4 це набагато простіше:

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

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

mpg123 (#i)**/*.mp3

+1 до цього, "find -print0" - це некрасивий хакер.
p-статичний

2

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

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

Я не розумію цієї відповіді ... Ви використовуєте -0прапор xargs без знаходження -print0. Також -Iпрапор xargs має низку наслідків. На відміну від простого простору, xargsякий буде стискати всі рядки зі stdin в одну команду, потенційно xargs -Iвиконується mpg321сотні чи тисячі разів (один раз для кожного файлу), що, звичайно, не є наміром. Також майте на увазі, що цитування foo є непотрібним, як xargsце робиться всередині країни. Зверніть увагу, якщо ви стиснути всі імена файлів на лінію і надіслати їх xargs -I, вони не відкриються.
Шість

1

Зазвичай найкраще безпосередньо, -exec ${tgt_process} \{\} +але якщо вам потрібно з findякихось причин отримати надійно обмежений список імен файлів у файлі чи потоці , ви можете зробити це:

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

Що виходить із цього - це дві унікальні нитки. На чолі кожного імені файлу стоїть рядок, \n///а в кінці кожного імені файлу - рядок ///\n. Ці два рядки не зустрічаються ніде у findвиході 's, за винятком тих позицій, незалежно від того, які символи містять назви файлів.

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

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

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

Це виводить належним чином захищений від оболонки масив імен файлів, незалежно від того, якими можуть бути їх складові символи. Тепер ви просто сподіваєтесь, що ваша програма на приймальному кінці її не заблукає.


0

Інший спосіб зробити це - уникнути всіх спеціальних символів, які входять у ваші імена файлів. Наприклад:

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

Це, в основному, передасть належним чином ім'я файлів xargs для виконання, і проблем не виникне.


1
Це все ще розбивається на нові рядки у назви файлів. І ви можете просто так sed 's|.|\\&|g'- саме так рекомендує POSIX.
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.