由于种种原因,我雄心勃勃地尝试将 c++ 代码转换为 bash。
此代码读取和操作特定于我的子字段的文件类型,该文件类型完全以二进制形式编写和构建。我的第一个与二进制相关的任务是完全按原样复制头的前 988 个字节,并将它们放入一个输出文件中,我可以在生成其余信息时继续写入该文件。
我很确定我当前的解决方案不起作用,实际上我还没有找到确定这一点的好方法。因此,即使它实际上编写正确,我也需要知道如何测试以确保!
这就是我现在正在做的:
hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}
headInput=`head -c 988 ${inputTrack} | hexdump`
headOutput=`head -c 988 ${output_hdr} | hexdump`
if [ "${headInput}" != "${headOutput}" ]; then echo "output header was not written properly. exiting. please troubleshoot."; exit 1; fi
Run Code Online (Sandbox Code Playgroud)
如果我使用 hexdump/xxd 来检查文件的这一部分,虽然我不能完全阅读其中的大部分内容,但似乎有些不对劲。我为比较而编写的代码只告诉我两个字符串是否相同,而不是按照我希望的方式复制它们。
有没有更好的方法在 bash 中做到这一点?我可以简单地复制/读取本机二进制中的二进制字节,逐字复制到文件吗?(理想情况下也存储为变量)。
Sté*_*las 23
在 shell 脚本中处理低级别的二进制数据通常是一个坏主意。
bash
变量不能包含字节 0。zsh
是唯一可以将该字节存储在其变量中的 shell。
在任何情况下,命令参数和环境变量都不能包含这些字节,因为它们是传递给execve
系统调用的NUL 分隔字符串。
另请注意:
var=`cmd`
Run Code Online (Sandbox Code Playgroud)
或其现代形式:
var=$(cmd)
Run Code Online (Sandbox Code Playgroud)
从cmd
. 因此,如果该二进制输出以 0xa 字节结尾,则存储在$var
.
在这里,您需要存储编码的数据,例如使用xxd -p
.
hdr_988=$(head -c 988 < "$inputFile" | xxd -p)
printf '%s\n' "$hdr_988" | xxd -p -r > "$output_hdr"
Run Code Online (Sandbox Code Playgroud)
您可以定义辅助函数,例如:
encode() {
eval "$1"='$(
shift
"$@" | xxd -p -c 0x7fffffff
exit "${PIPESTATUS[0]}")'
}
decode() {
printf %s "$1" | xxd -p -r
}
encode var cat /bin/ls &&
decode "$var" | cmp - /bin/ls && echo OK
Run Code Online (Sandbox Code Playgroud)
xxd -p
输出空间效率不高,因为它将 1 个字节编码为 2 个字节,但它可以更轻松地对其进行操作(连接、提取部分)。base64
是一种将 3 个字节编码为 4 个字节,但不太容易使用。
该ksh93
外壳具有编码格式(使用内置的base64
),你可以用它的使用read
和printf
/print
公用事业:
typeset -b var # marked as "binary"/"base64-encoded"
IFS= read -rn 988 var < input
printf %B var > output
Run Code Online (Sandbox Code Playgroud)
现在,如果没有通过 shell 或 env 变量或命令参数进行传输,只要您使用的实用程序可以处理任何字节值,就应该没问题。但请注意,对于文本实用程序,大多数非 GNU 实现无法处理 NUL 字节,您需要将语言环境修复为 C 以避免多字节字符出现问题。最后一个不是换行符的字符也可能导致问题以及很长的行(两个 0xa 字节之间的字节序列更长LINE_MAX
)。
head -c
它可用的地方在这里应该没问题,因为它意味着使用字节,并且没有理由将数据视为文本。所以
head -c 988 < input > output
Run Code Online (Sandbox Code Playgroud)
应该可以。在实践中,至少 GNU、FreeBSD 和 ksh93 内置实现是可以的。POSIX 没有指定-c
选项,但表示head
应该支持任何长度的行(不限于LINE_MAX
)
与zsh
:
IFS= read -rk988 -u0 var < input &&
print -rn -- $var > output
Run Code Online (Sandbox Code Playgroud)
或者:
var=$(head -c 988 < input && echo .) && var=${var%.}
print -rn -- $var > output
Run Code Online (Sandbox Code Playgroud)
即使在zsh
, 如果$var
包含 NUL 字节,您也可以将其作为参数传递给zsh
内置print
函数(如上)或函数,但不能作为参数传递给可执行文件,因为传递给可执行文件的参数是 NUL 分隔的字符串,这是内核限制,独立于外壳。
小智 11
由于种种原因,我雄心勃勃地尝试将 c++ 代码转换为 bash。
嗯,是。但也许你应该考虑一个非常重要的不这样做的原因。基本上,“bash”/“sh”/“csh”/“ksh”等不是为处理二进制数据而设计的,大多数标准 UNIX/LINUX 实用程序也不是。
您最好坚持使用 C++,或者使用能够处理二进制数据的脚本语言,如 Python、Ruby 或 Perl。
有没有更好的方法在 bash 中做到这一点?
更好的方法是不要在 bash 中执行此操作。
从你的问题:
复制标题的前 988 行
如果您要复制 988 行,那么它看起来像一个文本文件,而不是二进制文件。但是,您的代码似乎假定为 988 字节,而不是 988 行,因此我假定字节是正确的。
hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}
Run Code Online (Sandbox Code Playgroud)
这部分可能不起作用。一方面,流中的任何 NUL 字节都将被剥离,因为您${hdr_988}
用作命令行参数,而命令行参数不能包含 NUL。反引号也可能会进行空格处理(我不确定)。(实际上,由于echo
是内置的,NUL 限制可能不适用,但我会说它仍然不确定。)
为什么不直接将标头从输入文件写入输出文件,而不通过 shell 变量传递它?
head -c 988 "${inputFile}" >"${output_hdr}"
Run Code Online (Sandbox Code Playgroud)
或者,更便携的是,
dd if="${inputFile}" of="${output_hdr}" bs=988 count=1
Run Code Online (Sandbox Code Playgroud)
既然您提到您使用的是bash
,而不是 POSIX shell,那么您可以使用进程替换,那么作为测试如何呢?
cmp <(head -c 988 "${inputFile}") <(head -c 988 "${output_hdr}")
Run Code Online (Sandbox Code Playgroud)
最后:考虑使用$( ... )
而不是反引号。