8바이트를 어떻게 "읽기/해석"합니까?서명되지 않은정수(리틀 엔디안)?
어쩌면 이것을 달성할 수 있는 Bash-fu 마법 변환이 있을까요?
업데이트:
내 질문에 대한 설명에 뭔가가 교차된 것 같습니다. 다음은 제가 하려는 작업에 대한 더 광범위한 예입니다.
파일의 첫 번째(및 마지막) 64k를 읽고 싶습니다. 각 8바이트 단어는 64비트 Little-Endian 부호 없는 정수로 해석됩니다. 이것들정수파일을 고유하게 식별하는 데 사용되는 해시 계산입니다. 따라서 많은 계산이 필요하므로 ∴ 속도가 선호되지만 중요하지는 않습니다. (내가 이 작업을 하는 이유는 무엇입니까? smplayer
재생되는 미디어 .ini 파일의 이름을 해시하고 해당 파일에 액세스하여 수정하려고 하기 때문에 Bash에서 smplayer의 C++ 코드를 흉내냅니다.)
파이프 입력을 수용하는 데 적합한 솔루션이 최적일 것이며 Bash 변수가 \x00.을 처리할 수 없기 때문에 필수적일 수 있습니다.
나는 이와 같은 것이 Python, Perl 및 C/C++와 같은 언어에 더 적합할 수 있다는 것을 알고 있지만 Python 및 Perl을 모르고 C++로 이 작업을 수행할 수 있지만 실제로는 사용하지 않았습니다. 몇 년 동안 저는 Focus on Bash를 시도하고 있습니다.
짧은 Perl 및 Python 스니펫이 좋습니다. Bash가 선호됩니다(그러나 속도가 저하되지는 않습니다).
답변1
Bash는 단순히 잘못된 도구입니다. 껍질은 조각들을 서로 붙이는 데 능숙합니다. 텍스트 처리 및 산술은 측면에서 제공되지만 데이터 처리는 단순히 해당 권한에 속하지 않습니다.
Python에는 즉시 큰 숫자가 있으므로 Perl 대신 Python을 선택하겠습니다. 사용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
지정된 문자열의 바이트에 해당하는 숫자의 16진수 표현인 문자열을 설정합니다.빅엔디안 방식주문하다.
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")
( 빅엔디안 <
으로 교체하거나 플랫폼 엔디안을 위해 제거하세요.)>
답변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을 허용하고 파일의 마지막 64kB를 시스템의 엔디안(x86에서는 리틀 엔디안)의 부호 없는 8바이트 16진수 정수로 인쇄합니다. 첫 번째 64kB를 인쇄하려면 "tail"을 "head"로 바꾸십시오.
tail -c $(( 1024*64 )) | xxd -ps |tr -d '\n' | while read -N16 i ; do echo 0x$i ; done
제한사항: printf를 사용하여 출력을 10진수로 변환하려고 하면 범위를 벗어난 오류가 발생합니다.