如何在 Bash 中使用空字节?

如何在 Bash 中使用空字节?

我读过,由于 Bash 中的文件路径可以包含除空字节(零值字节$'\0')之外的任何字符,因此最好使用空字节作为分隔符。例如,如果 的输出find将发送到另一个程序,建议使用该-print0选项(对于find具有该选项的版本)。

但是,尽管这样的东西工作得很好(打印由换行符分隔的文件路径 - 别担心,这只是一个演示,我实际上并没有在真正的脚本中这样做):

find -print0 \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

像这样的事情不是工作:

for file in * ; do echo -n "$file"$'\0' ; done \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

当我尝试仅使用for-loop 部分时,我发现它只是将所有文件名打印在一起,没有之间的空字节。

为什么是这样?这是怎么回事?

答案1

Bash 在内部使用 C 风格的字符串,以空字节终止。这意味着 Bash 字符串(例如变量的值或命令的参数)实际上永远不能包含空字节。例如,这个迷你脚本:

foobar=$'foo\0bar'    # foobar='foo' + null byte + 'bar'
echo "${#foobar}"     # print length of $foobar

实际上打印3,因为$foobar实际上只是'foo'bar出现在字符串末尾之后。

同样,echo $'foo\0bar'只打印foo,因为echo不知道该\0bar部分。

正如您所看到的,该序列在-style 字符串\0中实际上非常具有误导性;$'...'它看起来像字符串中的一个空字节,但它最终不会以这种方式工作。在您的第一个示例中,您的read命令具有-d $'\0'.这有效,但只是因为-d ''也有效! (这不是 的明确记录的功能read,但我认为它的工作原理相同:''是空字符串,因此它的终止空字节立即出现。-d delim记录为使用“第一个字符德利姆”,我想如果“第一个字符”超出了字符串的末尾,甚至可以工作!)

但正如你从你的find例子中知道的那样一个命令可以打印出一个空字节,并且该字节可以通过管道传输到另一个将其读取为输入的命令。其中没有任何部分依赖于存储空字节在 Bash 中的字符串中。第二个示例的唯一问题是我们不能$'\0'在命令的参数中使用;echo "$file"$'\0'如果它知道您想要的话,就可以愉快地在末尾打印空字节。

echo因此,您可以使用来代替使用 ,它支持与-style 字符串printf相同类型的转义序列。$'...'这样,您就可以打印空字节,而不必在字符串中包含空字节。那看起来像这样:

for file in * ; do printf '%s\0' "$file" ; done \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

或者只是这样:

printf '%s\0' * \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

(注意:echo实际上还有一个-e标志可以让它处理\0并打印空字节;但是它也会尝试处理文件名中的任何特殊序列。因此该printf方法更加稳健。)


顺便说一句,有一些贝壳字符串中允许空字节。例如,您的示例在 Zsh 中运行良好(假设默认设置)。然而,无论您的 shell 是什么,类 Unix 操作系统都不提供在程序参数内包含空字节的方法(因为程序参数作为 C 样式字符串传递),因此总会存在一些限制。 (您的示例只能在 Zsh 中工作,因为echo它是一个内置 shell,因此 Zsh 可以调用它,而不依赖于调用其他程序的操作系统支持。如果您使用command echo而不是echo,则它会绕过内置程序并使用echo上的独立程序$PATH,您会在 Zsh 中看到与 Bash 中相同的行为。)

相关内容