仅使用 shell 工具,如何编辑包含 NULL(0x00 字符)的二进制流并在输出流中保留 0x00 字符?
编辑需要将指定位置的一个字符替换为另一个字符(在下面的示例中用字符“|”),如下所示:
dd ibs=1 skip=$offset count=$reglen status=none if=$ARQ |
sed 's/./\|/2' |
sed 's/./\|/5' #| more replacements....
但 sed 在替换之前删除了所有 '\0x00' 字符。
编辑 - 使用 @George Vasiliou 测试演示我的环境中的 sed 行为:
$ echo -e "lineA\nlineB\nlineC" | tr '\n' '\0' | od -t x1
0000000 6c 69 6e 65 41 00 6c 69 6e 65 42 00 6c 69 6e 65
0000020 43 00
0000022
$ echo -e "lineA\nlineB\nlineC" | tr '\n' '\0' | sed 's/./|/5' | od -t x1
0000000 6c 69 6e 65 7c 6c 69 6e 65 42 6c 69 6e 65 43
0000017
我的环境是 AIX 7.1,并且那里的 sed 不是 gnu 版本。
答案1
sed
是一个文本公用事业。它适用于文本行(由换行符分隔的有限长度的非 NUL 字符(不是字节)序列)。
如果你想改变第2个和第5个 字节字节序列,由于以下几个原因它不起作用:
sed
适用于文本。如果输入包含 NUL 字符、不以换行符结尾、两个换行符之间的字节数超过 LINE_MAX、包含不形成有效字符的字节序列(具体取决于实现),则sed
它将无法工作全部。 (请注意,GNUsed
没有很多这些限制)。- 即使该二进制输入碰巧形成有效文本,也
.
匹配字符而不是字节,因此可能匹配多个字节。 - 因为 sed 代码针对每个运行线输入的第二个和第五个字符,这将更改每行的第二个和第五个字符,而不是整个输入的第二个和第五个字符。
要将输入视为任意字节数组(没有 NUL 字节限制或长度限制),您可能需要改用perl
:
dd.... | perl -0777 -pe 'for $o (1, 4) {substr($_, $o, 1) = "|"}'
例子:
$ printf 'a\0b\0cd' |
> perl -0777 -pe 'for $o (1, 4) {substr($_, $o, 1) = "|"}' |
> od -Ax -tx1 -tc
000000 61 7c 62 00 7c 64
a | b \0 | d
000006
或者您可以使用中间文本表示,例如使用 的vim
帮助xxd
程序:
dd... | xxd -p | sed '1s/../7c/2;1s/../7c/5' | xxd -p -r
xxd -p
默认情况下,给出每行 60 个字符的十六进制转储。上面我们将第一行的第二个和第五个 2 位十六进制数替换7c
为 ASCII 的数字|
。
答案2
答案3
你确定吗 ?通过简单的测试,这在我的情况下似乎不会发生(gnu sed 4.2.2)
$ echo -e "lineA\nlineB\nlineC"
lineA
lineB
lineC
$ echo -e "lineA\nlineB\nlineC" |tr '\n' '\0'
lineAlineBlineC
$ echo -e "lineA\nlineB\nlineC" |tr '\n' '\0' |sed 's/./|/5'
line|lineBlineC
# Verification if the nulls are still there:
$ echo -e "lineA\nlineB\nlineC" |tr '\n' '\0' |sed 's/./|/5' |tr '\0' '\n'
line|
lineB
lineC
经过进一步测试,如果替换我的测试中的第 6 个字符(空位置),则 null 将丢失:
$ echo -e "lineA\nlineB\nlineC" |tr '\n' '\0' |sed 's/./|/6' |tr '\0' '\n'
lineA|lineB
lineC
$ echo -e "lineA\nlineB\nlineC" |tr '\n' '\0' |sed 's/./|/7' |tr '\0' '\n'
lineA
|ineB
lineC