Зробіть xargs обробляти імена файлів, які містять пробіли


252
$ ls *mp3 | xargs mplayer  

Playing Lemon.  
File not found: 'Lemon'  
Playing Tree.mp3.  
File not found: 'Tree.mp3'  

Exiting... (End of file)  

Моя команда не вдається, оскільки файл "Lemon Tree.mp3" містить пробіли, і тому xargs вважає, що це два файли. Чи можу я змусити find + xargs працювати з назви файлів, як це?


Замість ls |grep mp3 |sed -n "7p"вас можна просто використовувати echo "Lemon Tree.mp3".
Micha Wiedenmann


Це питання також відповів на stackoverflow.com/a/33528111/94687
ІМЗ - Іван Zakharyaschev

Відповіді:


255

xargsКоманда приймає пробільні символи (вкладки, прогалини, нові лінії) в якості роздільників. Ви можете звузити його лише для нових символів рядка ('\ n') з таким -dваріантом:

ls *.mp3 | xargs -d '\n' mplayer

Він працює лише з xargs GNU. Для систем BSD використовуйте такий -0варіант:

ls *.mp3 | xargs -0 mplayer

Цей метод простіший і працює також із xargs GNU.


6
Найкраща відповідь для загального використання! Це працює, навіть якщо попередня команда не "знайти"
nexayq

28
На жаль, ця опція недоступна в ОС X.
Томас Темпельман

25
@Thomas Для OS X прапор є -E, наприклад:xargs -E '\n'

30
В OS X -E '\ n' не вплинуло на мене, і я не очікував би, що це змінило eofstr, а не роздільник записів. Однак, я зміг використати прапор -0 як рішення, навіть якщо попередня команда не є «знайти», імітуючи ефект прапора find -print0 у моєму введенні, наприклад: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
biomiker

10
Для OS X ви можете "варити інсталяцію findutils", яка дає вам команду "gxargs", яка має перемикач -d.
Том Де Леу

213

Утиліта xargs зчитує простір, вкладку, новий рядок та рядки з обмеженим кінцем файлу зі стандартного вводу та виконує утиліту з рядками як аргументи.

Ви хочете уникати використання місця як роздільника. Це можна зробити, змінивши роздільник для xargs. Відповідно до посібника:

 -0      Change xargs to expect NUL (``\0'') characters as separators,
         instead of spaces and newlines.  This is expected to be used in
         concert with the -print0 function in find(1).

Як от:

 find . -name "*.mp3" -print0 | xargs -0 mplayer

Відповісти на запитання про відтворення сьомого MP3; простіше бігати

 mplayer "$(ls *.mp3 | sed -n 7p)"

10
Для цього використовуються GNU findта GNU xargs; не всі версії цих програм підтримують ці варіанти (хоча слід зробити так, щоб вони були).
Джонатан Леффлер

1
@JonathanLeffler s / GNU / FreeBSD / g; POSIX, на жаль, боїться символів NUL у текстових файлах і ще недостатньо терапії :-) Мої поради насправді вдаються до не портативних варіантів.
Єнс

6
І Mac OS X (а BSD похідною) має findз -print0і xargsз -0. Однак, AFAIK, HP-UX, AIX та Solaris (але я виправдовуюсь: HP-UX 11i не став; Solaris 10 не робив; AIX 5.x не став; але вони не є поточними версіями ). sedНаприклад, не важко буде змінити , наприклад, використовувати "рядки", що закінчуються '\0'замість '\n', і POSIX 2008 getdelim()полегшить управління.
Джонатан Леффлер

2
+1 + 1 хитрість для використання з файловими шляхами, що містять файли списку: cat $ file_paths_list_file | perl -ne 's | \ n | \ 000 | g; print' | xargs -0 zip $ zip_package
Йордан Георгієв,

2
Хороша ідея замінити нові рядки на NUL - мені довелося це зробити у вбудованій системі, у якій не було GNU find, ні GNU xargs, ні perl - але команду tr можна використовувати за те, щоб зробити те саме: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms
joensson


16

xargs в MacOS не має опції -d, тому для цього рішення використовується -0.

Отримайте ls для виведення одного файлу на рядок, а потім перекладіть нові рядки в нулі та скажіть xargs використовувати нулі як роздільник:

ls -1 *mp3 | tr "\n" "\0" | xargs -0 mplayer


8
find . -name 'Lemon*.mp3' -print0 | xargs 0 -i mplayer '{}' 

Це допомогло в моєму випадку видалити різні файли з пробілами. Він також повинен працювати з mplayer. Необхідна хитрість - цитати. (Тестовано на Linux Xubuntu 14.04.)


