使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


Answers:


255

xargs命令将空格字符(制表符,空格,换行符)用作分隔符。您可以使用以下-d选项将其范围缩小到仅换行符('\ n'):

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

它仅适用于GNU xargs。对于BSD系统,请使用以下-0选项:

ls *.mp3 | xargs -0 mplayer

此方法更简单,并且也可以与GNU xargs一起使用。


6
通用的最佳答案!即使您之前的命令不是“ find”,此命令仍然有效
nexayq

28
不幸的是,此选项不可用在OS X
托马斯Tempelmann

25
@Thomas对于OS X,标记为-E,例如:xargs -E '\n'

30
在OS X上,-E'\ n'对我没有影响,我也不希望它会修改eofstr而不是记录分隔符。但是,即使在前面的命令不是“ find”的情况下,我也可以通过模拟输入中find -print0标志的效果来利用-0标志作为解决方案,例如:ls * mp3 | tr'\ n''\ 0'| xargs -0 mplayer
biomiker

10
对于OS X,你可以在“BREW安装的findutils”,它给你的“gxargs”命令确实有-d开关。
Tom De Leu

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字符,并且还没有得到足够的治疗:-)我的建议实际上是诉诸于非便携式选项。
詹斯(Jens)

6
和Mac OS X(一个BSD衍生物)具有find-print0xargs-0。但是,AFAIK,HP-UX,AIX和Solaris却没有(但我有待纠正:HP-UX 11i没有; Solaris 10没有; AIX 5.x没有;但是它们不是当前版本)。sed例如,使用以结尾的'lines' '\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查找,GNU xargs或perl的嵌入式系统上执行此操作-但是可以利用tr命令执行相同的操作:cat $ file_paths_list_file | tr'\ n''\ 0'| xargs -0 du -hms
joensson 2014年


16

MacOS上的xargs没有-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 上测试。)


7

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(扩展的regex),例如GNU sed或bsd sed的最新版本(例如,FreeBSD,它最初在FreeBSD 8之前拼写了选项-E,并且同时支持-r和-E至少通过FreeBSD 11)。否则,您可以使用基本的正则表达式字符类括号表达式,并在[]分隔符中手动输入空格和制表符。

[1]这可能更适合作为对该答案的评论或修改,但是目前我没有足够的声誉来评论,只能提出修改建议。由于上面的后者形式(没有grep)会改变Dick.Guertin最初的回答,因此直接编辑可能还是不合适的。


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

它取决于(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结尾的“行”而不是换行符...而且我不确定是否有任何行。

对于目前的特定情况,第一种是最简单的。但是,它可能无法一概而论地涵盖您尚未列出的其他情况。


2

我知道我没有xargs直接回答问题,但是值得一提find的是-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的终止符(+\;)之间的区别在于+,它可以将多个文件名分组到一个命令行中。而\;将对每个文件名执行命令。

因此,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]# 

看到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'呢?
Tripleee '16

1
我发现“ tr'''?'”消除了对“ sed”的需要。单“?” 字符非空白,但与任何单个字符匹配,在这种情况下为空白。由于您正在尝试处理所有以.mp3结尾的文件,因此发生其他事情的可能性很小,并且可以接受:“ ls | grep''| tr'''?| xargs -L1 GetFileInfo”
Dick

您还可以同时处理“制表符”:tr'\ t''??' 同时处理。
Dick Guertin

1

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

您还可以使用Perl -0在行末添加一个空字符,然后在xargs中使用该选项。与xargs -d'\ n'(在已批准的答案中)不同,它可以在任何地方使用,包括OSX。

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

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

(注意:对于过滤,与“ find的” --name参数相比,我更喜欢使用易于记忆的“ | grep”语法。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.