捕获 find 的输出。-print0 到 bash 数组中
题
使用 find . -print0
由于文件名可能包含空格、换行符、引号等,这似乎是在 bash 中获取文件列表的唯一安全方法。
然而,我很难真正使 find 的输出在 bash 或其他命令行实用程序中有用。我设法利用输出的唯一方法是将其通过管道传输到 perl,并将 perl 的 IFS 更改为 null:
find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'
此示例打印找到的文件数,避免文件名中的换行符破坏计数的危险,如下所示:
find . | wc -l
由于大多数命令行程序不支持空分隔输入,我认为最好的办法是捕获 find . -print0
在 bash 数组中,就像我在上面的 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内建(例如while
),管道版本在子shell执行它们,并且它们设置(例如阵列a
)任何变量都将丢失时退出。 cmd1 < <(cmd2)
只能运行在CMD2子shell,所以阵列住过去的建设。警告:这种形式的重定向只在bash可用,甚至没有庆典在SH-仿真模式;你必须#!/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通过文件列表,而不是标准输入。
其他提示
也许你正在寻找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字符串没有迹象引起麻烦的bash - 当然是一个XXD或类似的东西也可以做的工作
然而计数文件的另一种方法:
find /DIR -type f -print0 | tr -dc '\0' | wc -c
由于击4.4,内建mapfile
具有-d
开关(以指定的分隔符,类似于-d
语句的read
开关),以及分隔符可以是一个空字节。因此,一个很好的问题的答案在标题
捕获
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
这一页 给出了一个很好的例子,更多信息请参见 第26章 在里面 高级 Bash 脚本指南.
避免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,这并不能解决你的问题,但我怀疑它服务于任何真实世界的场景。尝试使用空,似乎默认IFS处理规则相抵触运行。季节你的口味与查找选项和错误处理。
savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"
戈登·戴维森的答案是伟大的庆典。然而有用的快捷方式为用户的zsh存在:
首先,将你串中的变量:
A="$(find /tmp -type f -print0)"
接着,分割这个变量并将它存储在一个阵列:
B=( ${(s/^@/)A} )
有一招:^@
是NUL字符。要做到这一点,必须按下Ctrl + V,接着按Ctrl + @
您可以检查$ B的每一个条目包含正确的值:
for i in "$B[@]"; echo \"$i\"
细心的读者可能注意到呼叫find
命令可以在使用**
语法大多数情况下被避免。例如:
B=( /tmp/** )
猛砸从未善于处理文件名(或任何文本真的),因为它使用空格作为列表分隔符。
我建议使用python与 SH 的库,而不是