因此,我想将 binaryFile.dd 的前 512 个字节作为第二个参数传递给 myProgram,但 bash 会删除所有 NUL 字符。有什么办法可以在 bash 中避免这种情况,还是我什么都隐藏?
myProgram parameter1 "$(head -c 512 binaryFile.dd)"
答案1
无法在命令参数中传递空字节。这并不是因为 bash 的限制,尽管 bash 也有这个限制。这是运行命令的接口的限制:它将空字节视为参数的结尾。没有逃避机制。
大多数 shell 不支持变量或函数和内置函数的参数中的空字节。 Zsh 是一个值得注意的例外。
$ ksh -c 'a=$(printf foo\\0bar); printf "$a"' | od -t x1
0000000 66 6f 6f
0000003
$ bash -c 'a=$(printf foo\\0bar); printf "$a"' | od -t x1
0000000 66 6f 6f 62 61 72
0000006
$ zsh -c 'a=$(printf foo\\0bar); printf "$a"' | od -t x1
0000000 66 6f 6f 00 62 61 72
0000007
但即使使用 zsh,如果您尝试将参数传递给外部命令,那么空字节后面的任何内容都会被忽略 - 不是由 zsh 而是由内核忽略。
$ zsh -c 'a=$(printf foo\\0bar); /usr/bin/printf "$a"' | od -t x1
0000000 66 6f 6f
0000003
如果要将空字节传递给程序,则需要找到命令行参数以外的其他方法。
head -c 512 binaryFile.dd | myProgram --read-parameter2-from-stdin parameter1
myProgram --read-parameter2-from-file=<(head -c 512 binaryFile.dd) parameter1
答案2
bash
不太适合直接处理二进制数据。
可以将二进制数据与文件一起使用,也可以使用表示数据的十六进制字符串。
要转换为十六进制,您可以使用hexdump
, xxd
, od
。
例如,要将 512 字节转换为十六进制字符串,请使用
xxd -ps -c 512 file.bin
将其转换回二进制使用
echo "$myhexstring" | xxd -r -ps > file.bin
答案3
不,bash 中的字符串不可能包含 NUL ( \0
)。
因此,变量(因为它包含字符串)不能包含 NUL。
原因是 bash 是用c
“字符串以 NUL 结尾”的范式编写的。[1] Linux内核也施加了这样的限制。[2]但即使内核允许在字符串[3](参数)中使用 NUL,大多数 shell,尤其是 bash,也不能在变量[4]中包含 NUL 。
位置参数($1
、$2
等)相当于变量,也不能包含 NUL。
然而,nul 可以存在于文件、流和 printf 中:
$ printf 'test\0nuls\n' | od -vAn -tx1c
74 65 73 74 00 6e 75 6c 73 0a
t e s t \0 n u l s \n
正如您所看到的,printf 创建了一个 NUL 并且它流经管道 ( |
)。但 NUL 是从“命令执行”中剥离出来的:
$ echo $(printf 'test\0nuls\n') | od -vAn -tx1c
bash: warning: command substitution: ignored null byte in input
74 65 73 74 6e 75 6c 73 0a
t e s t n u l s \n
在 bash 4.4 中它甚至会发出警告。在这种情况下,zsh 会默默地将 NUL 替换为空格:
$ zsh -c ' echo $(printf "test\0nuls\n") | od -vAn -tx1c'
74 65 73 74 20 6e 75 6c 73 0a
t e s t n u l s \n
我们可以使用 printf 以及包含 NUL 的文件的cat
、head
或tail
dd 部分创建一个包含 NUL 的文件:
$ printf 'test\0nuls\0in\0files\0\n' > testnul.bin
$ cat testnul.bin | xxd -ps
74657374006e756c7300696e0066696c6573000a
$ head -c 7 testnul.bin | xxd -ps
74657374006e75
$ dd if=testnul.bin bs=7 count=1 | xxd -ps
74657374006e75
1+0 records in
1+0 records out
7 bytes copied, 0.000655689 s, 10.7 kB/s
$ dd if=testnul.bin bs=7 count=1 2>/dev/null| xxd -ps
74657374006e75
就您而言,没有简单的[5]方法可以将二进制文件的内容作为参数。也许十六进制表示可以工作:
$ myProgram "$parameter1" "$(xxd -ps -c 512 binaryFile.dd)"
感谢@Gilles 所做的所有额外工作(和细节)。
1
[1]
一切都归结为旧的的定义C string
«字符串以 NUL ( \0
) 结尾»。该范例已在多个C
库和工具中进行编码,其中 POSIX 有几个示例。喜欢strcpy
在这里其中指出(强调我的):
strcpy() 函数应复制 s2 指向的字符串(包括终止 NUL 字符) 到 s1 指向的数组中。
这意味着假定字符串以 NUL 结尾。
或者,换句话说,可能只有一个 NUL,即最后一个。
2
[2]系统execve()
调用,也在 POSIX 中定义,期望字符串(命令参数)以 NUL 结尾。这就是为什么即使 shell 可以与 NUL 一起使用(大多数都不能,但 zsh 是一个明显的例外):
$ zsh -c 'a=$(printf "included\0null"); printf "$a"' | od -vAn -tx1c
69 6e 63 6c 75 64 65 64 00 6e 75 6c 6c
i n c l u d e d \0 n u l l
能不是在调用传递的参数中使用 NUL execve()
:
$ zsh -c 'a=$(printf "included\0null"); /usr/bin/printf "$a"' | od -vAn -tx1c
69 6e 63 6c 75 64 65 64
i n c l u d e d
3
[3]但即使内核能够在参数中包含 NUL,bash 也不允许它们:
$ bash -c 'a=$(printf "included\0null"); /usr/bin/printf "$a"' | od -vAn -tx1c
bash: warning: command substitution: ignored null byte in input
69 6e 63 6c 75 64 65 64 6e 75 6c 6c
i n c l u d e d n u l l
在 bash 4.4 中,当 NUL 被删除时,它甚至会发出警告。
4
[4] 大多数 shell,尤其是 bash,不能在变量中包含 NUL。
$ printf 'included\0null' | od -vAn -tx1c
69 6e 63 6c 75 64 65 64 00 6e 75 6c 6c
i n c l u d e d \0 n u l l
$ printf 'included\0null' | ( read a; printf '%s\n' "$a" | od -vAn -tx1c )
69 6e 63 6c 75 64 65 64 6e 75 6c 6c
i n c l u d e d n u l l
如果正在运行的 shell 是 zsh,则这(相反)将使用 null:
$ zsh -c 'printf "included\0null" | ( read a; printf "%s\n" "$a" | od -vAn -tx1c )'
69 6e 63 6c 75 64 65 64 00 6e 75 6c 6c 0a
i n c l u d e d \0 n u l l \n
5
[5]
这意味着“直接”(简单)包含值 0 ( ) 的字节\0
是不可能的。但是,使用十六进制、基数 64 或某种等效形式的 C-string 编码(复杂),$'\0'
可以包含零值。