是否跳过/忽略流程替换标准化的NUL字节?

时间:2015-09-22 16:24:31

标签: shell environment-variables posix nul process-substitution

执行摘要

在进行进程替换时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 valuevalue=$(printf '\0abc')会产生不同的结果,具体取决于IFS的值(read进行字段拆分)。

1 个答案:

答案 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"