Faz loop através de blocos de dados binários do stdin no Bash

6

Estou procurando algo como while IFS= read -r -n $length str; do ... done , mas para dados binários. É possível fazer isso usando dd ou outras ferramentas? Existe alguma técnica para fazer com que essas ferramentas consigam ver quando o pipe - que stdin é realmente lido - está fechado e termina o loop?

Atualmente eu codifico e decodificação de dados binários e uso read , mas é tão lento .. ( base64 | while read -r -n77 str; do echo $str | base64 -d; ... done )

    
por xin 08.03.2014 / 18:19

1 resposta

5

bash não pode conter dados binários em suas variáveis. Já é ruim o suficiente processar texto com loops de shell, seria ainda pior para o processamento de dados binários. O shell é a ferramenta para executar outras ferramentas.

Observe também que o comando read buit-in lê caracteres, não bytes.

Além disso, dd faz uma read chamada de sistema, portanto, dd bs=77 count=1 não necessariamente lerá 77 bytes, especialmente se stdin for um canal (a implementação GNU de dd foi iflag=fullblock para isso).

Aqui, você deseja usar uma linguagem de programação de processamento de dados como perl :

Em perl :

perl -ne 'BEGIN{$/=}
  print "Do something with the 77 byte long <$_> record\n"'

Com o GNU awk :

LC_ALL=C awk -vRS='.{,77}' '{print "the record is in <" RT ">"}'

Se você quiser usar um shell, sua melhor opção é provavelmente zsh , que é o único que pode armazenar dados binários em suas variáveis:

while LC_ALL=C IFS= read -ru0 -k77 record; do
  print -r -- "you may only call builtins with $record
    anyway since you can't pass NUL bytes in arguments
    to an external command"
done

Se tudo o que você quer fazer é passar cada pedaço como stdin para uma nova invocação de some command , então você pode usar o GNU split e sua opção --filter :

split -b 77 --filter='some command'

--filter inicia um novo shell para avaliar some command para cada bloco. A menos que seu sh faça a otimização por si só, você pode fazer:

split -b 77 --filter='exec some command'

Para salvar um fork.

Usando dd , você poderia analisar sua saída stderr para descobrir o final da entrada. Você também precisaria do iflag=fullblock específico do GNU:

while
  {
    report=$({
      LC_ALL=C dd bs=77 iflag=fullblock count=1 2>&3 |
        some command >&4 3>&- 4>&-
    } 3>&1)
  } 4>&1
  [ "${report%%+*}" -eq 1 ]
do
  : nothing
done

Se o tamanho da entrada for múltiplo de 77, some command será executado um tempo extra com uma entrada vazia.

    
por 08.03.2014 / 19:27