Joh*_*ell 6 linux bash shell sh
在各种bash脚本中,我遇到了以下内容: $'\0'
一些上下文的示例:
while read -r -d $'\0' line; do
echo "${line}"
done <<< "${some_variable}"
Run Code Online (Sandbox Code Playgroud)
$'\ 0'作为其值返回什么?或者,略有不同,$'\ 0'评估为什么?为什么?
这有可能在其他地方得到解答.我在发布之前进行了搜索,但是在dollar-quote-slash-zero-quote中有限数量的字符或有意义的单词使得很难从stackoverflow搜索或谷歌获得结果.因此,如果还有其他重复问题,请允许一些优雅并将其与此问题联系起来.
ric*_*ici 12
在bash中,$'\0'
恰好相同''
:一个空字符串.在这种情况下,使用特殊的Bash语法绝对没有意义.
Bash字符串总是以NUL结尾,因此如果您设法将NUL插入字符串的中间,它将终止该字符串.在这种情况下,C-escape \0
被转换为NUL字符,然后它充当字符串终止符.
所述-d
的的选项read
内置(其限定了线端字符输入)预计其参数的单个字符.它不检查该字符是否是NUL字符,因此使用NUL终结符''
或显式NUL $'\0'
(它也是NUL终止符,因此它可能没有什么不同)同样高兴.在任何一种情况下,效果都是读取NUL终止的数据,例如由find
(s)-print0
选项产生的数据.
在特定情况下read -d '' line <<< "$var'
,不可能$var
有一个内部NUL字符(由于上述原因),因此line
将被设置为$var
删除了前导和尾随空格的整个值.(正如@mklement所说,这在建议的代码片段中不会显而易见,因为read
即使变量已经设置,也会有非零退出状态; read
只有在实际找到分隔符时才返回成功,并且NUL不能here-string的一部分.)
请注意,两者之间存在很大差异
read -d '' line
Run Code Online (Sandbox Code Playgroud)
和
read -d'' line
Run Code Online (Sandbox Code Playgroud)
第一个是正确的.在第二个中,传递给的参数单词read
是just -d
,这意味着该选项将是下一个参数(在本例中line
).read -d$'\0' line
会有相同的行为; 在任何一种情况下,空间都是必要的.(所以,再次,不需要C-escape语法).
补充rici的有用答案:
请注意,这个答案是关于bash
. ksh
并且zsh
还支持$'...'
字符串,但它们的行为不同:
*zsh
确实使用$'\0'
. 相比之下,
*ksh
具有与 相同的限制bash
,并且另外将命令替换输出中的第一个 NUL 解释为字符串终止符(在第一个 NUL 处切断,而bash
剥离此类NUL )。
$'\0'
是一个ANSI C 引用的字符串,它在技术上创建了一个 NUL(0x0
字节),但实际上导致了空(空)字符串(与 相同''
),因为任何 NUL 都被 Bash 在上下文中解释为(C 风格)字符串终止符参数和here-docs/here-strings。
因此,使用它有点误导,$'\0'
因为它表明您可以通过这种方式创建 NUL,而实际上您不能:
您不能创建 NUL作为命令参数或here-doc / here-string 的一部分,也不能将NUL 存储在变量中:
echo $'a\0b' | cat -v # -> 'a'
- 字符串在 'a' 后终止cat -v <<<$'a\0b' # -> 'a'
- 同上相比之下,在命令替换的上下文中,NUL 被剥离:
echo "$(printf 'a\0b')" | cat -v # -> 'ab'
- NUL 被剥离但是,您可以通过文件和管道传递 NUL字节。
printf 'a\0b' | cat -v # -> 'a^@b'
-通过标准输出和管道保留NULprintf
通过其单引号参数生成 NUL,printf
然后将其转义序列解释并写入标准输出。相比之下,如果您使用printf $'a\0b'
,bash
将再次将 NUL 解释为前面的字符串终止符并仅传递'a'
给printf
.如果我们考察示例代码,其目的是读取整个输入一次,跨线(因此我改变了line
对content
):
while read -r -d $'\0' content; do # same as: `while read -r -d '' ...`
echo "${content}"
done <<< "${some_variable}"
Run Code Online (Sandbox Code Playgroud)
这永远不会进入while
循环体,因为 stdin 输入是由here-string提供的,正如解释的那样,它不能包含 NUL。
请注意,read
实际上确实会使用寻找 NUL -d $'\0'
,即使$'\0'
是有效的''
. 换句话说:read
按照惯例,将空(空)字符串解释为 NUL 作为-d
选项参数,因为出于技术原因不能指定 NUL 本身。
如果输入中没有实际的 NUL,read
的退出代码表示失败,因此永远不会进入循环。
但是,即使没有分隔符,该值也会被读取,因此要使此代码与 here-string 或 here-doc 一起使用,必须进行如下修改:
while read -r -d $'\0' content || [[ -n $content ]]; do
echo "${content}"
done <<< "${some_variable}"
Run Code Online (Sandbox Code Playgroud)
然而,作为一个评论@rici笔记,用单(多线)输入字符串,就没有必要使用while
在所有:
read -r -d $'\0' content <<< "${some_variable}"
Run Code Online (Sandbox Code Playgroud)
这会读取 的全部内容$some_variable
,同时修剪前导和尾随空格(这是在其默认值下read
所做的$IFS
,$' \t\n'
)。
@rici 还指出,如果不需要这样的修剪,一个简单的方法content=$some_variable
就可以了。
将此与实际包含 NULs 的输入进行对比,在这种情况下while
,需要处理每个以 NUL 分隔的标记(但没有|| [[ -n $<var> ]]
子句);find -print0
输出由 NUL 分隔的文件名):
while IFS= read -r -d $'\0' file; do
echo "${file}"
done < <(find . -print0)
Run Code Online (Sandbox Code Playgroud)
请注意使用IFS= read ...
来禁止修剪前导和尾随空格,这在这种情况下是不希望的,因为必须按原样保留输入文件名。
小智 5
从技术上讲,扩展$'\0'
将始终成为shell(不在 zsh 中)的空字符串''
(又名空字符串)。或者,反过来说, a$'\0'
永远不会扩展为 ascii NUL
(或具有零值的字节),(同样,不在 zsh 中)。应该指出的是,这两个名称非常相似令人困惑:NUL
和null
。
然而,当我们谈论read -d ''
.
什么read
看是值''
(空字符串)作为分隔符。
什么read
确实是从标准上的字符分割输入$'\0'
(是一个实际的0x00
)。
在 bash 脚本中, $'\0' 会评估什么,为什么?
这意味着我们需要解释$'\0'
扩展到什么。
$'\0'
扩展为什么非常简单:它扩展为空字符串''
(在大多数 shell 中,而不是在 zsh 中)。
但是使用的例子是:
read -r -d $'\0'
Run Code Online (Sandbox Code Playgroud)
这将问题转换为: $'\0' 扩展为什么分隔符?
这是一个非常令人困惑的转折。为了正确解决这个问题,我们需要全面了解何时以及如何在 shell 中使用 NUL(具有零值或“0x00”的字节)。
我们需要一些 NUL 来处理。可以从 shell 生成 NUL 字节:
$ echo -e 'ab\0cd' | od -An -vtx1
61 62 00 63 64 0a ### That works in bash.
$ printf 'ab\0cd' | od -An -vtx1
61 62 00 63 64 ### That works in all shells tested.
Run Code Online (Sandbox Code Playgroud)
shell 中的变量不会存储 NUL。
$ printf -v a 'ab\0cd'; printf '%s' "$a" | od -An -vtx1
61 62
Run Code Online (Sandbox Code Playgroud)
该示例旨在在 bash 中执行,因为只有 bash printf 具有-v
选项。但是该示例清楚地表明包含 NUL 的字符串将在 NUL 处被剪切。简单变量将在零字节处剪切字符串。如果字符串是 C 字符串,它必须以 NUL 结尾,这是合理的预期\0
。一旦找到 NUL,字符串就必须结束。
在命令替换中使用 NUL 时,其工作方式会有所不同。此代码应为变量分配一个值$a
,然后打印它:
$ a=$(printf 'ab\0cd'); printf '%s' "$a" | od -An -vtx1
Run Code Online (Sandbox Code Playgroud)
确实如此,但在不同的 shell 中会有不同的结果:
### several shells just ignore (remove)
### a NUL in the value of the expanded command.
/bin/dash : 61 62 63 64
/bin/sh : 61 62 63 64
/bin/b43sh : 61 62 63 64
/bin/bash : 61 62 63 64
/bin/lksh : 61 62 63 64
/bin/mksh : 61 62 63 64
### ksh trims the the value.
/bin/ksh : 61 62
/bin/ksh93 : 61 62
### zsh sets the var to actually contain the NUL value.
/bin/zsh : 61 62 00 63 64
/bin/zsh4 : 61 62 00 63 64
Run Code Online (Sandbox Code Playgroud)
特别值得一提的是,bash(4.4 版)警告了以下事实:
/bin/b44sh : warning: command substitution: ignored null byte in input
61 62 63 64
Run Code Online (Sandbox Code Playgroud)
在命令替换中,shell 默默地忽略了零字节。
了解 zsh 中不会发生这种情况非常重要。
现在我们有了关于 NUL 的所有内容。我们可以看看 read 做了什么。
read
对 NUL 分隔符做什么。这让我们回到命令read -d $'\0'
:
while read -r -d $'\0' line; do
Run Code Online (Sandbox Code Playgroud)
该$'\0'
shoud 已扩展为一个字节的值0x00
,但外壳将其切割并实际上变成了''
. 这意味着这两个 $'\0'
和''
通过读取接收为相同的值。
话虽如此,编写等效的构造似乎是合理的:
while read -r -d '' line; do
Run Code Online (Sandbox Code Playgroud)
它在技术上是正确的。
这一点有两个方面,一个是 read 的 -d 选项之后的字符,另一个是:如果给定定界符为-d $'\0'
? ,则 read 将使用什么字符。
第一面上面已经详细回答了。
第二方面是非常令人困惑的扭曲,因为该命令read
实际上将读取值的下一个字节0x00
(即所$'\0'
代表的值)。
要实际证明情况确实如此:
#!/bin/bash
# create a test file with some zero bytes.
printf 'ab\0cd\0ef\ngh\n' > tfile
while true ; do
read -r -d '' line; a=$?
echo "exit $a"
if [[ $a == 1 ]]; then
printf 'last %s\n' "$line"
break
else
printf 'normal %s\n' "$line"
fi
done <tfile
Run Code Online (Sandbox Code Playgroud)
执行时,输出将是:
$ ./script.sh
exit 0
normal ab
exit 0
normal cd
exit 1
last ef
gh
Run Code Online (Sandbox Code Playgroud)
前两个exit 0
是成功读取进行到下一个“零字节”,并且都包含了正确的价值观ab
和cd
。下一次读取是最后一次(因为没有更多的零字节)并包含值 $'ef\ngh' (是的,它还包含一个新行)。
所有这一切都表明(并证明)read -d ''
实际上读取到下一个“零字节”,这也以 ascii 名称已知NUL
并且应该是$'\0'
扩展的结果。
简而言之:我们可以安全地声明read -d ''
读取到下一个0x00
(NUL)。
我们必须声明 aread -d $'\0'
将扩展为 的分隔符0x00
。使用$'\0'
是向读者传达这种正确含义的更好方式。作为代码风格的东西:我写 $'\0' 来表达我的意图。
一个,并且只有一个,用作分隔符的字符:的字节值0x00
(即使在 bash 中它恰好被剪切)
注意:此命令将打印流的十六进制值。
$ printf 'ab\0cd' | od -An -vtx1
$ printf 'ab\0cd' | xxd -p
$ printf 'ab\0cd' | hexdump -v -e '/1 "%02X "'
61 62 00 63 64
Run Code Online (Sandbox Code Playgroud)