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

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

showkey
источник
Вместо ls |grep mp3 |sed -n "7p"тебя можно просто использовать echo "Lemon Tree.mp3".
Миха Виденманн
На этот вопрос также отвечает stackoverflow.com/a/33528111/94687
imz - Иван

Ответы:

256

Команда xargsпринимает символы пробела (табуляции, пробелы, новые строки) в качестве разделителей. Вы можете сузить это только для символов новой строки ('\ n') с -dопцией как это:

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

Работает только с GNU xargs. Для систем BSD используйте такую -0опцию:

ls *.mp3 | xargs -0 mplayer

Этот метод проще и работает с GNU xargs.

луч
источник
6
Лучший ответ для общего пользования! Это работает, даже если ваша предыдущая команда не «найти»
nexayq
28
К сожалению, эта опция недоступна в OS X.
Томас Темпельманн
25
@Thomas Для OS X установлен флаг -E, например:xargs -E '\n'
30
На OS X -E '\ n' не оказал на меня никакого влияния, и я не ожидал, что это изменит eofstr, а не разделитель записей. Тем не менее, я смог использовать флаг -0 в качестве решения, даже если предыдущая команда не была 'find', имитировав эффект флага -print0 find в моем вводе, например: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
биомикер
10
Для OS X вы можете «brew install findutils», которая дает вам команду «gxargs», у которой есть ключ -d.
Том Де Лео
214

Утилита 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)"
Jens
источник
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-поиска, ни GNU-xargs, ни perl-но команда tr может быть использована для того же: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms
Йоэнссон
28

Пытаться

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

вместо того

ls | grep mp3 
Скотт К Уилсон
источник
16

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

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

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

Грэм Пайл
источник
8
find . -name 'Lemon*.mp3' -print0 | xargs 0 -i mplayer '{}' 

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

Bendel
источник
7

В ответе Дика. Гуэртина [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) изменяют поведение первоначального ответа Дика. Гуэртина, прямое редактирование, возможно, в любом случае не подходит.

Juan
источник
1
сумасшедшие парни из Unix, которые запускают сценарии, которые
присваивают
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, в отличие, скажем, от Лимонов, и (б) от того, содержат ли какие-либо имена файлов новые строки (и хотите ли вы их переименовать, если они это сделают).

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

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 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.

землеройка
источник
1

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

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

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

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

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

Dick.Guertin
источник
Какова цель обхода через tr? Почему не просто ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
tripleee
1
Я обнаружил, что «tr», «?» Устраняет необходимость в «sed». Сингл "?" символ не является пустым, но соответствует ЛЮБОМУ одиночному символу, в этом случае: пусто. Вероятность того, что это что-то еще, довольно мала и приемлема, так как вы пытаетесь обработать ВСЕ файлы, заканчивающиеся на .mp3: "ls | grep '' | tr '' '?' | xargs -L1 GetFileInfo "
Дик Гертен
Вы также можете одновременно обрабатывать "tab": 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" --name.)

Чарли Далсасс
источник