7

Відповідь Dick.Guertin [1] припускає, що можна уникнути пробілів у імені файлу - цінна альтернатива іншим запропонованим тут рішенням (наприклад, використання нульового символу як роздільника, а не пробілу). Але це може бути і простіше - вам справді не потрібен унікальний персонаж. Ви можете просто додати безпосередньо доданий пробіл:

ls | grep ' ' | sed 's| |\\ |g' | xargs ...

Крім того, grep необхідний лише у тому випадку, якщо ви хочете лише файли з пробілами в іменах. Більш загально (наприклад, під час обробки партії файлів, у деяких з яких є пробіли, у деяких немає), просто пропустіть grep:

ls | sed 's| |\\ |g' | xargs ...

Тоді, звичайно, ім'я файлу може мати інший пробіл, ніж пробіли (наприклад, вкладка):

ls | sed -r 's|[[:blank:]]|\\\1|g' | xargs ...

Це передбачає, що у вас є sed, який підтримує -r (розширений регулярний вираз), такий як GNU sed або останні версії bsd sed (наприклад, FreeBSD, який спочатку написав опцію "-E" перед FreeBSD 8 і підтримує як -r & -E для сумісності принаймні через FreeBSD 11). В іншому випадку ви можете використовувати базовий вираз дужки класу регулярних виразів і вручну вводити символи пробілу та вкладки в []роздільники.

[1] Це, можливо, більше підходить як коментар чи редакція до цієї відповіді, але на даний момент у мене недостатньо репутації для коментарів і я можу лише пропонувати зміни. Оскільки остання форма вище (без грепу) змінює поведінку оригінальної відповіді Діка.Гуртіна, пряма редакція, можливо, все одно не підходить.


1
божевільні хлопці Unix, які виконують скрипти, які називають файли, не враховуючи їх вихід, ось хто
andrew lorien

4

ls | grep mp3 | sed -n "7p" | xargs -i mplayer {}

Зауважте, що в команді, наведеній вище, xargsбуде викликано mplayerзаново для кожного файлу. Це може бути небажаним mplayer, але може бути нормальним для інших цілей.


1
Корисне доповнення до існуючих відповідей, але варто зазначити, що це призведе mplayerдо того, що він буде викликаний заново для кожного файлу. Має значення, якщо ви спробуєте, наприклад ... | xargs -I{} mplayer -shuffle {}: це буде грати у повністю детермінованому порядку, незважаючи на те -shuffle.

1
Мабуть, зазвичай це не є наміром. xargsв основному використовується з командами, які приймають список імен файлів (простий приклад rm:), і намагається передати стільки імен файлів, скільки вони можуть вміститися в кожному виклику, лише розбившись на кілька викликів, якщо потрібно. Різницю можна побачити, коли ви використовуєте команду, де видно кожне виклик, наприклад echo(за замовчуванням): seq 0 100000 | xargsдрукує всі числа від 0 до 23695 (специфічно для платформи, але це відбувається в моїй системі) на першому рядку, до 45539 на рядку 2 і т. д. І ви праві, для більшості команд це не має значення.

4

У macOS 10.12.x (Sierra), якщо у вас є пробіли в іменах файлів або підкаталогах, ви можете використовувати наступне:

find . -name '*.swift' -exec echo '"{}"' \; |xargs wc -l

2

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

Є багато способів впоратися з цим, але деякі з них:

mplayer Lemon*.mp3

find . -name 'Lemon*.mp3' -exec mplayer {} ';'

i=0
for mp3 in *.mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

for mp3 in *.mp3
do
    case "$mp3" in
    (Lemon*) mplayer "$mp3";;
    esac
done

i=0
find . -name *.mp3 |
while read mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

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

Якщо у вас є GNU findі GNU xargs(або FreeBSD (* BSD?), Або Mac OS X), ви також можете використовувати параметри -print0та -0параметри, як у:

find . -name 'Lemon*.mp3' -print0 | xargs -0 mplayer

Це працює незалежно від вмісту імені (єдині два символи, які не можуть відображатись у імені файлу, є косою рискою та NUL, а коса риса не викликає проблем у шляху до файлу, тому використання NUL як роздільника імені покриває все). Однак якщо вам потрібно відфільтрувати перші 6 записів, вам потрібна програма, яка обробляє "рядки", що закінчуються NUL замість нового рядка ... і я не впевнений, що такі є.

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


2

Я знаю , що я не відповідати на xargsпитання прямо , але це варто відзначити find«s -execваріант.

