【问题标题】:Make xargs handle filenames that contain spaces使 xargs 处理包含空格的文件名
【发布时间】:2013-05-21 10:41:25
【问题描述】:
$ 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 使用这样的文件名吗?

【问题讨论】:

标签: shell find xargs


【解决方案1】:

试试

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

而不是

ls | grep mp3 

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 这是使用 GNU find 和 GNU xargs;并非这些程序的所有版本都支持这些选项(尽管有理由证明它们应该支持)。
    • @JonathanLeffler s/GNU/FreeBSD/g;遗憾的是,POSIX 害怕文本文件中的 NUL 字符并且还没有得到足够的治疗:-) 事实上,我的建议是诉诸非便携式选项。
    • 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() 将使其易于管理。
    • +1 + 1 使用包含列表文件的文件路径的技巧:cat $file_paths_list_file | perl -ne 's|\n|\000|g;print'| xargs -0 压缩 $zip_package
    • 用 NUL 替换换行符是个好主意——我必须在没有 GNU find、GNU xargs 或 perl 的嵌入式系统上这样做——但可以利用 tr 命令来做同样的事情:猫 $file_paths_list_file | tr '\n' '\0' | xargs -0 du -hms
    【解决方案3】:

    这取决于 (a) 您对数字 7 的依恋程度,而不是 Lemons,以及 (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 而不是换行符结尾的“行”……我不确定是否有。

    对于手头的具体情况,第一个是迄今为止最简单的;但是,它可能无法概括涵盖您尚未列出的其他场景。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 对现有答案的有用补充,但值得注意的是,这将导致 mplayer 为每个文件重新调用。如果您尝试例如,这很重要... | xargs -I{} mplayer -shuffle {}:尽管-shuffle,这将以完全确定的顺序播放。
      • 这通常不是本意。 xargs 主要用于接受文件名列表的命令(简单示例:rm),并尝试在每次调用中传递尽可能多的文件名,仅在需要时拆分为多个调用。当您使用每个调用可见的命令时,您可以看到差异,例如echo(默认值):seq 0 100000 | xargs 打印从 0 到 23695 的所有数字(特定于平台,但这是在我的系统上发生的情况)第一行,到第 2 行的 45539 ​​等。你是对的,对于大多数命令来说,这无关紧要。
      【解决方案5】:

      xargs 命令将空白字符(制表符、空格、换行符)作为分隔符。

      您可以使用-d 选项将其范围缩小,仅适用于换行符('\n'),如下所示:

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

      它仅适用于 GNU xargs。

      对于 MacOS:

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

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

      mplayer *.mp3
      

      【讨论】:

      • 一般用途的最佳答案!即使您之前的命令不是“查找”,这也有效
      • 在 OS X 上,-E '\n' 对我没有影响,我也不希望它修改 eofstr 而不是记录分隔符。但是,我能够利用 -0 标志作为解决方案,即使前面的命令不是“find”,通过在我的输入中模拟 find 的 -print0 标志的效果,例如:ls *mp3 | tr '\n' '\0' | xargs -0 播放器
      • 对于 OS X,您可以“brew install findutils”,它会为您提供 确实 具有 -d 开关的“gxargs”命令。
      • Using ls in scipts is a bad idea. 这种特定情况的正确解决方法是简单地使用mplayer *.mp3 而不是尝试使用xargs。
      • ls *.mp3 | xargs -0 mplayer 无效 - ls 不输出零终止输出。使用printf "%s\0" *.mp3 | xargs -0 mplayer,但实际上,只是mplayer *.mp3。
      【解决方案6】:
      find . -name 'Lemon*.mp3' -print0 | xargs -­0 -i mplayer '{}' 
      

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

      【讨论】:

        【解决方案7】:

        鉴于这篇文章的具体标题,这是我的建议:

        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'?
        • 我发现 "tr ' ' '?'" 消除了对 "sed" 的需要。单“?”字符是非空白的,但匹配任何单个字符,在这种情况下:空白。它是其他东西的可能性很小,并且可以接受,因为您正在尝试处理以 .mp3 结尾的所有文件:“ls | grep ' ' | tr ' ' '?' | xargs -L1 获取文件信息"
        • 也可以同时处理“tab”:tr ' \t' '??'处理两者。
        【解决方案8】:

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

        假设您有一个支持 -r(扩展正则表达式)的 sed,例如 GNU sed 或最新版本的 bsd sed(例如,FreeBSD 在 FreeBSD 8 之前最初拼写选项“-E”并同时支持 -r 和 - E 至少通过 FreeBSD 11 兼容)。否则,您可以使用基本的正则表达式字符类括号表达式并在[] 分隔符中手动输入空格和制表符。

        [1] 这可能更适合作为对该答案的评论或编辑,但目前我没有足够的声誉来发表评论,只能建议编辑。由于上面的后一种形式(没有 grep)改变了 Dick.Guertin 原始答案的行为,因此直接编辑可能无论如何都不合适。

        【讨论】:

          【解决方案9】:

          我知道我没有直接回答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]# 
          

          看到由于-l2,grep 使用两个文件名执行。

          【讨论】:

            【解决方案10】:

            其他解决方案可能会有所帮助...

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

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

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

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

            【讨论】:

              【解决方案11】:

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

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

              【讨论】:

                【解决方案12】:

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

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

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

                【讨论】:

                  【解决方案13】:

                  在 macOS 上(Monterey/12 向前,我不确定从 10.15/Catalina 回溯多远),如果文件名或子目录中有空格,则可以使用以下内容:

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

                  正如 Jen 的回答所指出的:

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2010-09-13
                    • 1970-01-01
                    • 2014-07-05
                    • 2013-03-30
                    • 2018-01-13
                    • 2011-11-20
                    • 1970-01-01
                    相关资源
                    最近更新 更多