我发现使用 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 %B
和typeset -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
)。