使 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 使用这样的文件名吗?

139258 次浏览

试一试

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

而不是

ls | grep mp3

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

这取决于(a)与柠檬(Lemons)相比,你对数字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结尾的“行”,而不是换行符…我不确定是否有。

就目前的具体情况而言,第一种方法是最简单的;但是,它可能不能泛化到您尚未列出的其他场景。

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

注意,在上面的命令中,xargs将为每个文件重新调用mplayer。这对于mplayer可能是不可取的,但对于其他目标可能是可以的。

xargs命令使用空白字符(制表符、空格、新行)作为分隔符。

你可以通过-d选项将范围缩小到新的行字符('\n'),如下所示:

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

它只适用于GNU xargs。

MacOS:

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

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

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

在我的例子中,这有助于删除带有空格的不同文件。它应该也工作与mplayer。必要的技巧是引用。(在Linux Xubuntu 14.04上测试。)

鉴于这篇文章的特定标题,以下是我的建议:

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

其思想是将空格转换为任何唯一字符,如'<',然后将其转换为'\ ',反斜杠加空格。然后你可以将其导入到任何你喜欢的命令中,比如:

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

这里的关键在于'tr'和'sed'命令;你可以使用'<'以外的任何字符,比如'?,甚至是制表符。

迪克。Guertin的回答[1]表明,可以转义文件名中的空格,这是这里建议的其他解决方案(例如使用空字符作为分隔符而不是空白字符)的有价值的替代方案。但它可以更简单——你真的不需要一个唯一的字符。你可以直接用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”,并且支持-r &-E的兼容性通过FreeBSD 11至少)。否则,您可以使用基本的正则表达式字符类括号表达式,并手动在[]分隔符中输入空格和制表符。

这可能更适合作为一个评论或编辑的答案,但目前我没有足够的声誉来评论,只能建议编辑。因为后面的形式(没有grep)改变了Dick的行为。格汀最初的回答,直接的编辑可能是不合适的。

我知道我没有直接回答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是如何调用的,grep的选项-h-H将控制是否打印文件名。


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]#

可以看到,由于-l2grep被执行时带有两个文件名。

替代解决方案可能会有所帮助……

还可以使用Perl在行尾添加空字符,然后在xargs中使用-0选项。与xargs -d '\n'(在已批准的答案中)不同,这适用于任何地方,包括OS X。

例如,要递归地列出(执行,移动等)可能包含空格或其他有趣字符的MPEG3文件-我会使用:

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

(注意:对于过滤,我更喜欢更容易记住的“| grep”语法而不是“find’s”——name参数。)

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

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

MacOS上的xargs没有-d选项,所以这个解决方案使用-0代替。

获取ls每行输出一个文件,然后将换行符转换为空值,并告诉xargs使用空值作为分隔符:

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

在macOS (Monterey/12向前,我不确定10.15/Catalina过去了多久)上,如果你的文件名或子目录中有空格,你可以使用以下命令:

mdfind  -0 -onlyin . -name .txt | xargs -0 grep stackoverflow |  wc -l

正如珍的回答所示:

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