如何使用 BASH 内置读取读取包括零字节的二进制数据?

如何使用 BASH 内置读取读取包括零字节的二进制数据?

我发现使用 read buildin 命令读取时 bash 会忽略输入上的二进制零。有办法解决这个问题吗?

该任务是从一次传送 12 字节二进制数据块的管道中读取数据,即 2 个 16 位整数和 2 个 32 位整数。数据速率低,性能没有问题。由于 bash 变量是 C 风格的,所以显而易见的read -N 12 struct是不起作用,超出 NUL 的字节是不可访问的。所以我想我需要使用 逐字节读取数据read -N 1 byte。容易修复的问题是转义(需要-r)和 UTF 多字节字符编码(export LC_ALL=C)。到目前为止我无法解决的问题是处理零字节。我认为它们会显示为空变量byte,但实际上read -r -N 1 byte在零时根本不返回(忽略零),而是返回数据流中的下一个非零字节。

这就是我试图做的,只要没有零进入,它就可以毫无缺陷地工作:

export LC_ALL=C

while true;
  do
     for ((index = 0; index < 12; index++))
       do
          read -r -N 1 byte
          if [ -n "${byte}" ]; then
               struct[${index}]=$(echo -n "${byte}" | od -An -td1)
             else
               struct[${index}]=0
            fi
       done
... # some arithmetics reconstructing the four bitfields and processing them
  done < pipe

事实证明else中的分支if从未被占用。包含零的 12 字节数据块不会使循环for运行 12 次,而是等待更多数据来填充数组struct。我通过使用命令向管道输入 12 个字节来演示该行为

echo -en "ABCDE\tGH\0JKL" > pipe

由于很容易欺骗自己,因此我用以下命令验证了零的发送

~# mkfifo pipe
~# od -An -td1 <pipe &
[1] 25512
~# echo -en "ABCDE\tGH\0JKL" > pipe
~#    65   66   67   68   69    9   71   72    0   74   75   76

[1]+  Done                    od -An -td1 < /root/pipe

有没有办法改变 bash 的这种行为?或者还能如何读取零字节?

答案1

bash变量不能存储 NUL 字节(只能zsh存储 NUL 字节,但另请参阅ksh93'sprintf %Btypeset -b使用 base64 编码)。它的read内置函数也会跳过输入中的 NUL 字节。

但是,在这里,您可以使用:

LC_ALL=C IFS= read -rd '' -n1 c

即从 NUL 分隔的记录中最多读取一个字节。因此,如果$c为空,则意味着 EOF(但 thenread的退出状态将为非零)或读取了 NUL 字节。

对于这两者,您可以通过以下方式获取该字节的数值:

LC_ALL=C printf -v value %d "'$c"

所以:

while
  IFS= LC_ALL=C read -rd '' -n1 c &&
    LC_ALL=C printf -v value %d "'$c"
do
  echo "Got byte with value $value"
done

一次读取一个字节直到 EOF 并支持 NUL 字节。

或者你也可以这样做:

value=$(dd bs=1 count=1 2> /dev/null | od -An -vtu1)

或者通过一些od实现:

value=$(od -N1 -An -vtu1)

尽管这意味着分叉额外的进程并运行外部可执行文件(如果 stdin 是终端设备,则不会像那样使其脱离icanon模式read)。

相关内容