好了,我有一个脚本来处理find
的null分隔输出,我可以很容易地使用bash shell来处理它,如下所示:
#!/bin/sh
find "$1" -print0 | while read -rd '' path; do echo "$path"; done
这是一个很傻的例子,因为它只是将结果转换为新行,但它只是给予你知道我想做什么。这个基本方法工作得很好,避免了由于各种文件系统上的文件可能包含新行而导致的潜在问题。
然而,我需要在非bash shell上做同样的事情,这意味着我失去了对read -d
的支持。那么,在不求助于bash(或其他shell)特定功能的情况下,有没有一种方法可以像上面一样处理空分隔的结果?
如果没有,那么保护自己免受结果中新行的最佳方法是什么呢?我在想也许可以使用find
的-exec
选项将文件名中的新行替换为某种转义值,但我不确定查找和替换新行的最佳方法(例如,我不能使用tr
)或使用什么替换,这就是为什么空字符是最好的选择。
6条答案
按热度按时间yptwkmov1#
参见How can I find and safely handle file names containing newlines, spaces or both?。
例如,您可以使用
find -exec
:或
xargs -0
:请注意,在上面的示例中,您仍然需要设置
IFS
,否则您将删除前导/尾随空格:你是对的,这是一个真实的的bummer,这个
read
只能正常工作在bash
.我通常不给予一个该死的可能在文件名中的换行符,只是确保否则可移植代码不会中断,如果他们发生(而不是忽略问题和你的脚本爆炸),我相信足以满足大多数情况下,例如.或者使用
globbing
(如果可能),它的行为正确:tmb3ates2#
添加到Adrian Frühwirth's excellent answer:
这里是一个严格符合POSIX的解决方案,包括shell代码 * 和 * 实用程序及其使用的选项:
这避免了
find
的-print0
和read -d
。注意:有一个-很大程度上是假设的-你的shell代码将被调用 * 不止一次 * 的风险,即当有这么多的输入文件名,他们的组合字节计数太大,无法传递给一个单一的
sh -c
调用-请参阅this article aboutARG_MAX
的详细信息和blubberdiblub的评论如下。也就是说,
-exec ... +
,像xargs
一样,注意ARG_MAX
的限制,并将太大的参数集分散在 * 多个 * 调用中。s71maibg3#
主题是“如何在非Bash Shell中迭代空分隔结果”。到目前为止,大多数答案都提供了
find . -print0
的特殊解决方案,实际上是通过一系列空分隔字符串(例如find . -exec ...
或shellglobbing
)来避免迭代。文件“/proc//environ”或“/proc//cmdline”是很好的(Linux)示例,它们确实需要迭代一系列以null结尾的字符串。对于仅支持POSIX的shell(例如dash)AFAIK,唯一能正确工作的解决方案是使用
xargs -0
(或类似的工具,如parallel -0
),正如在Adrian Frühwirth和FatalError的答案中已经提到的:上面的例子需要以root身份运行。它也适用于包含换行符和其他特殊字符的字符串。
uqcuzwp84#
1.使用
zsh
最简单的解决方案是使用
zsh
,这是一个非bash
的shell,支持通过read -d ""
阅读空分隔值(从4.2版开始,2004年发布)和唯一一个可以在变量中存储null的主流shell。而且,管道的最后一个组件在zsh
中不是在subshell中运行的,所以在那里设置的变量不会丢失。我们可以简单地写:使用
zsh
,我们还可以很容易地避免空分隔符的问题(至少在find . -print
的情况下),通过使用setopt globdots
,它使globs匹配隐藏文件,以及**
,它递归到子目录。这基本上适用于所有版本的zsh
,即使是那些早于4.2的版本:2.使用POSIX shell和
od
2.1使用管道
一个通用的,POSIX兼容的解决方案,用于迭代空分隔值,需要以一种不丢失信息的方式转换输入,并且将空值转换为更容易处理的其他内容。我们可以使用
od
转储所有输入字节的八进制值,并使用printf
轻松地将数据转换回来:2.2使用变量存储中间结果
请注意,
while
循环将在子shell中运行(至少在zsh
和原始的非公有域Korn shell之外的shell中),这意味着在该循环中设置的变量在代码的其余部分中不可见。如果这是不可接受的,则可以从主shell运行while
循环,其输入可以存储在变量中:2.3使用临时文件存储中间结果
如果
find
命令的输出很长,脚本将无法将输出存储在变量中,并且可能会崩溃。而且,most shells use temporary files to implement heredocs,所以与其使用变量,不如显式写入临时文件,并避免使用变量存储中间结果的问题。2.4使用命名管道
我们可以使用命名管道来解决上述两个问题:现在阅读和写可以并行完成,我们不需要在变量中存储中间结果。但是,请注意,这在Cygwin中可能不起作用。
3.修改以上方案,使其与原Bourne shell兼容
上述解决方案应该在任何POSIX shell中工作,但在原始的Bourne shell中失败,这是Solaris 10和更早版本中的默认
/bin/sh
。此shell不支持%
-替换,并且文件名中的尾随换行符需要以另一种方式保留,例如:4.分隔符不使用null
正如评论中指出的,Haravikk的答案并不完全正确。下面是他的代码的修改版本,可以处理各种奇怪的情况,例如以
~:/\/:
开头的路径和文件名中的尾随换行符。请注意,它只适用于相对路径名;一个类似的技巧可以通过在绝对路径名前面加上/./
来完成,但是需要修改read_path()
来处理它。这个方法是受Rich’s sh (POSIX shell) tricks的启发。vcirk6k65#
你可以做的一件事是使用
xargs -0
选项将参数传递给另一个shell,例如:57hvy0tb6#
Adrian Frühwirth的答案绝对是最正确和完整的,但对于那些对这个问题感兴趣的人,我只想分享我现在使用的代码:
当你像这样运行
find
时,这是可行的:由于在结果开始处添加的字符串不应该出现在实际的文件名中(如果有更简单的字符串,让我知道!),那么在决定是否将结果连接到一个字符串中时,使用它应该是安全的。
我将把它与
-print0
和read -d
支持的测试结合起来使用,这样我就可以尽可能简单地使用它,但是上面的方法应该是安全的,或者至少它可以在我迄今为止测试的所有环境中工作,并且在我不能使用更漂亮的方法时似乎可以完成这项工作;例如,如果我不能使用globbing,因为我需要来自find
或ls
的更具体的结果