No bash, como converter 8 bytes em um int não assinado (64bit LE)?

7

Como posso 'ler / interpretar' 8 bytes como unsigned int (Little Endian) ?
Talvez haja uma conversão mágica Bash-fu para isso?

UPDATE:
Parece que algo foi interligado na interpretação da minha pergunta. Aqui está um exemplo mais amplo do que estou tentando fazer.

Eu quero ler o primeiro (e último) 64k de um arquivo. Cada palavra de 8 bytes deve ser interpretada como um inteiro sem sinal Little-Endian de 64 bits. Estes inteiros devem ser usados em uma computação de hash que identifica exclusivamente o arquivo. Portanto, há muitos cálculos a fazer, ∴ a velocidade é preferida, mas não é crítica. (Por que estou fazendo isso? Porque smplayer hashes os nomes de seus arquivos .ini de mídia reproduzida, e eu quero acessar e modificar esses arquivos, então estou imitando o código C ++ do smplay no Bash.)

Uma solução que aceita uma entrada canalizada seria ótima, e provavelmente é essencial por causa da maneira como as variáveis Bash não podem lidar com \ x00.

Eu percebo que algo como isso é provavelmente mais adequado para os gostos de Python, Perl e C / C ++, mas eu não sei Python e Perl, e embora eu pudesse fazê-lo em C ++, tem sido anos desde que eu usei e estou tentando me concentrar no Bash.

Os snippets Short Perl e Python são bons. Bash é o preferido (mas não ao sacrifício da velocidade).

    
por Peter.O 14.05.2011 / 00:08

3 respostas

5

Bash é a ferramenta errada. As conchas são boas para juntar pedaços; o processamento de texto e a aritmética são fornecidos ao lado e o processamento de dados não está em sua competência.

Eu preferiria Python por Perl, porque o Python tem bignums logo de cara. Use struct.unpack para descompactar os dados.

#!/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

Aqui está a minha resposta à pergunta original. A questão revisada não tem muito a ver com o original, que foi sobre a conversão de uma seqüência de 8 bytes no inteiro de 64 bits que ela representa na ordem little-endian.

Eu não acho que o bash tenha algum recurso embutido para isso. O snippet a seguir define a para uma string que é a representação hexadecimal do número que corresponde aos bytes na string especificada na ordem big endian .

a=0x$(printf "%s" "$string" |
      od -t x1 -An |
      tr -dc '[:alnum:]')

Para a ordem little-endian, inverta a ordem dos bytes na string original. No bash, e para uma string de tamanho conhecido, você pode fazer

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:]')

Você também pode obter o endianness preferido da sua plataforma se o seu od oferecer suporte a tipos de 8 bytes.

a=0x$(printf "%s" "$string" |
      od -t x8 -An |
      tr -dc '[:alnum:]')

Se você pode fazer aritmética em $a vai depender se o seu bash suporta aritmética de 8 bytes. Mesmo que isso aconteça, ele será tratado como um valor assinado.

Como alternativa, use Perl:

a=0x$(perl -e 'print unpack "Q<", $ARGV[0]' "$string")

Se o seu perl é compilado sem suporte a números inteiros de 64 bits, você precisará quebrar os bytes.

a=0x$(perl -e 'printf "%x%08x\n", reverse unpack "L<L<", $ARGV[0]' "$string")

(Substitua < por > para big-endian ou remova-o para obter o endianness da plataforma.)

    
por 14.05.2011 / 00:39
4

O método python de Gilles é definitivamente mais rápido, mas eu achei que só iria usar essas ferramentas * * * * * * como um todo para o moinho. sobre 'bc' como qualquer outra coisa ... Ele tem muito material de inicialização, para atender arquivos de entrada com menos de 64k ... O hash é inicializado na extensão do arquivo e, em seguida, cada um dos inteiros de 64 bits é adicionado sucessivamente a ele; causando o estouro de inteiro (esperado) .. bc conseguiu fazer o truque ...

# 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+)%ouint/"; \
               echo "hash"; } |bc)
  done
  echo $hash
#

# Output:
16A6528E803325FF
    
por 16.05.2011 / 01:50
0

Isso aceita stdin e imprime os últimos 64kB do arquivo como inteiros hexadecimais de 8 bytes sem sinal na extremidade da máquina (little-endian em x86). Para imprimir os primeiros 64kB, substitua 'tail' por 'head'

tail -c $(( 1024*64 )) | xxd -ps |tr -d '\n' | while read -N16 i ; do echo 0x$i ; done

LIMITAÇÕES: a tentativa de converter a saída para decimal usando printf resultará em erros fora do intervalo

    
por 11.04.2017 / 19:22