执行摘要
在进行进程替换时shell是否跳过NUL字节的标准行为?
例如,执行
printf '\0abc' | read value && echo $value
将产生abc
。即使printf
输出的hexdump显示输出明显正在输出,也会跳过NUL值。
我的第一个想法是“分词”。但是,在使用实际流程替换时
value=$(printf '\0abc')
结果相似,=
不执行分词。
长篇故事
在搜索this question的正确答案时,我意识到至少有三个我很熟悉的shell实现(ash,zsh和bash)忽略一个NUL将过程替换中的值读入变量时的字符。
当发生这种情况时,管道中的确切点似乎是不同的,但结果始终是NUL字节被丢弃,就好像它从未出现在那里一样。
我已经检查了一些实现,好吧,这似乎是正常的行为。
ash
将skip over '\0'
on input,但从代码中可以看出这是纯粹的巧合还是预期的行为:
if (lastc != '\0') {
[...]
}
bash
源代码包含explicit, albeit #ifdef
'd warning告诉我们它在进程替换时跳过了NUL值:
#if 0
internal_warning ("read_comsub: ignored null byte in input");
#endif
我对zsh
的行为不太确定。它将'\0'
识别为元字符(由内部imeta()
函数定义)并预先设置一个特殊的Meta
代理字符并在输入字符上设置第5位,基本上取消默认它,这也使'\0'
进入空间' '
)
if (imeta(c)) {
*ptr++ = Meta;
c ^= 32;
cnt++;
}
这似乎后来被删除,因为没有证据表明上面的value
命令中的printf
包含元字符。因为我不熟悉zsh
的内部因素,所以需要大量帮助。另请注意副作用免费声明。
请注意,zsh
还允许您在IFS
中包含NUL(元转义)(例如,可以在没有find -print0
的情况下进行单词分割xargs -0
)。因此,printf '\0abc' | read value
和value=$(printf '\0abc')
会产生不同的结果,具体取决于IFS
的值(read
进行字段拆分)。
答案 0 :(得分:4)
所有现存的POSIX shell都使用C字符串(NUL终止),而不是Pascal字符串(将其长度作为单独的元数据,因此能够包含NUL)。因此,它们不可能在字符串内容中包含NUL。对于Bourne Shell和ksh来说尤其如此,这两者都是对POSIX sh标准的主要影响。
规范允许shell在这里以实现定义的方式运行;在不知道特定的shell和释放被定位的情况下,我不希望在终止第一个NUL返回的流和完全丢弃NUL之间的特定行为。 Quoting:
shell应通过在子shell环境中执行命令(参见Shell执行环境)并使用标准输出替换命令替换(命令文本加上封闭的“$()”或反引号)来扩展命令替换。命令,在替换结束时删除一个或多个字符的序列。输出结束前的嵌入字符不得删除;但是,根据IFS的值和有效的引用,它们可以被视为字段分隔符并在字段拆分期间被消除。 如果输出包含任何空字节,则行为未指定。
这并不是说你无法在广泛使用的shell中读取和生成包含NUL的流!请参阅下面的内容,使用进程替换(为bash编写,但如果有的话,应该使用ksh或zsh进行微小更改):
# read content from stdin into array variable and a scalar variable "suffix"
array=( )
while IFS= read -r -d '' line; do
array+=( "$line" )
done < <(process that generates NUL stream here)
suffix=$line # content after last NUL, if any
# emit recorded content
printf '%s\0' "${array[@]}"; printf '%s' "$suffix"