繁体   English   中英

捕获查找的输出。 -print0进入bash数组

[英]Capturing output of find . -print0 into a bash array

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

但是,我很难让find的输出在bash或其他命令行实用程序中有用。 我设法使用输出的唯一方法是将它传递给perl,并将perl的IFS更改为null:

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

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

find . | wc -l

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

我怎样才能做到这一点?

这不起作用:

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

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

Greg的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 < <(cmd2) )与更常用的管道( cmd2 | cmd1 )类似但不完全相同 - 如果命令是shell builtins(例如while ),则管道版本在子shell中执行它们,它们设置的任何变量(例如数组a )在退出时都会丢失。 cmd1 < <(cmd2)仅在子shell中运行cmd2,因此数组将超过其构造。 警告:这种重定向形式仅在bash中可用,在sh仿真模式下甚至不是bash; 你必须用#!/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传递文件列表,而不是stdin。

也许你正在寻找xargs:

find . -print0 | xargs -r0 do_something_useful

选项-L 1对你也很有用,这使得xargs exec do_something_useful只有1个文件参数。

主要问题是,分隔符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-string没有任何迹象会导致bash出现问题 - 当然xxd或类似的东西也可以完成这项工作。

另一种计算文件的方法:

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

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

捕获find . -print0输出find . -print0 find . -print0进入bash数组

是:

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

你可以安全地计算:

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

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

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

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

然后,您可以逐个列出文件(在这种情况下以相反的顺序):

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

此页面提供了一个很好的示例,有关更多示例,请参阅Advanced Bash-Scripting Guide中的第26章

如果可以,请避免使用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' 

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

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

这与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[@]}

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

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

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/** )

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

我建议使用shthon代替sh库。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM