Заставить xargs обрабатывать имена файлов, содержащие пробелы

$ 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 работать с такими именами файлов?


person showkey    schedule 26.05.2013    source источник
comment
Вместо ls |grep mp3 |sed -n "7p" вы можете просто использовать echo "Lemon Tree.mp3".   -  person Micha Wiedenmann    schedule 26.05.2013
comment
Возможный дубликат Как использовать xargs для копирования файлов, в именах которых есть пробелы и кавычки?   -  person imz -- Ivan Zakharyaschev    schedule 11.05.2017
comment
На этот вопрос также отвечает stackoverflow.com/a/33528111/94687   -  person imz -- Ivan Zakharyaschev    schedule 11.05.2017


Ответы (12)


Команда xargs использует символы пробела (табуляции, пробелы, новые строки) в качестве разделителей.

Вы можете сузить его только для символов новой строки ('\ n') с помощью опции -d, например:

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

Работает только с GNU xargs.

Для MacOS:

ls *.mp3 | tr \\n \\0 | xargs -0 mplayer

Более простой и практичный подход (когда нет необходимости обрабатывать имена файлов дальше):

mplayer *.mp3
person Ray    schedule 15.09.2015
comment
Лучший ответ для общего пользования! Это работает, даже если ваша предыдущая команда не найдена - person nexayq; 18.06.2016
comment
В OS X -E '\ n' не повлиял на меня, и я не ожидал, что это изменит eofstr, а не разделитель записей. Тем не менее, я смог использовать флаг -0 в качестве решения, даже если предыдущая команда не является «find», путем имитации эффекта флага find -print0 в моем вводе, например: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer - person biomiker; 05.07.2017
comment
Для OS X вы можете заварить install findutils, что даст вам команду gxargs, у которой есть есть переключатель -d. - person Tom De Leu; 16.03.2018
comment
gxargs для победы - person tofutim; 26.03.2018
comment
Не могли бы вы подтвердить, работает ли второй метод на OSX? Спасибо. - person Ray; 14.03.2019
comment
Использование ls в scipts - плохая идея. Правильный обходной путь для этого конкретного случая - просто mplayer *.mp3 вместо пытаюсь использовать xargs. - person tripleee; 11.10.2019
comment
Отлично работает в CentOS. Спасибо - person rogervila; 27.04.2020
comment
ls *.mp3 | xargs -0 mplayer недействителен - ls не выводит выход с нулевым завершением. Используйте printf "%s\0" *.mp3 | xargs -0 mplayer, но на самом деле просто mplayer *.mp3. - person KamilCuk; 30.05.2021

Утилита 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

Чтобы ответить на вопрос о проигрывании седьмого мп3; проще запустить

 mplayer "$(ls *.mp3 | sed -n 7p)"
person Jens    schedule 26.05.2013
comment
Это использует GNU find и GNU xargs; не все версии этих программ поддерживают эти параметры (хотя есть основания полагать, что они должны). - person Jonathan Leffler; 26.05.2013
comment
@JonathanLeffler s / GNU / FreeBSD / g; POSIX, к сожалению, боится символов NUL в текстовых файлах и еще не получил достаточной терапии :-) Мой совет на самом деле прибегает к непереносимым вариантам. - person Jens; 26.05.2013
comment
А 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 _ 8_ упростит управление. - person Jonathan Leffler; 26.05.2013
comment
+1 + 1 трюк для использования с путями к файлам, содержащим файлы списков: cat $ file_paths_list_file | perl -ne 's | \ n | \ 000 | g; print' | xargs -0 zip $ zip_package - person Yordan Georgiev; 24.06.2014
comment
Хорошая идея заменить символы новой строки на NUL - мне пришлось сделать это во встроенной системе, в которой не было ни GNU find, ни GNU xargs, ни perl, - но для того же можно использовать команду tr: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms - person joensson; 01.11.2014
comment
@YordanGeorgiev ваш комментарий (особенно с использованием @joensson tr вместо perl) должен быть ответом! - person Ahmed Fasih; 27.05.2015
comment
@JonathanLeffler, хотя в macos есть -print0 и -0, он не работает, если имя файла содержит пробел. - person user2807219; 29.04.2017
comment
@ user2807219: Мне любопытно узнать, как вы это продемонстрируете. Меня устраивает. Если нужно, отправьте мне электронное письмо - посмотрите мой профиль - и включите ссылку на этот вопрос и MCVE, демонстрирующий, как вы сталкиваетесь с проблемами. - person Jonathan Leffler; 30.04.2017
comment
@JonathanLeffler, спасибо за ответ, я попробовал еще раз, проблема не в пробеле, find с xargs работает с пространством filename даже без -print0 и -0. Моя настоящая проблема в том, что имена папок и файлов слишком длинные, тогда результат выполнения будет mv: rename {} to / pathTo / {}: Нет такого файла или каталога. - person user2807219; 01.05.2017
comment
@ user2807219: Любопытно. Это тоже совсем другое. Однажды у меня была проблема с тестом конфигурации программного обеспечения, который не удался на очень, очень глубоких иерархиях каталогов на Mac - он пытался проверить, насколько глубоко они могут зайти, и запутался, чему способствовала ошибка Mac OS X. Но это было бы необычно. Либо создайте свой собственный вопрос с описанием проблемы, либо свяжитесь со мной в автономном режиме, чтобы сообщить подробности. Вероятно, есть способы справиться с этим, но вам нужно знать и объяснять, почему у вас такие длинные имена, проиллюстрировать некоторые примеры (они не поместятся в комментарии) и объяснить, что вы пытаетесь сделать. - person Jonathan Leffler; 01.05.2017

Пытаться

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

вместо

ls | grep mp3 
person Scott C Wilson    schedule 26.05.2013

