出于多种原因,我雄心勃勃地尝试将 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
如果我使用 hexdump/xxd 检查文件的这一部分,虽然我无法准确读取其中的大部分内容,但似乎有些不对劲。而且我为比较而编写的代码只告诉我两个字符串是否相同,而不是它们是否按照我想要的方式复制。
在 bash 中是否有更好的方法来做到这一点?我可以简单地复制/读取本机二进制中的二进制字节,以逐字复制到文件吗? (最好也存储为变量)。
答案1
在 shell 脚本中处理低级别的二进制数据通常是一个坏主意。
bash
变量不能包含字节 0。zsh
是唯一可以在其变量中存储该字节的 shell。
在任何情况下,命令参数和环境变量都不能包含这些字节,因为它们是传递给execve
系统调用的 NUL 分隔字符串。
另请注意:
var=`cmd`
或其现代形式:
var=$(cmd)
从 的输出中删除所有尾随换行符cmd
。那么,如果那样的话二进制输出以 0xa 字节结尾,存储在$var
.
在这里,您需要存储编码的数据,例如使用xxd -p
.
hdr_988=$(head -c 988 < "$inputFile" | xxd -p)
printf '%s\n' "$hdr_988" | xxd -p -r > "$output_hdr"
您可以定义辅助函数,例如:
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
xxd -p
输出的空间效率不高,因为它将 1 个字节编码为 2 个字节,但它使使用它进行操作(连接、提取部分)变得更容易。base64
是将 3 个字节编码为 4 个字节的一种,但使用起来并不容易。
shellksh93
有一个内置的编码格式 (uses base64
),您可以将其与它的read
和printf
/print
实用程序一起使用:
typeset -b var # marked as "binary"/"base64-encoded"
IFS= read -rn 988 var < input
printf %B var > output
现在,如果没有通过 shell 或环境变量或命令参数进行传输,那么只要您使用的实用程序可以处理任何字节值,就应该没问题。但请注意,对于文本实用程序,大多数非 GNU 实现无法处理 NUL 字节,并且您需要将语言环境修复为 C 以避免多字节字符出现问题。最后一个字符不是换行符也会导致问题以及很长的行(两个 0xa 字节之间的字节序列比 更长LINE_MAX
)。
head -c
它可用的地方在这里应该没问题,因为它意味着使用字节,并且没有理由将数据视为文本。所以
head -c 988 < input > output
应该可以。实际上,至少 GNU、FreeBSD 和 ksh93 内置实现是可以的。 POSIX 没有指定该-c
选项,但表示head
应该支持任何长度的行(不限于LINE_MAX
)
和zsh
:
IFS= read -rk988 -u0 var < input &&
print -rn -- $var > output
或者:
var=$(head -c 988 < input && echo .) && var=${var%.}
print -rn -- $var > output
即使在 中zsh
,如果$var
包含 NUL 字节,您也可以将其作为参数传递给zsh
内置函数(如上print
)或函数,但不能作为参数传递给可执行文件,因为传递给可执行文件的参数是 NUL 分隔的字符串,这是内核限制,与 shell 无关。
答案2
出于多种原因,我雄心勃勃地尝试将 C++ 代码转换为 bash。
嗯,是。但也许你应该考虑不这样做的一个非常重要的原因。基本上,“bash”/“sh”/“csh”/“ksh”等不是为处理二进制数据而设计的,大多数标准 UNIX / LINUX 实用程序也不是为处理二进制数据而设计的。
您最好要么坚持使用 C++,要么使用能够处理二进制数据的脚本语言,如 Python、Ruby 或 Perl。
在 bash 中是否有更好的方法来做到这一点?
更好的方法是不要在 bash 中执行此操作。
答案3
从你的问题来看:
复制标题的前 988 行
如果您复制 988 行,那么它看起来像是一个文本文件,而不是二进制文件。但是,您的代码似乎假设 988 字节,而不是 988 行,所以我假设字节是正确的。
hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}
这部分可能不起作用。一方面,流中的任何 NUL 字节都将被删除,因为您用作${hdr_988}
命令行参数,而命令行参数不能包含 NUL。反引号也可能会进行空白处理(我对此不确定)。 (实际上,由于echo
是内置的,所以 NUL 限制可能不适用,但我想说它仍然不确定。)
为什么不直接将标头从输入文件写入输出文件,而不通过 shell 变量传递它呢?
head -c 988 "${inputFile}" >"${output_hdr}"
或者,更便携的是,
dd if="${inputFile}" of="${output_hdr}" bs=988 count=1
既然您提到您正在使用bash
,而不是 POSIX shell,您可以使用进程替换,那么将此作为测试怎么样?
cmp <(head -c 988 "${inputFile}") <(head -c 988 "${output_hdr}")
最后:考虑使用$( ... )
而不是反引号。