我正在编写一个 bash 脚本,需要获取文件的标头(前 10 个字节),然后在另一个部分中获取除前 10 个字节之外的所有内容。这些是二进制文件,前 10 个字节可能包含
\0
和 \n
。似乎大多数实用程序都使用 ASCII 文件。完成这项任务的好方法是什么?
如前所述,要获取前 10 个字节:
head -c 10
获取除前 10 个字节之外的所有字节(至少对于 GNU
tail
):
tail -c+11
head -c 10
在这里做了正确的事。
dd
命令从二进制文件复制任意数量的字节。
dd if=infile of=outfile1 bs=10 count=1
dd if=infile of=outfile2 bs=10 skip=1
阅读SO请求:
获取文件的标头(前 10 个字节),然后在另一个部分中获取除前 10 个字节之外的所有内容。
我明白了:
如何在特定点分割文件
因为这里的所有答案都会访问同一个文件两次,而不是仅仅分割!!
使用 Un*x 的有趣之处在于,将每个整个作业视为过滤器,使用无缓冲的I/O很容易分割流。大多数标准 un*x 工具(
cat
、grep
、awk
、sed
、python
、perl
...)都用作过滤器。
head
或 dd
但单次使用{ head -c 10 >head_part; cat >tail_part;} <file
这样效率更高,因为您的文件仅被读取 1 次,前 10 个字节转到
head_part
,其余的转到 tail_part
。
注意:第二个重定向
>tail_part
也可以放置在整个列表({ ...;}
)之外...
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/
如果目标是在可用的 bash 变量中获取 前 10 个字节的值,这里有一个很好且有效的方法:
因为十个字节很少,可以避免fork到
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
(底部带有
\n
的示例;)
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
。