使用 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?
发布评论
评论(13)
无耻地从 Greg's BashFAQ 窃取(进行了一些更改):
请注意,此处使用的重定向构造(< code>cmd1 <<(cmd2)) 与更常见的管道 (
cmd2 | cmd1
) 类似,但不完全相同 - 如果命令是 shell 内置命令 (例如while
),管道版本在子shell中执行它们,并且它们设置的任何变量(例如数组a
)在它们退出时都会丢失。 <代码>cmd1 < <(cmd2) 仅在子 shell 中运行 cmd2,因此数组在其构造后仍存在。 警告:这种形式的重定向仅在 bash 中可用,甚至在 sh 模拟模式下的 bash 中也不可用; 您必须以#!/bin/bash
启动脚本。另外,因为文件处理步骤(在本例中,只是
a+=("$file")
,但您可能想直接在循环中做一些更奇特的事情)的输入已重定向,因此它无法使用任何可能从标准输入读取的命令。 为了避免这个限制,我倾向于使用:...它通过单元 3 而不是 stdin 传递文件列表。
Shamelessly stolen (with some changes) from Greg's BashFAQ:
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 arraya
) 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:...which passes the file list via unit 3, rather than stdin.
从 Bash 4.4 开始,内置的
mapfile
具有-d
开关(用于指定分隔符,类似于的
语句),分隔符可以是空字节。 因此,对标题中的问题有一个很好的回答-d
开关) read是:
Since Bash 4.4, the builtin
mapfile
has the-d
switch (to specify a delimiter, similar to the-d
switch of theread
statement), and the delimiter can be the null byte. Hence, a nice answer to the question in the titleis:
也许您正在寻找 xargs:
选项 -L 1 也可能对您有用,这使得 xargs exec do_something_useful 只需 1 个文件参数。
Maybe you are looking for xargs:
The option -L 1 could be useful for you too, which makes xargs exec do_something_useful with only 1 file argument.
主要问题是,分隔符 NUL (\0) 在这里没有用,因为不可能为 IFS 分配 NUL 值。 因此,作为优秀的程序员,我们要注意程序的输入是它能够处理的。
首先,我们创建一个小程序,它为我们完成这一部分:
...并将其称为 base64str (不要忘记 chmod +x)
其次,我们现在可以使用一个简单而直接的 for 循环:
所以诀窍是, 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:
...and call it base64str (don't forget chmod +x)
Second we can now use a simple and straightforward for-loop:
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.
另一种计算文件数量的方法:
Yet another way of counting files:
戈登·戴维森的回答非常适合 bash。 然而,对于 zsh 用户来说,有一个有用的快捷方式:
首先,将字符串放入变量中:
接下来,拆分该变量并将其存储在数组中:
有一个技巧:
^@
是 NUL 字符。 为此,您必须键入 Ctrl+V,然后键入 Ctrl+@。您可以检查 $B 的每个条目是否包含正确的值:
细心的读者可能会注意到,在大多数情况下使用
**
语法可以避免调用find
命令。 例如:Gordon Davisson's answer is great for bash. However a useful shortcut exist for zsh users:
First, place you string in a variable:
Next, split this variable and store it in an array:
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:
Careful readers may notice that call to
find
command may be avoided in most cases using**
syntax. For example:我认为存在更优雅的解决方案,但我会扔掉这个。这也适用于带有空格和/或换行符的文件名:
然后,您可以逐一列出文件(在本例中以相反的顺序):
此页面提供了一个很好的示例,有关更多信息,请参阅 /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:
You can then e.g. list the files one by one (in this case in reverse order):
This page gives a nice example, and for more see Chapter 26 in the Advanced Bash-Scripting Guide.
您可以安全地进行计数:(
它为找到的每个文件/目录打印换行符,然后计算打印出的换行符...)
You can safely do the count with this:
(It prints a newline for every file/dir found, and then count the newlines printed out...)
如果可以的话,请避免使用 xargs:
Avoid xargs if you can:
我是新人,但我相信这是一个答案; 希望它可以帮助某人:
I am new but I believe that this an answer; hope it helps someone:
老问题,但没有人建议这种简单的方法,所以我想我会的。 当然,如果您的文件名有 ETX,这并不能解决您的问题,但我怀疑它适用于任何现实场景。 尝试使用 null 似乎违反了默认的 IFS 处理规则。 通过查找选项和错误处理来根据您的口味进行调味。
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.
这与Stephan202的版本类似,但是文件(和目录)被一次性放入一个数组中。 这里的
for
循环只是为了“做有用的事情”:获取计数:
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":To get a count:
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.