如何获取二进制文件的前十个字节

112

我正在编写一个bash脚本,需要获取文件的头部(前10个字节),然后在另一个部分获取除前10个字节之外的所有内容。这些是二进制文件,并且可能在前10个字节中包含\0\n。似乎大多数工具都适用于ASCII文件。有什么好的方法来完成这个任务吗?


1
如需十六进制输出,请参考此答案 https://unix.stackexchange.com/a/10882/103618 - Alwin Kesler
4个回答

195

如前所述,要获取前10个字节:

head -c 10

要获取除了前10个字节之外的所有内容(至少使用GNU的 tail 命令):

tail -c+11

3
是的。我喜欢dd的想法,但这个选项不需要改变缓冲区大小,所以它适用于大文件。谢谢。 - User1
1
不要打开同一个文件两次,你可以使用(head;cat)<file!请看我的回答 - F. Hauri - Give Up GitHub

59

head -c 10 在这里做了正确的事情。


11
有趣的是,这个回答是第一个,它是正确的,但其他回答得到了更多的赞。 - robert
4
可能是因为这只是一个部分回答。 - valid

38
你可以使用 dd 命令从二进制文件中复制任意数量的字节。
dd if=infile of=outfile1 bs=10 count=1
dd if=infile of=outfile2 bs=10 skip=1

2
这个可以工作,但速度很慢。10个字节的bs正在处理这些巨大文件。有没有更快的方法跳过前10个字节?看起来很简单。 - User1
@ceving,你需要在{ printf a; sleep 1; printf b; }|cat | dd bs=2 count=1 iflag=fullblock 2>/dev/null | wc -c中使用iflag=fullblock。请参考man dddd --help - F. Hauri - Give Up GitHub
@F.Hauri 一般来说这是一个好主意,因为对于管道来说是必要的。但我认为对于文件来说没有必要。 - ceving
@ceving,如果你在处理一个大文件,将缓冲区大小降低到10字节以读取整个文件是过度杀伤! - F. Hauri - Give Up GitHub
@ceving 对于文件和流,没有理由采取不同的操作方式。 - F. Hauri - Give Up GitHub
显示剩余4条评论

2

如何在下分割一个流(或文件)

这里有两个答案!

阅读SO请求:

获取文件的头部(前10个字节),然后在另一部分中获取除前10个字节以外的所有内容。

我理解为:

如何在特定点分割文件

由于所有答案都访问同一个文件两次,而不是仅仅将其分割

这是我的建议:

在使用Un*x时有趣的事情是将每个完整的工作视为过滤器,使用非缓冲的I/O可以轻松地拆分流。大多数标准的Un*x工具(如cat、grep、awk、sed、python、perl等)都可以作为过滤器。

1. 使用head或dd但只需一次操作

{ head -c 10 >head_part; cat >tail_part;} <file

这种方法更加高效,因为文件只被读取了一次,前10个字节被放到head_part中,剩余的部分则被放到tail_part中。

注意:第二个重定向>tail_part也可以放在整个list ({ ...;}) 的外面。

你也可以使用dd来实现同样的功能:

{ dd count=1 bs=10 of=head_part; cat;} <file >tail_part

这个翻译如下:

这种方法比运行两个dd进程两次打开同一个文件更有效率。

...并且仍然使用标准的块大小来处理其余部分的文件:

另一个基于按行读取的示例:

将HTTP(或邮件)流根据几乎为空的行(只包含回车符\r)进行拆分:

nc google.com 80 <<<$'GET / HTTP/1.0\r\nHost: google.com\r\n\r' |
    { sed -u '/^\r$/q' >/tmp/so_head.raw; cat;} >/tmp/so_body.raw

或者,去掉空的最后一行标题:
nc google.com 80 <<<$'GET / HTTP/1.0\r\nHost: google.com\r\n\r' |
    { sed -nu '/^\r$/q;p' >/tmp/so_head.raw; cat;} >/tmp/so_body.raw

