我如何将 8 个字节“读取/解释”为未签名整数(小端)?
也许有一个 Bash-fu 魔法转换可以实现这一点?
更新:
在我的问题的解释中似乎有些东西交叉了。这是我正在尝试做的一个更广泛的例子。
我想读取文件的第一个(和最后一个)64k。每个 8 字节字都被解释为 64 位 Little-Endian 无符号整数。这些整数将用于唯一标识文件的哈希计算。因此需要进行大量计算,∴速度是首选,但不是关键。 (我为什么这样做?因为smplayer
散列其播放媒体 .ini 文件的名称,并且我想访问和修改这些文件,所以我在 Bash 中模仿 smplayer 的 C++ 代码。)
适合接受管道输入的解决方案将是最佳的,并且可能是必不可少的,因为 Bash 变量无法处理 \x00..
我意识到这样的东西可能更适合 Python、Perl 和 C/C++ 之类的语言,但我不了解 Python 和 Perl,虽然我可以用 C++ 做到这一点,但我已经好几年没有使用过我正在尝试专注于 Bash。
简短的 Perl 和 Python 片段很好。 Bash 是首选(但不以牺牲速度为代价)。
答案1
Bash 完全是一个错误的工具。贝壳擅长将碎片粘合在一起。文本处理和算术是在旁边提供的,数据处理根本不属于他们的职权范围。
我会选择 Python 而不是 Perl,因为 Python 马上就有 bignums。使用struct.unpack
解压数据。
#!/usr/bin/env python
import os, struct, sys
fmt = "<" + "Q" * 8192
header_bytes = sys.stdin.read(65536)
header_ints = list(struct.unpack(fmt, header_bytes))
sys.stdin.seek(-65536, 2)
footer_bytes = sys.stdin.read(65536)
footer_ints = list(struct.unpack(fmt, header_bytes))
# your calculations here
这是我对原来问题的回答。修改后的问题与原始问题没有太大关系,原始问题是将一个 8 字节序列转换为其以小端顺序表示的 64 位整数。
我认为 bash 没有任何内置功能。以下代码片段设置a
为一个字符串,该字符串是与中指定字符串中的字节相对应的数字的十六进制表示形式大尾数法命令。
a=0x$(printf "%s" "$string" |
od -t x1 -An |
tr -dc '[:alnum:]')
对于小端顺序,反转原始字符串中字节的顺序。在 bash 中,对于已知长度的字符串,你可以这样做
a=0x$(printf "%s" "${string:7:1}${string:6:1}${string:5:1}${string:4:1}${string:3:1}${string:2:1}${string:1:1}${string:0:1}" |
od -t x1 -An |
tr -dc '[:alnum:]')
od
如果您支持 8 字节类型,您还可以获得平台的首选字节序。
a=0x$(printf "%s" "$string" |
od -t x8 -An |
tr -dc '[:alnum:]')
能否进行算术$a
取决于您的 bash 是否支持 8 字节算术。即使确实如此,它也会将其视为有符号值。
或者,使用 Perl:
a=0x$(perl -e 'print unpack "Q<", $ARGV[0]' "$string")
如果您的 Perl 编译时没有 64 位整数支持,则需要分解字节。
a=0x$(perl -e 'printf "%x%08x\n", reverse unpack "L<L<", $ARGV[0]' "$string")
(替换<
为>
big-endian 或将其删除以获得平台字节序。)
答案2
Gilles 的 python 方法肯定更快,但我想我只是把这个 *bash***+***std-single- Purpose-tools* 作为一般的工具扔进磨坊..它可能一样多关于“BC”和其他一样...它有很多初始化的东西,以满足小于 64k 的输入文件...散列初始化为文件的长度,然后依次将每个 64 位整数添加到其中;导致(预期的)整数溢出..bc
设法做到了这一点...
# This script reads 8196 8-byte blocks (64 KiB) from the head and tail of a file
# Each 8-bytes block is interpreted as an unsigned 64-bit Little-Endian integer.
# The head integers and tail integers ar printed to stdout; one integer per line.
#
# INIT: If the file is smaller than 64k, calculate the number of unsigned ints to read
# ====
file="$1"
flen=($(du -b "$file")) # file length
qlen=8 # ui64 length in bytes
((flen<qlen)) && exit 1 # file is too short -- exit
bmax=$((64*1024)) # byte end of read (== byte max to read)
((flen<bmax)) && ((bmax=flen)) # reduce byte max to file length
qmax=$((bmax/qlen)) # ui64 end of read (== ui64 max to read)
(((qmax*qlen)<bmax)) && ((bmax=(qmax*qlen))) # round down byte max (/8)
hash=$(echo $flen |xxd -p -u)
#
# MAIN
# ====
for skip in 0 $((flen-bmax)) ;do
hash=$(dd if="$file" bs=1 count=$bmax skip=$skip 2>/dev/null |
xxd -p -u -c 8 |
{ echo -e " ibase=16 \n obase=10 \n scale=0 \n hash=$hash \n ouint=10000000000000000 "; \
sed -re "s/(..)(..)(..)(..)(..)(..)(..)(..)/hash=(hash+\8\7\6\5\4\3\2\1)%ouint/"; \
echo "hash"; } |bc)
done
echo $hash
#
# Output:
16A6528E803325FF
答案3
它接受 stdin 并按照机器的字节序(x86 上的小字节序)将文件的最后 64kB 打印为无符号 8 字节十六进制整数。要打印前 64kB,请将“tail”替换为“head”
tail -c $(( 1024*64 )) | xxd -ps |tr -d '\n' | while read -N16 i ; do echo 0x$i ; done
限制:尝试使用 printf 将输出转换为十进制将导致超出范围的错误