xargs в MacOS не имеет опции -d, поэтому в этом решении вместо этого используется -0.

Получите ls для вывода по одному файлу на строку, затем преобразуйте символы новой строки в нули и скажите xargs использовать нули в качестве разделителя:

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

person Graeme Pyle    schedule 10.01.2018
comment
работает отлично на macOS .. спасибо !!! - person Eklavyaa; 23.12.2020

Ответ Dick.Guertin [1] предположил, что можно избежать пробелов в имени файла, это ценная альтернатива другим предлагаемым здесь решениям (например, использование нулевого символа в качестве разделителя, а не пробела). Но можно было бы и попроще - вам действительно не нужен уникальный персонаж. Вы можете просто попросить sed добавить экранированные пробелы напрямую:

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] Возможно, это более уместно в качестве комментария или редактирования к этому ответу, но на данный момент у меня недостаточно репутации, чтобы комментировать, и я могу только предлагать изменения. Поскольку последние формы выше (без grep) изменяют поведение исходного ответа Dick.Guertin, прямое редактирование, возможно, в любом случае не подходит.

person Juan    schedule 04.02.2016
comment
сумасшедшие парни из Unix, которые запускают скрипты, которые называют файлы без учета их вывода, вот кто - person andrew lorien; 11.04.2017

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

В моем случае это помогло удалить разные файлы с пробелами. Это тоже должно работать с mplayer. Необходимая уловка - это цитаты. (Проверено в Linux Xubuntu 14.04.)

person Bendel    schedule 04.10.2015

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

Обратите внимание, что в приведенной выше команде xargs будет вызывать mplayer заново для каждого файла. Это может быть нежелательно для mplayer, но может быть приемлемым для других целей.

person Acumenus    schedule 25.08.2014
comment
Полезное дополнение к существующим ответам, но стоит отметить, что это приведет к тому, что mplayer будет вызываться заново для каждого файла. Имеет значение, если вы попробуете, например. ... | xargs -I{} mplayer -shuffle {}: это будет воспроизводиться в полностью детерминированном порядке, несмотря на -shuffle. - person ; 25.08.2014
comment
Скорее всего, это не намерение. xargs в основном используется с командами, которые принимают список имен файлов (простой пример: rm) и пытаются передать столько имен файлов, сколько может поместиться в каждый вызов, только при необходимости разделяясь на несколько вызовов. Вы можете увидеть разницу, когда используете команду, в которой каждый вызов виден, например echo (по умолчанию): seq 0 100000 | xargs печатает все числа от 0 до 23695 (зависит от платформы, но это то, что происходит в моей системе) в первой строке, на 45539 в строке 2 и т. д. И вы правы, для большинства команд это не имеет значения. - person ; 25.08.2014

В macOS 10.12.x (Sierra), если у вас есть пробелы в именах файлов или подкаталогах, вы можете использовать следующее:

find . -name '*.swift' -exec echo '"{}"' \; |xargs wc -l
person Byron Formwalt    schedule 10.12.2017

Я знаю, что не отвечаю на xargs вопрос напрямую, но стоит упомянуть вариант find -exec.

Учитывая следующую файловую систему:

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

0 directories, 4 files

Команда find может использоваться для обработки пространства в Dream Theater и 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 (+ и \;) в том, что + группирует столько имен файлов, сколько может, в одной командной строке. В то время как \; выполнит команду для каждого имени файла.

Итак, 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.

person shrewmouse    schedule 11.08.2017

Это зависит от (а) того, насколько вы привязаны к числу 7, в отличие, скажем, от Lemons, и (б) от того, содержат ли какие-либо имена ваших файлов новые строки (и готовы ли вы их переименовать, если они есть).

Есть много способов справиться с этим, но некоторые из них:

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, а не на новую строку ... и я не уверен, что они есть.

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

person Jonathan Leffler    schedule 26.05.2013

Альтернативные решения могут быть полезны ...

Вы также можете добавить нулевой символ в конец ваших строк с помощью Perl, а затем использовать параметр -0 в xargs. В отличие от xargs -d '\ n' (в утвержденном ответе) - это работает везде, включая OS X.

Например, чтобы рекурсивно перечислить (выполнить, переместить и т. Д.) Файлы MPEG3, которые могут содержать пробелы или другие забавные символы - я бы использовал:

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

(Примечание: для фильтрации я предпочитаю более простой для запоминания синтаксис "| grep" аргументам "find" --name.)

person Charlie Dalsass    schedule 16.10.2017

Учитывая конкретное название этого сообщения, вот мое предложение:

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

Идея состоит в том, чтобы преобразовать пробелы в любой уникальный символ, например '‹', а затем заменить его на '\', обратную косую черту, за которой следует пробел. Затем вы можете передать это в любую команду, которая вам нравится, например:

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

Ключ здесь кроется в командах 'tr' и 'sed'; и вы можете использовать любой символ, кроме '‹', например '?' или даже символ табуляции.

person Dick.Guertin    schedule 21.11.2015
comment
Какова цель объезда через tr? Почему не просто ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'? - person tripleee; 04.02.2016
comment
Я нашел это тр '' '?' устраняет необходимость в sed. Сингл? символ не является пробелом, но соответствует ЛЮБОМУ одиночному символу, в данном случае: пробелу. Шансы на то, что это что-то еще, довольно малы и приемлемы, поскольку вы пытаетесь обработать ВСЕ файлы, заканчивающиеся на .mp3: ls | grep '| tr '' '?' | xargs -L1 GetFileInfo - person Dick Guertin; 05.02.2016
comment
Вы также можете одновременно обрабатывать табуляцию: tr '\ t' '??' обрабатывает оба. - person Dick Guertin; 05.02.2016