我如何在bash中使用二进制文件来逐字复制字节而不进行任何转换?


14

由于种种原因,我正在雄心勃勃地尝试将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检出文件的这一部分,尽管我无法完全读取其中的大部分内容,但似乎有些问题。我编写的用于比较的代码仅告诉我两个字符串是否相同,而不告诉我是否按照我希望的方式复制它们。

有更好的方法来执行此操作吗?我可以简单地复制/读取本机二进制文件中的二进制字节以逐字复制到文件中吗?(最好也存储为变量)。


您可以dd用来复制单个字节(将其设置count1)。不过,我不确定要存储它们。
DDPWNAGE '16

不要以C方式进行重击,这会造成很多麻烦。而是使用适当的bash构造
Ferrybig '16

Answers:


22

在shell脚本中以较低级别处理二进制数据通常是一个坏主意。

bash变量不能包含字节0。zsh是唯一可以在其变量中存储该字节的外壳。

无论如何,命令参数和环境变量不能包含那些字节,因为它们是传递给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输出不是空间有效的,因为它在2个字节中编码了1个字节,但是它使得使用它进行操作(连接,提取部分)更加容易。base64是一种以4为单位编码3个字节的格式,但使用起来并不容易。

ksh93外壳具有内置的编码格式(uses base64),您可以将其readprintf/ 和print实用程序一起使用:

typeset -b var # marked as "binary"/"base64-encoded"
IFS= read -rn 988 var < input
printf %B var > output

现在,如果没有通过shell或env变量或命令参数进行传递,那么只要您使用的实用程序可以处理任何字节值,就可以了。但是请注意,对于文本实用程序,大多数非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。


zsh不是唯一可以在一个shell变量中存储一个或多个NUL字节的shell。 ksh93也可以这样做。在内部,ksh93仅将二进制变量存储为base64编码的字符串。
fpmurphy

@ fpmurphy1,这不是我所谓的处理二进制数据,该变量不包含二进制数据,因此,例如,您不能在它们上使用任何shell运算符,也不能将它们传递给内置函数或函数解码形式...我称其为内置base64编码/解码支持
斯特凡Chazelas

11

由于种种原因,我正在雄心勃勃地尝试将c ++代码转换为bash。

嗯,是。但是也许您应该考虑一个不这样做的重要原因。基本上,“ bash” /“ sh” /“ csh” /“ ksh”等并不是为处理二进制数据而设计的,也不是大多数标准UNIX / LINUX实用程序。

坚持使用C ++或使用能够处理二进制数据的脚本语言(例如Python,Ruby或Perl)会更好。

有更好的方法来执行此操作吗?

更好的方法是不进行bash操作。


4
+1表示“更好的方法是不进行bash操作”。
Guntram Blohm支持Monica

1
不采用这种方法的另一个原因是,最终的应用程序运行速度将大大降低,并消耗更多的系统资源。
fpmurphy

Bash管道可以充当某种高级领域特定语言,可以提高可理解性。没有任何关于管道不是二进制,并且有作为命令行工具来实现各种工具与二进制数据交互(ffmpegimagemagickdd)。现在,如果要进行编程而不是将所有内容粘合在一起,那么使用一种功能强大的编程语言就可以了。
Att Righ

6

根据您的问题:

复制标题的前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

既然您提到使用的是bashPOSIX shell,而不是POSIX shell,那么您可以使用进程替换,那么作为测试如何呢?

cmp <(head -c 988 "${inputFile}") <(head -c 988 "${output_hdr}")

最后:考虑使用$( ... )而不是反引号。


请注意,这dd不一定等同head于非常规文件。headread(2)根据需要进行尽可能多的系统调用来获得这988个字节,而dd只会做一个read(2)。GNU dd有一个iflag=fullblock尝试完整读取该块的方法,但是它的可移植性甚至比少head -c
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.