这将会产生两个文件:
ls -l so_*.raw
-rw-r--r-- 1 root    root           307 Apr 25 11:40  so_head.raw
-rw-r--r-- 1 root    root           219 Apr 25 11:40  so_body.raw

grep www so_*.raw
so_body.raw:<A HREF="http://www.google.com/">here</A>.
so_head.raw:Location: http://www.google.com/

2. Pure bash way:

如果目标是在一个可用的变量中获取前10个字节的值,这里有一种不错且高效的方法:

因为只有十个字节,可以避免使用head命令。参考在BASH中按字节读取文件

read8() {
    local _r8_var=${1:-OUTBIN} _r8_car LANG=C IFS=
    read -r -d '' -n 1 _r8_car || { printf -v $_r8_var '';return 1;}
    printf -v $_r8_var %02X "'"$_r8_car
}
{ 
    first10=()
    for i in {0..9};do
        read8 first10[i] || break
    done
    cat
 } < "$infile" >"$outfile"

这将创建一个数组${first10[@]},其中包含$infile的前十个字节的十六进制值,并将其余数据存储到$outfile中。
declare -p first10

declare -a first10=([0]="25" [1]="50" [2]="44" [3]="46" [4]="2D" [5]="31" [6]="2E"
[7]="34" [8]="0A" [9]="25")

这是一个PDF文件(%PDF -> 25 50 44 46)... 这里有另一个示例:
{
    first10=()
    for i in {0..9};do
        read8 first10[i] || break
    done
    cat
} <<<"Hello world!"
d!

由于我没有重定向输出,字符串d!将在终端上输出。
echo ${first10[@]}
48 65 6C 6C 6F 20 77 6F 72 6C

printf '%b%b%b%b%b%b%b%b%b%b\n' ${first10[@]/#/\\x}
Hello worl

关于二进制

您说:

这些是二进制文件,前10个字节很可能会有\0\n

{
    first10=()
    for i in {0..9};do
        read8 first10[i] || break
    done
    cat
} < <(gzip <<<"Hello world!") >/dev/null 

echo ${first10[@]}
1F 8B 08 00 00 00 00 00 00 03

作为一个函数
read8() { local _r8_var=${1:-OUTBIN} _r8_car LANG=C IFS=
    read -r -d '' -n 1 _r8_car || { printf -v $_r8_var '';return 1;}
    printf -v $_r8_var %02X "'"$_r8_car ;}
get10() {
    local -n result=${1:-first10}     # 1st arg is array name
    local -i _i
    result=()
    for ((_i=0;_i<${2:-10};_i++));do  # 2nd arg is number of bytes
        read8 result[_i] || { unset result[_i] ; return 1 ;}
    done
    cat
}

然后(在这里,我使用特殊字符表示:没有换行符)。
get10 pdf 4 <$infile >$outfile
printf %b ${pdf[@]/#/\\x}
%PDF⛶

echo $(( $(stat -c %s $infile) - $(stat -c %s $outfile) ))
4

get10 test 8 <<<'Hello world'
rld!

printf %b ${test[@]/#/\\x}
Hello Wo⛶

get10 test 24 <<<'Hello World!'
printf %b ${test[@]/#/\\x}
Hello World!

最后一个打印的字符是\n!;)

最终二进制演示:

get10 test 256 < <(gzip <<<'Hello world!')

printf '%b' ${test[@]/#/\\x} | gunzip 
Hello world!

printf "  %s %s %s %s  %s %s %s %s    %s %s %s %s  %s %s %s %s\n" ${test[@]}
  1F 8B 08 00  00 00 00 00    00 03 F3 48  CD C9 C9 57
  28 CF 2F CA  49 51 E4 02    00 41 E4 A9  B2 0D 00 00
  00                    

注意!当要读取的字节数较少时,这个方法很有效且速度很快,即使处理大文件也是如此。这可以用于文件识别等方面。但是对于将文件分割成更大的部分,您必须使用split、head、tail和/或dd等工具。

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