З огляду на таку файлову систему:

[root@localhost bokeh]# tree --charset assci bands
bands
|-- Dream\ Theater
|-- King's\ X
|-- Megadeth
`-- Rush

0 directories, 4 files

Команда find може бути зроблена для обробки місця в Dream Theatre та King's X. Отже, щоб знайти барабанщиків кожного гурту за допомогою grep:

[root@localhost]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

У -execпараметі {}стоїть назва файлу, включаючи шлях. Зауважте, що вам не доведеться уникати цього або ставити його в лапки.

Різниця між -exec's термінаторами ( +і\; ) полягає в тому, що +групується стільки імен файлів, які вони можуть надати в один командний рядок. Тоді як \;буде виконуватися команда для кожного імені файлу.

Так, find bands/ -type f -exec grep Drums {} + вийде:

grep Drums "bands/Dream Theater" "bands/Rush" "bands/King's X" "bands/Megadeth"

і find bands/ -type f -exec grep Drums {} \; призведе до:

grep Drums "bands/Dream Theater"
grep Drums "bands/Rush"
grep Drums "bands/King's X"
grep Drums "bands/Megadeth"

У випадку grep цього є побічний ефект, який можна надрукувати або назвати файл.

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} \;
Drums:Mike Mangini
Drums: Neil Peart
Drums:Jerry Gaskill
Drums:Dirk Verbeuren

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

Звичайно, grepпараметри -hі -Hбудуть контролювати, друкується чи ні ім'я файлу незалежно від того, як grepвикликається.


xargs

xargs також може керувати тим, як файли man у командному рядку.

xargsза замовчуванням групує всі аргументи в один рядок. Для того, щоб зробити те саме, що -exec \;і використовує xargs -l. Зауважте, що -tопція повідомляє xargsнадрукувати команду перед її виконанням.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n' -l -t grep Drums
grep Drums ./bands/Dream Theater 
Drums:Mike Mangini
grep Drums ./bands/Rush 
Drums: Neil Peart
grep Drums ./bands/King's X 
Drums:Jerry Gaskill
grep Drums ./bands/Megadeth 
Drums:Dirk Verbeuren

Дивіться, що -l параметр вказує xargs виконувати grep для кожного імені файлу.

За замовчуванням (тобто немає -lопції):

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush ./bands/King's X ./bands/Megadeth 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren

xargsмає кращий контроль над тим, скільки файлів може бути в командному рядку. Дайте -lопцію максимальну кількість файлів на команду.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -l2 -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
grep Drums ./bands/King's X ./bands/Megadeth 
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren
[root@localhost bokeh]# 

Дивіться, що grepбуло виконано двома іменами через -l2.


1

З огляду на конкретну назву цієї публікації, ось моя пропозиція:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g'

Ідея полягає в тому, щоб перетворити пробіли в будь-який унікальний символ, як-от "<", а потім змінити його на "\", зворотну косу рису, а потім пробіл. Потім ви можете передавати це в будь-яку команду, яка вам подобається, наприклад:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g' | xargs -L1 GetFileInfo

Ключове значення тут полягає у командах 'tr' та 'sed'; і ви можете використовувати будь-який символ, крім "<", наприклад "?" або навіть символом табуляції.


Яке призначення об’їзду через tr? Чому б не просто ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
трійка

1
Я виявив, що "tr" "?" "Усуває потребу в" sed ". Сингл "?" символ не порожній, але відповідає БУДЬ-якому одному символу, в цьому випадку: blank. Шанси на те, що це щось інше, досить малі і прийнятні, оскільки ви намагаєтеся обробити ВСІ файли, що закінчуються на .mp3: "ls | grep '' | tr '' '?" | xargs -L1 GetFileInfo "
Дік Гертін

Ви можете одночасно обробляти "вкладку": tr '\ t' '??' обробляє обидва.
Дік Гертін

1

Альтернативні рішення можуть бути корисними ...

Ви також можете додати нульовий символ до кінця рядків за допомогою Perl, а потім скористатися -0 параметром у xargs. На відміну від xargs -d '\ n' (у затвердженій відповіді) - це працює скрізь, включаючи OS X.

Наприклад, для рекурсивного переліку (виконання, переміщення тощо) файлів MPEG3, які можуть містити пробіли чи інші смішні символи - я б використовував:

find . | grep \.mp3 | perl -ne 'chop; print "$_\0"' | xargs -0  ls

(Примітка. Для фільтрації я віддаю перевагу більш легкий для запам'ятовування синтаксис "| grep" перед "find's" - ім'ям аргументів.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.