【问题标题】:Capturing output of find . -print0 into a bash array捕获 find 的输出。 -print0 到 bash 数组中
【发布时间】:2010-11-10 03:51:07
【问题描述】:

使用find . -print0 似乎是在 bash 中获取文件列表的唯一安全方法,因为文件名可能包含空格、换行符、引号等。

但是,我很难真正让 find 的输出在 bash 或其他命令行实用程序中有用。我设法利用输出的唯一方法是将其通过管道传输到 perl,并将 perl 的 IFS 更改为 null:

find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'

此示例打印找到的文件数,避免文件名中的换行符破坏计数的危险,如下所示:

find . | wc -l

由于大多数命令行程序不支持以 null 分隔的输入,我认为最好的办法是在 bash 数组中捕获 find . -print0 的输出,就像我在上面的 perl sn-p 中所做的那样,然后继续任务,不管它是什么。

我该怎么做?

这不起作用:

find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

一个更普遍的问题可能是:我怎样才能用 bash 中的文件列表做有用的事情?

【问题讨论】:

  • 做有用的事情是什么意思?
  • 哦,你知道,数组通常有用的地方是:找出它们的大小;迭代它们的内容;向后打印它们;对它们进行排序。那种事。 unix 中有很多实用程序可以处理数据:wc、bash 的 for 循环、tac 和 sort;但是在处理可能包含空格或换行符的列表时,这些似乎都没有用。 IE。文件名。使用空值输入字段分隔符传递数据似乎是解决方案,但很少有实用程序可以处理这个问题。
  • 这是一篇关于如何在 shell 中正确处理文件名的文章,有很多细节:http://www.dwheeler.com/essays/filenames-in-shell.html

标签: arrays bash null find delimiter


【解决方案1】:

无耻从Greg's BashFAQ盗取:

unset a i
while IFS= read -r -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done < <(find /tmp -type f -print0)

请注意,这里使用的重定向构造 (cmd1 &lt; &lt;(cmd2)) 与更常用的管道 (cmd2 | cmd1) 相似,但并不完全相同——如果命令是 shell 内置命令(例如 while),则管道版本在子shell中执行它们,它们设置的任何变量(例如数组a)在它们退出时都会丢失。 cmd1 &lt; &lt;(cmd2) 仅在子外壳中运行 cmd2,因此该数组在其构造之后仍然存在。警告:这种形式的重定向只在 bash 中可用,甚至在 sh-emulation 模式下也不可用;您必须以 #!/bin/bash 开始您的脚本。

另外,因为文件处理步骤(在这种情况下,只是a[i++]="$file",但您可能想直接在循环中做一些更有趣的事情)的输入重定向,它不能使用任何可能从标准输入读取的命令。为了避免这个限制,我倾向于使用:

unset a i
while IFS= read -r -u3 -d $'\0' file; do
    a[i++]="$file"        # or however you want to process each file
done 3< <(find /tmp -type f -print0)

...通过单元 3 传递文件列表,而不是标准输入。

【讨论】:

  • 啊,快到了……这是迄今为止最好的答案。但是,我刚刚在包含名称中带有换行符的文件的目录上尝试过它,并且在使用 echo ${a[1]} 检查该元素时,换行符似乎已变成空格(0x20)。知道为什么会这样吗?
  • 您运行的是哪个版本的 bash?我在处理字符串中的换行符和删除 (\177) 的旧版本时遇到了麻烦(不幸的是,我不记得具体是哪个)。 IIRC,即使 x="$y" 也不总是适用于这些字符。我刚刚用 bash 2.05b.0 和 3.2.17 (我手边最旧和最新的)进行了测试;两者都正确处理换行符,但 v2.05b.0 吃了删除字符。
  • 我已经在 osx 上的 3.2.17、linux 上的 3.2.39 和 netBSD 上的 3.2.48 上尝试过;都把换行符变成空格。
  • -d '' 等价于-d $'\0'
  • 将元素添加到数组末尾的更简单方法是:arr+=("$file")
【解决方案2】:

从 Bash 4.4 开始,内置的mapfile-d 开关(用于指定分隔符,类似于read 语句的-d 开关),分隔符可以是空字节。因此,很好地回答了标题中的问题

find . -print0 的输出捕获到 bash 数组中

是:

mapfile -d '' ary < <(find . -print0)

【讨论】:

  • 这看起来更优雅,而且还可以作为定位的魅力:mapfile -d '' list &lt; &lt;(locate -b -0 -r "$1$")
【解决方案3】:

也许你正在寻找 xargs:

find . -print0 | xargs -r0 do_something_useful

选项 -L 1 也可能对您有用,这使得 xargs exec do_something_useful 只需 1 个文件参数。

【讨论】:

  • 这不是我想要的,因为没有机会对列表进行类似数组的操作,例如排序:您必须在每个元素出现时使用它查找命令。如果您可以详细说明此示例,其中“do_something_useful”部分是 bash 数组推送操作,那么这可能就是我所追求的。
【解决方案4】:

主要问题是,分隔符 NUL (\0) 在这里没有用,因为不可能为 IFS 分配 NUL 值。因此,作为优秀的程序员,我们要注意程序的输入是它能够处理的。

首先我们创建一个小程序,它会为我们完成这部分工作:

#!/bin/bash
printf "%s" "$@" | base64

...称之为base64str(别忘了chmod +x)

其次,我们现在可以使用简单直接的 for 循环:

