36

在尝试通过使用 NULL 字符作为分隔符正确处理文件/文件夹名列表(请参阅我的其他问题)时,我偶然发现了我不理解的 Bash 奇怪行为:

将包含一个或多个 NULL 字符的字符串分配给变量时,NULL 字符会丢失/忽略/不存储。

例如,

echo -ne "n\0m\0k" | od -c   # -> 0000000   n  \0   m  \0   k

但:

VAR1=`echo -ne "n\0m\0k"`
echo -ne "$VAR1" | od -c   # -> 0000000   n   m   k

这意味着我需要将该字符串写入文件(例如,在 /tmp 中)并在不需要或不可行直接管道的情况下从那里读回它。

Z shell (zsh) 中执行这些脚本时,在这两种情况下都会保留包含 \0 的字符串,但遗憾的是,我不能假设运行我的脚本的系统中存在 zsh,而 Bash 应该存在。

如何在不丢失任何(元)字符的情况下有效地存储或处理包含 \0 字符的字符串?

4

4 回答 4

39

在 Bash 中,您不能将 NULL 字符存储在变量中。

但是,您可以使用该xxd命令存储数据的普通十六进制转储(稍后再次反转此操作)。

VAR1=`echo -ne "n\0m\0k" | xxd -p | tr -d '\n'`
echo -ne "$VAR1" | xxd -r -p | od -c   # -> 0000000    n  \0   m  \0   k
于 2011-07-04T12:32:22.710 回答
20

正如其他人已经说过的,您不能存储/使用 NUL char

  • 在一个变量中
  • 在命令行的参数中。

但是,您可以处理任何二进制数据(包括 NUL 字符):

  • 在管道中
  • 在文件中

所以回答你的最后一个问题:

谁能给我一个提示,如何在不丢失任何(元)字符的情况下有效地存储或处理包含 \0 字符的字符串?

您可以使用文件或管道有效地存储和处理具有任何元字符的任何字符串。

如果您打算处理数据,您还应注意:

绕过限制

如果你想使用变量,那么你必须通过编码来摆脱 NUL 字符,这里的各种其他解决方案提供了巧妙的方法来做到这一点(一个明显的方法是使用例如 base64 编码/解码)。

如果您担心内存或速度,您可能希望使用最小的解析器并且只引用 NUL 字符(和引用字符)。在这种情况下,这将帮助您:

quote() { sed 's/\\/\\\\/g;s/\x0/\\x00/g'; }

然后,您可以在将您的数据存储在变量和命令行参数中之前保护您的数据,方法是将您的敏感数据传输到quote中,这将输出一个没有 NUL 字符的安全数据流。echo -en "$var_quoted"您可以通过使用它将在标准输出上发送正确的字符串来取回原始字符串(带有 NUL 字符) 。

例子:

## Our example output generator, with NUL chars
ascii_table() { echo -en "$(echo '\'0{0..3}{0..7}{0..7} | tr -d " ")"; }
## store
myvar_quoted=$(ascii_table | quote)
## use
echo -en "$myvar_quoted"

注意:用于| hd获得十六进制数据的清晰视图,并检查您没有丢失任何 NUL 字符。

更换工具

请记住,您可以在不使用命令行中的变量或参数的情况下使用管道走得很远,例如,不要忘记<(command ...)将创建命名管道(一种临时文件)的构造。

编辑:的第一个实现quote不正确,无法正确处理由 .\解释的特殊字符echo -en。感谢@xhienne 发现了这一点。

EDIT2:第二个实现quote有错误,因为使用 only \0than 实际上会吃掉更多的零,因为,\0和是等价的。所以被. 感谢@MatthijsSteen 发现这个。\00\000\0000\0\x00

于 2014-07-01T13:41:41.847 回答
12

用于uuencodePOSIXuudecode可移植性

xxd并且base64 不是 POSIX 7uuencode 是.

VAR="$(uuencode -m <(printf "a\0\n") /dev/stdout)"
uudecode -o /dev/stdout <(printf "$VAR") | od -tx1

输出:

0000000 61 00 0a
0000003

不幸的是,除了写入文件之外,我没有看到 Bash 进程<()替换扩展的 POSIX 7 替代方案,并且默认情况下它们没有安装在 Ubuntu 12.04 中(sharutils包)。

所以我想真正的答案是:不要为此使用 Bash,使用 Python 或其他一些更理智的解释语言。

于 2014-04-10T10:28:35.233 回答
3

我喜欢杰夫的回答。我会使用 Base64 编码而不是 xxd。它节省了一点空间,并且(我认为)更容易识别其意图。

VAR=$(echo -ne "foo\0bar" | base64)
echo -n "$VAR" | base64 -d | xargs -0 ...

至于-e,它是回显带有编码空值('\0')的文字字符串所需要的,尽管我似乎还记得如果您将任何用户输入回显为“echo -e”是不安全的他们可以注入 echo 将解释的转义序列并以坏事告终。将编码的存储字符串回显到解码中时,不需要 -e 标志。

于 2011-07-19T15:33:12.163 回答