我读过,因为 Bash 中的文件路径可以包含除空字节(零值字节$'\0')之外的任何字符,所以最好使用空字节作为分隔符。例如,如果将 的输出find发送到另一个程序,则建议使用该-print0选项(对于find具有它的版本)。
但是,虽然这样的事情工作正常(打印由换行符分隔的文件路径 - 别担心,这只是一个演示,我实际上并没有在实际脚本中这样做):
find -print0 \
| while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done
Run Code Online (Sandbox Code Playgroud)
这样的事情就不会工作:
for file in * ; do echo -n "$file"$'\0' ; done \
| while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done
Run Code Online (Sandbox Code Playgroud)
当我只尝试for-loop 部分时,我发现它只是将所有文件名打印在一起,中间没有空字节。
为什么是这样?这是怎么回事?
rua*_*akh 49
Bash 在内部使用 C 风格的字符串,以空字节结束。这意味着 Bash 字符串(例如变量的值或命令的参数)实际上永远不能包含空字节。例如,这个小脚本:
foobar=$'foo\0bar' # foobar='foo' + null byte + 'bar'
echo "${#foobar}" # print length of $foobar
Run Code Online (Sandbox Code Playgroud)
实际上打印3,因为$foobar实际上只是'foo':bar出现在字符串末尾之后。
同样,echo $'foo\0bar'只打印foo,因为echo不知道该\0bar部分。
如您所见,\0序列实际上在$'...'- 样式字符串中非常具有误导性;它看起来像字符串中的空字节,但它最终不会以这种方式工作。在您的第一个示例中,您的read命令具有-d $'\0'. 这有效,但只是因为-d ''也有效!(这不是 的明确记录的功能read,但我认为它的工作原因相同:''是空字符串,所以它的终止空字节立即出现。记录为使用“ delim的第一个字符”,我想它甚至可以工作如果“第一个字符”超过字符串的末尾!)-d delim
但是当你从你知道的find例子,它是可能的命令打印出一个空字节,并为字节通过管道输送到读取它作为输入另一个命令。没有任何部分依赖于在 Bash 内的字符串中存储空字节。你的第二个例子的唯一问题是我们不能$'\0'在命令的参数中使用;echo "$file"$'\0'可以很高兴地在最后打印空字节,只要它知道你想要它。
因此echo,您可以使用printf,而不是使用,它支持与$'...'-style 字符串相同类型的转义序列。这样,您可以打印空字节,而不必在字符串中包含空字节。那看起来像这样:
for file in * ; do printf '%s\0' "$file" ; done \
| while IFS= read -r -d '' ; do echo "$REPLY" ; done
Run Code Online (Sandbox Code Playgroud)
或者干脆这个:
printf '%s\0' * \
| while IFS= read -r -d '' ; do echo "$REPLY" ; done
Run Code Online (Sandbox Code Playgroud)
(注意:echo实际上也有一个-e标志,可以让它处理\0和打印一个空字节;但它也会尝试处理文件名中的任何特殊序列。所以这种printf方法更健壮。)
顺便说一句,还有一些炮弹也允许空字节字符串内。例如,您的示例在 Zsh 中运行良好(假设默认设置)。但是,无论您使用哪种 shell,类 Unix 操作系统都没有提供在程序参数中包含空字节的方法(因为程序参数是作为 C 样式字符串传递的),因此总会有一些限制。(您的示例只能在 Zsh 中工作,因为它echo是一个内置的 shell,因此 Zsh 可以在不依赖操作系统支持调用其他程序的情况下调用它。如果您使用了command echo代替echo,那么它绕过了内置echo程序并在 上使用了独立程序$PATH,您会在 Zsh 中看到与在 Bash 中相同的行为。)