for i in `find -type f -exec base64str '{}' \;`
do 
  file="`echo -n "$i" | base64 -d`"
  # do something with file
done

所以诀窍是,base64 字符串没有标志,这会给 bash 带来麻烦——当然,xxd 或类似的东西也可以完成这项工作。

【讨论】:

  • 必须确保 find 正在处理的文件系统部分从调用 find 到脚本完成时不会发生变化。如果不是这种情况,则会产生竞争条件,可以利用它来调用错误文件的命令。例如,要删除的目录(例如 /tmp/junk)可以由非授权用户替换为 /home 的符号链接。如果 find 命令以 root 身份运行,并且它是 find -type d -exec rm -rf '{}' \;,这将删除所有用户的主文件夹。
  • read -r -d '' 会将直到下一个 NUL 的所有内容读入"$REPLY"。无需关心IFS
【解决方案5】:

另一种计算文件的方法:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 

【讨论】:

    【解决方案6】:

    你可以放心地用这个来计数:

    find . -exec echo ';' | wc -l
    

    (它为找到的每个文件/目录打印一个换行符,然后计算打印出的换行符...)

    【讨论】:

    • 对每个文件使用-printf 选项而不是-exec 要快得多:find . -printf "\n" | wc -l
    【解决方案7】:

    我认为存在更优雅的解决方案,但我将把它扔进去。这也适用于带有空格和/或换行符的文件名:

    i=0;
    for f in *; do
      array[$i]="$f"
      ((i++))
    done
    

    然后你可以例如一个一个地列出文件(在这种情况下以相反的顺序):

    for ((i = $i - 1; i >= 0; i--)); do
      ls -al "${array[$i]}"
    done
    

    This page 给出了一个很好的例子,更多信息请参见Advanced Bash-Scripting Guide 中的Chapter 26

    【讨论】:

    • 这(以及下面的其他类似示例)几乎是我所追求的 - 但有一个大问题:它仅适用于当前目录的 glob。我希望能够操作完全任意的文件列表;例如“find”的输出,它递归地列出目录,或任何其他列表。如果我的列表是:( /tmp/foo.jpg | /home/alice/bar.jpg | /home/bob/my holiday/baz.jpg | /tmp/new\nline/grault.jpg )或任何其他完全任意的文件列表(当然,其中可能包含空格和换行符)?
    【解决方案8】:

    尽可能避免使用 xargs:

    man ruby | less -p 777 
    IFS=$'\777' 
    #array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) ) 
    array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) ) 
    echo ${#array[@]} 
    printf "%s\n" "${array[@]}" | nl 
    echo "${array[0]}" 
    IFS=$' \t\n' 
    

    【讨论】:

    • 为什么将IFS设置为\777
    【解决方案9】:

    我是新手,但我相信这是一个答案;希望它可以帮助某人:

    STYLE="$HOME/.fluxbox/styles/"
    
    declare -a array1
    
    LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`
    
    
    echo $LISTING
    array1=( `echo $LISTING`)
    TAR_SOURCE=`echo ${array1[@]}`
    
    #tar czvf ~/FluxieStyles.tgz $TAR_SOURCE
    

    【讨论】:

      【解决方案10】:

      Gordon Davisson 的回答非常适合 bash。然而,对于 zsh 用户来说,存在一个有用的快捷方式:

      首先,将字符串放入变量中:

      A="$(find /tmp -type f -print0)"
      

      接下来,拆分这个变量并将其存储在一个数组中:

      B=( ${(s/^@/)A} )
      

      有一个技巧:^@ 是 NUL 字符。为此,您必须键入 Ctrl+V,然后键入 Ctrl+@。

      您可以检查 $B 的每个条目是否包含正确的值:

      for i in "$B[@]"; echo \"$i\"
      

      细心的读者可能会注意到,在大多数情况下,使用** 语法可以避免调用find 命令。例如:

      B=( /tmp/** )
      

      【讨论】:

        【解决方案11】:

        这类似于 Stephan202 的版本,但文件(和目录)被一次性放入一个数组中。这里的for 循环只是为了“做有用的事情”:

        files=(*)                        # put files in current directory into an array
        i=0
        for file in "${files[@]}"
        do
            echo "File ${i}: ${file}"    # do something useful 
            let i++
        done
        

        要计数:

        echo ${#files[@]}
        

        【讨论】:

          【解决方案12】:

          老问题,但没有人建议这种简单的方法,所以我想我会的。如果你的文件名有 ETX,这并不能解决你的问题,但我怀疑它适用于任何现实世界的场景。尝试使用 null 似乎违反了默认的 IFS 处理规则。使用查找选项和错误处理根据您的口味调整。

          savedFS="$IFS"
          IFS=$'\x3'
          filenames=(`find wherever -printf %p$'\x3'`)
          IFS="$savedFS"
          

          【讨论】:

          • ETX 是什么意思?也许是文件名 EXTension 或者 End of Text...
          【解决方案13】:

          Bash 从来不擅长处理文件名(或任何文本),因为它使用空格作为列表分隔符。

          我建议将 python 与 sh 库一起使用。

          【讨论】:

            猜你喜欢
            • 2013-02-12
            • 1970-01-01
            • 2014-07-08
            • 2021-09-07
            • 2014-10-25
            • 2010-10-11
            • 2023-03-31
            • 1970-01-01
            • 2011-10-13
            相关资源
            最近更新 更多