带有二进制数据的Bash echo命令?

3

请问有人能解释一下为什么这个脚本有时只返回15个字节的十六进制字符串表示吗?

for i in {1..10}; do 
  API_IV=`openssl rand 16`; API_IV_HEX=`echo -n "$API_IV" | od -vt x1 -w16 | awk '{$1="";print}'`; echo $API_IV_HEX; 
done

就像这样:

c2 2a 09 0f 9a cd 64 02 28 06 43 f8 13 80 a5 04
fa c4 ac b1 95 23 7c 36 95 2d 5e 0e bf 05 fe f4
38 55 d3 b4 32 bb 61 f4 fd 17 92 67 e2 9b b4 04
6d a7 f8 46 e9 99 bd 89 87 f9 7f 2b 15 5a 17 8a
11 c8 89 f4 8f 66 93 f1 6d b9 2b 64 7e 01 61 68
93 e3 9d 28 95 e1 c8 92 e5 62 d9 bf 20 b3 1c dd
37 64 ef b0 2f da c7 60 1c c8 20 b8 28 9d f9
29 f0 5a e9 cc 36 66 de 02 82 fc 8e 36 bf 5d d1
b2 57 d8 79 21 df 73 1c af 07 e9 80 0a 67 c6 15
ba 77 cb 92 39 42 39 f9 a4 57 c8 c4 be 62 19 54

如果将 "openssl rand 16" 直接传送到 od 命令,则可以正常工作,但我需要二进制值。感谢您的帮助。

那么,我想实现的目标有没有解决方案,即同时拥有二进制和十六进制字符串?我可以将其输出到文件中并重新读取,但这是唯一的方法吗? - Luan Nguyen
2个回答

4

echo命令和其他一些标准命令一样,将\x00视为字符串结束标记。因此在其后停止显示。

也许你正在寻找openssl rand-hex选项:

sh$ openssl rand 16 -hex
4248bf230fc9dd927ab53f799e2a9708

如果您的系统支持该选项,则可以重写您的示例:

sh$ openssl version
OpenSSL 1.0.1e 11 Feb 2013

sh$ for i in {1..10}; do 
    openssl rand 16 -hex | sed -e 's|..|& |g' -e 's| $||'
done
20 cb 6b 7a 85 2d 0b fe 9e c7 d0 4b 91 88 1b bb
5d 74 99 5e 05 c9 7d 9d 37 dd 02 f3 23 bb c5 b7
51 e9 0f dc 58 04 5e 30 e3 6b 9f 63 aa fc 95 05
fc 6b b8 cb 05 82 53 85 78 0e 59 13 3b e7 c1 4b
cf fa fc d9 1a 25 df e0 f8 59 71 a6 2c 64 c5 87
93 1a 29 b4 5a 52 77 bb 3f bb 1d 0a 46 5d c8 b4
0c bb c2 b2 b4 89 d4 37 1c 86 0a 7a 58 b8 64 e2
ee fc a7 ec 6c f8 7f 51 04 43 d6 00 d8 79 65 43
b9 73 9e cc 4b 42 9e 64 9d 5b 21 6a 20 b7 c3 16
06 8a 15 22 6a d5 ae ab 9a d2 9f 60 f1 a9 26 bd

如果您需要将十六进制转换为字节,请使用此perl单行命令:
echo MY_HEX_STRING |
  perl -ne 's/([0-9a-f]{2})/print chr hex $1/gie' |
  my_tool_reading_binary_input_from_stdin

(来源于https://dev59.com/j3I-5IYBdhLWcg3w8NSB#1604770)

请注意,我使用管道到客户端程序,而不是在这里使用shell变量,因为我认为它无法正确处理\x00


作为bash无法正确处理包含\x00的二进制字符串,如果您绝对想坚持使用shell编程,则最好使用中间文件来存储二进制数据。而不是变量:
这是思路。随意根据您的需求进行调整:
for i in {1..10}; do
    openssl rand 16 > api_iv_$i # better use `mktemp` here
    API_IV_HEX=`od -vt x1 -w16 < api_iv_$i | awk '{$1="";print}'`
    echo $API_IV_HEX; 
done

sh$ bash t.sh 
cf 06 ab ab 86 fd ef 22 1a 2c bd 7f 8c 45 27 e5
2a 01 9c 7a fa 15 d3 ea 40 89 8b 26 d5 4f 97 08
55 2e c9 d3 cd 0d 3a 6f 1b a0 fe 38 6d 0e 20 07
fe 60 35 62 17 80 f2 db 64 7a af da 81 ff f7 e0
74 9a 5c 39 0e 1a 6b 89 a3 21 65 01 a3 de c4 1c
c3 11 45 e3 e0 dc 66 a3 e8 fb 5b 8a bd d0 7d 43
a4 ee 80 f8 c8 8b 4e 50 5c dd 21 00 3b d0 bc cf
e2 d5 11 d4 7d 98 08 a7 16 7b 8c 56 44 ba 6d 53
ad 63 65 fd bf 3f 1f 4a a1 c5 d0 58 23 ae d1 47
80 74 f1 d0 b9 00 e5 1d 50 74 53 96 4b ce 59 50

sh$ hexdump -C ./api_iv_10
00000000  80 74 f1 d0 b9 00 e5 1d  50 74 53 96 4b ce 59 50  |.t......PtS.K.YP|
00000010

作为个人意见,如果您确实有大量的二进制数据处理,我建议切换到一些更加面向数据的其他语言(如Python等)。

我知道openssl rand有-hex选项,但我需要两个版本:二进制和十六进制字符串。如果你想知道,十六进制字符串用作加密一些数据的IV,而二进制则用于在加密数据之前添加。如果我接受这个答案,有没有一个好的方法将十六进制字符串转换为二进制? - Luan Nguyen
@LuanNguyen,你确定可以在shell变量中存储包含\x00的二进制数据吗?据我所知,shell会被该字符串结束标记弄糊涂。在我的bash中:V=$'\xFF\xAA\x00\xBB'; echo "${#V}"显示2。对于bash来说,这个二进制字符串是两个字节长的。 - Sylvain Leroux
@LuanNguyen 我添加了一个参考链接,展示了如何使用 Perl 一行代码将 十六进制转换为字节 ... 并提醒大家在 shell 变量中存储二进制数据时需要注意的几个问题 ;) 希望这可以帮到你。 - Sylvain Leroux
非常好的解释。我如何将其接受为答案? - Luan Nguyen
我认为printf会更容易。 - SArcher

2

由于缺失的字节是ASCII NUL '\0' '\x00'。当echo命令在每个参数中遇到null字节时,它会停止打印其参数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接