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

发布于 2024-07-26 07:22:55 字数 692 浏览 2 评论 0 原文

使用 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 中的文件列表做有用的事情?

Using find . -print0 seems to be the only safe way of obtaining a list of files in bash due to the possibility of filenames containing spaces, newlines, quotation marks etc.

However, I'm having a hard time actually making find's output useful within bash or with other command line utilities. The only way I have managed to make use of the output is by piping it to perl, and changing perl's IFS to null:

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

This example prints the number of files found, avoiding the danger of newlines in filenames corrupting the count, as would occur with:

find . | wc -l

As most command line programs do not support null-delimited input, I figure the best thing would be to capture the output of find . -print0 in a bash array, like I have done in the perl snippet above, and then continue with the task, whatever it may be.

How can I do this?

This doesn't work:

find . -print0 | ( IFS=

A much more general question might be: How can I do useful things with lists of files in bash?

\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

A much more general question might be: How can I do useful things with lists of files in bash?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

情定在深秋 2024-08-02 07:22:55

无耻地从 Greg's BashFAQ 窃取(进行了一些更改):

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

请注意,此处使用的重定向构造(< code>cmd1 <<(cmd2)) 与更常见的管道 (cmd2 | cmd1) 类似,但不完全相同 - 如果命令是 shell 内置命令 (例如while),管道版本在子shell中执行它们,并且它们设置的任何变量(例如数组a)在它们退出时都会丢失。 <代码>cmd1 < <(cmd2) 仅在子 shell 中运行 cmd2,因此数组在其构造后仍存在。 警告:这种形式的重定向仅在 bash 中可用,甚至在 sh 模拟模式下的 bash 中也不可用; 您必须以#!/bin/bash 启动脚本。

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

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

...它通过单元 3 而不是 stdin 传递文件列表。

Shamelessly stolen (with some changes) from Greg's BashFAQ:

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

Note that the redirection construct used here (cmd1 < <(cmd2)) is similar to, but not quite the same as the more usual pipeline (cmd2 | cmd1) -- if the commands are shell builtins (e.g. while), the pipeline version executes them in subshells, and any variables they set (e.g. the array a) are lost when they exit. cmd1 < <(cmd2) only runs cmd2 in a subshell, so the array lives past its construction. Warning: this form of redirection is only available in bash, not even bash in sh-emulation mode; you must start your script with #!/bin/bash.

Also, because the file processing step (in this case, just a+=("$file"), but you might want to do something fancier directly in the loop) has its input redirected, it cannot use any commands that might read from stdin. To avoid this limitation, I tend to use:

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

...which passes the file list via unit 3, rather than stdin.

睫毛上残留的泪 2024-08-02 07:22:55

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

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

是:

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

Since Bash 4.4, the builtin mapfile has the -d switch (to specify a delimiter, similar to the -d switch of the read statement), and the delimiter can be the null byte. Hence, a nice answer to the question in the title

Capturing output of find . -print0 into a bash array

is:

mapfile -d '' ary < <(find . -print0)
伴随着你 2024-08-02 07:22:55

也许您正在寻找 xargs:

find . -print0 | xargs -r0 do_something_useful

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

Maybe you are looking for xargs:

find . -print0 | xargs -r0 do_something_useful

The option -L 1 could be useful for you too, which makes xargs exec do_something_useful with only 1 file argument.

半寸时光 2024-08-02 07:22:55

主要问题是,分隔符 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 或类似的东西也可以完成这项工作。

The main problem is, that the delimiter NUL (\0) is useless here, because it isn't possible to assign IFS a NUL-value. So as good programmers we take care, that the input for our program is something it is able to handle.

First we create a little program, which does this part for us:

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

...and call it base64str (don't forget chmod +x)

Second we can now use a simple and straightforward for-loop:

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

So the trick is, that a base64-string has no sign which causes trouble for bash - of course a xxd or something similar can also do the job.

三五鸿雁 2024-08-02 07:22:55

另一种计算文件数量的方法:

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

Yet another way of counting files:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 
南…巷孤猫 2024-08-02 07:22:55

戈登·戴维森的回答非常适合 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/** )

Gordon Davisson's answer is great for bash. However a useful shortcut exist for zsh users:

First, place you string in a variable:

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

Next, split this variable and store it in an array:

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

There is a trick: ^@ is the NUL character. To do it, you have to type Ctrl+V followed by Ctrl+@.

You can check each entry of $B contains right value:

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

Careful readers may notice that call to find command may be avoided in most cases using ** syntax. For example:

B=( /tmp/** )
老娘不死你永远是小三 2024-08-02 07:22:55

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

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

然后,您可以逐一列出文件(在本例中以相反的顺序):

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

此页面提供了一个很好的示例,有关更多信息,请参阅 /html/" rel="nofollow noreferrer">高级 Bash 脚本指南

I think more elegant solutions exists, but I'll toss this one in. This will also work for filenames with spaces and/or newlines:

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

You can then e.g. list the files one by one (in this case in reverse order):

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

This page gives a nice example, and for more see Chapter 26 in the Advanced Bash-Scripting Guide.

看透却不说透 2024-08-02 07:22:55

您可以安全地进行计数:(

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

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

You can safely do the count with this:

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

(It prints a newline for every file/dir found, and then count the newlines printed out...)

原来分手还会想你 2024-08-02 07:22:55

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

Avoid xargs if you can:

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' 
信仰 2024-08-02 07:22:55

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

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

I am new but I believe that this an answer; hope it helps someone:

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
等你爱我 2024-08-02 07:22:55

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

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

Old question, but no-one suggested this simple method, so I thought I would. Granted if your filenames have an ETX, this doesn't solve your problem, but I suspect it serves for any real-world scenario. Trying to use null seems to run afoul of default IFS handling rules. Season to your tastes with find options and error handling.

savedFS="$IFS"
IFS=
\x3'
filenames=(`find wherever -printf %p
\x3'`)
IFS="$savedFS"
醉态萌生 2024-08-02 07:22:55

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

This is similar to Stephan202's version, but the files (and directories) are put into an array all at once. The for loop here is just to "do useful things":

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

To get a count:

echo ${#files[@]}
吻泪 2024-08-02 07:22:55

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

我建议使用 python 和 sh 库。

Bash has never been good at handling filenames (or any text really) because it uses spaces as a list delimiter.

I'd recommend using python with the sh library instead.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文