使xargs能够处理包含空格的文件名

409
$ 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处理这样的文件名吗?


1
可能是重复的问题:如何使用xargs复制文件名中包含空格和引号的文件? - imz -- Ivan Zakharyaschev
此问题也已经在 https://dev59.com/ZHVC5IYBdhLWcg3w7V1q#33528111 得到了解答。 - imz -- Ivan Zakharyaschev
15个回答

454

xargs 命令将空白字符(制表符、空格、换行符)视为定界符。

您可以使用 -d 选项仅针对换行符('\n')进行操作,如下所示:

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

它仅适用于GNU xargs。

对于MacOS:

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

更加简单实用的方法(当不需要进一步处理文件名时):

mplayer *.mp3

9
一般情况下最佳答案!即使你之前的指令不是“查找”,这也适用。 - nexayq
38
在OS X上,-E '\n' 对我没有影响,我也不期望它会有影响,因为它修改了eofstr而不是记录分隔符。然而,我能够利用-0标志作为解决方案,即使前一个命令不是“find”,通过在我的输入中模拟find的-print0标志的效果,例如:ls *mp3 | tr '\n' '\0' | xargs -0 mplayer - biomiker
24
在 OS X 中,您可以通过运行 "brew install findutils" 命令来安装 "findutils",该命令会提供 "gxargs" 命令,并且 确实 具有 -d 开关。 - Tom De Leu
1
在脚本中使用 ls 是一个不好的主意。这种情况的正确解决方法是简单地使用 mplayer *.mp3,而不是尝试使用 xargs - tripleee
1
“ls .mp3 | xargs -0 mplayer” 是无效的,因为“ls”不会输出零终止符。请使用“printf"%s\0".mp3 | xargs -0 mplayer”,但实际上只需使用“mplayer *.mp3”。 - KamilCuk
显示剩余6条评论

251

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)"

11
这里使用了GNU的find和GNU的xargs,并非所有版本的这些程序都支持这些选项(尽管有理由认为它们应该支持)。 - Jonathan Leffler
8
Mac OS X(一个BSD衍生版本)具有带有-print0find和带有-0xargs。据我所知,HP-UX、AIX和Solaris没有这个功能(但我可能会被纠正:HP-UX 11i没有;Solaris 10没有;AIX 5.x没有;但它们不是当前版本)。例如,将sed更改为使用以'\0'结尾的“行”而不是'\n'不难,并且POSIX 2008 getdelim()可以轻松管理。 - Jonathan Leffler
2
使用包含列表文件的文件路径时的+1 + 1技巧:cat $file_paths_list_file | perl -ne 's|\n|\000|g;print'| xargs -0 zip $zip_package - Yordan Georgiev
2
将换行符替换为NUL是个好主意 - 我曾在一个没有GNU find、GNU xargs或perl的嵌入式系统上这样做过 - 但tr命令可以用来完成同样的任务:cat $file_paths_list_file | tr '\n' '\0' | xargs -0 du -hms - joensson
1
@YordanGeorgiev,你的评论(特别是@joensson使用tr而不是perl)应该成为一个答案! - Ahmed Fasih
显示剩余5条评论

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

而不是

ls | grep mp3 

26

在MacOS上,xargs没有-d选项,因此此解决方案使用-0代替。

让ls每行输出一个文件名,然后将换行符转换为NULL,并告诉xargs使用NULL作为分隔符:

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


17

Dick.Guertin的答案[1]建议在文件名中转义空格是其他解决方案(如使用null字符作为分隔符而不是空格)的有价值的替代方案。但可能更简单 - 您实际上不需要一个唯一的字符。您可以让sed直接添加转义空格:

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

此外,只有当您仅想要带空格名称的文件时才需要使用grep。更通用的做法是(例如,在处理一批文件时,其中一些有空格,一些没有),只需跳过grep:
ls | sed 's| |\\ |g' | xargs ...

当然,文件名可能含有除空格之外的其他空白字符(例如制表符):
ls | sed -r 's|[[:blank:]]|\\\1|g' | xargs ...

假设您有一个支持-r(扩展正则表达式)的sed,例如GNU sed或最新版本的bsd sed(例如FreeBSD,在FreeBSD 8之前拼写选项“-E”,并至少通过FreeBSD 11支持-r和-E以实现兼容性)。否则,您可以使用基本的正则表达式字符类括号表达式,并在[]分隔符中手动输入空格和制表符字符。[1]这可能更适合作为评论或对该答案的编辑,但目前我没有足够的声望来发表评论,只能建议编辑。由于上面的后一种形式(不带grep)改变了Dick.Guertin原始答案的行为,直接编辑也许不合适。

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

这对我有帮助,可以删除带空格的不同文件。对于mplayer也应该适用。必要的技巧是引号。(在Linux Xubuntu 14.04上测试过。)


8

我知道我没有直接回答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 也可以控制命令行上有多少个文件。 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]# 

由于使用了-l2选项,可以看到grep命令被执行了两个文件名。


5

在 macOS 10.12.x(Sierra)中,如果文件名或子目录中有空格,您可以使用以下方法:

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

4

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

注意,在上面的命令中,xargs会为每个文件调用一个新的mplayer。这可能对mplayer不利,但对其他目标可能没有问题。


1
对现有答案的一项有用的补充,但值得注意的是,这将导致每个文件都会调用mplayer。如果您尝试例如 ... | xargs -I{} mplayer -shuffle {}:尽管使用了 -shuffle,但播放顺序仍然完全确定。 - user743382
1
通常这并不是意图。xargs通常与接受文件名列表的命令一起使用(例如rm),并尝试将尽可能多的文件名传递给每个调用,只有在需要时才会分成多个调用。当您使用每次调用都可见的命令时(如默认值的echo),您可以看到差异:seq 0 100000 | xargs在第一行上打印从0到23695(平台特定,但这是我的系统发生的事情),第2行打印从45539开始,以此类推。而且您是对的,对于大多数命令来说,这并不重要。 - user743382

3

这取决于(a)您对数字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而不是换行符结尾的“行”数据的程序...我不确定是否有这样的程序存在。
对于手头的特定情况来说,第一种方法是最简单的;然而,它可能不能推广到覆盖您尚未列出的其他情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接