Como posso converter dados de texto de dois valores para binário (representação de bits)

4

Eu tenho um arquivo de texto com two (2) apenas caracteres possíveis (e talvez novas linhas \n ). Exemplo:

ABBBAAAABBBBBABBABBBABBB

(tamanho 24 bytes )

Como posso converter isso em um arquivo binário, o que significa uma representação de bits, com cada um dos dois valores possíveis sendo atribuído a 0 ou 1 ?

Arquivo binário resultante ( 0=A , 1=B ):

011100001111101101110111     # 24 bits - not 24 ASCII characters

Arquivo resultante em Hex:

70FB77                       # 3 bytes - not 6 ASCII characters

Eu estaria mais interessado em uma solução de linha de comando (talvez dd , xxd , od , tr , printf , bc ). Além disso, em relação ao inverso: como recuperar o original?

    
por henfiber 25.06.2015 / 17:57

4 respostas

5

Outro perl:

perl -pe 'BEGIN { binmode \*STDOUT } chomp; tr/AB/
$ echo ABBBAAAABBBBBABBABBBABBB | \
    perl -pe 'BEGIN { binmode \*STDOUT } chomp; tr/AB/
#!/usr/bin/env perl

binmode \*STDIN;

while ( defined ( $_ = getc ) ) {
    $_ = unpack "B*";
    tr/01/AB/;
    print;
    print "\n" if ( not ++$cnt % 3 );
}
print "\n" if ( $cnt % 3 );
/; $_ = pack "B*", $_' | \ od -tx1 0000000 70 fb 77 0000003
/; $_ = pack "B*", $_'

Prova:

perl -pe 'BEGIN { $/ = ; $\ = "\n"; binmode \*STDIN } $_ = unpack "B*"; tr/01/AB/'

A leitura acima lê uma linha por vez. Cabe a você garantir que as linhas sejam exatamente o que deveriam ser.

Editar: a operação inversa:

perl -pe 'BEGIN { binmode \*STDOUT } chomp; tr/AB/
$ echo ABBBAAAABBBBBABBABBBABBB | \
    perl -pe 'BEGIN { binmode \*STDOUT } chomp; tr/AB/
#!/usr/bin/env perl

binmode \*STDIN;

while ( defined ( $_ = getc ) ) {
    $_ = unpack "B*";
    tr/01/AB/;
    print;
    print "\n" if ( not ++$cnt % 3 );
}
print "\n" if ( $cnt % 3 );
/; $_ = pack "B*", $_' | \ od -tx1 0000000 70 fb 77 0000003
/; $_ = pack "B*", $_'

Isto lê um byte de entrada de cada vez.

Editar 2: Operação reversa mais simples:

perl -pe 'BEGIN { $/ = ; $\ = "\n"; binmode \*STDIN } $_ = unpack "B*"; tr/01/AB/'

O texto acima lê 3 bytes por vez a partir de STDIN (mas receber EOF no meio de uma sequência não é um problema fatal).

    
por 25.06.2015 / 19:16
3
{   printf '2i[q]sq[?z0=qPl?x]s?l?x'
    tr -dc AB | tr AB 01 | fold -b24
}   <infile   | dc

Ao fazer a seguinte declaração, @ lcd047 acertou muito bem meu estado anterior de confusão:

You seem to be confused by the output of od. Use od -tx1 to look at bytes. od -x reads words, and on little endian machines that swaps bytes. I didn't follow closely the exchange above, but I think your initial version was correct, and you don't need to mess with byte order at all. Just use od -tx1, not od -x.

Agora isso me faz sentir muito melhor - a necessidade anterior de dd conv=swab estava me incomodando o dia todo. Eu não sabia, mas sabia que havia algo de errado com isso. Ser capaz de explicar isso em minha própria estupidez é muito reconfortante - especialmente desde que aprendi algo.

De qualquer forma, isso excluirá cada byte que não seja [AB] , então tr os levará para [01] , antes de fold ing o fluxo resultante em 24 bytes por linha. dc ? lê uma linha por vez, verifica se a entrada continha alguma coisa e, em caso afirmativo, P reimera o valor do byte desse número para stdout.

De man dc :

  • P

    • Retira o valor no topo da pilha. Se for uma string, é simplesmente impressa sem uma nova linha. Caso contrário, é um número e a parte inteira de seu valor absoluto é impressa como um fluxo de bytes "base ( UCHAR_MAX+1 )" .
  • i

    • Abre o valor do topo da pilha e usa-o para definir a base de entrada.

alguma automação de shell

Aqui está uma função de shell que eu escrevi com base no acima, que pode ir nos dois sentidos:

ABdc()( HOME=/dev/null  A='[fc[fc]]sp[100000000o]p2o[fc]' B=2i
        case    $1      in
        (-B) {  echo "$B"; tr AB 01      | paste -dP - ~      ; }| dc;;
        (-A) {  echo "$A"; od -vAn -tu1  | paste -dlpx - ~ ~ ~; }| dc|
         dc  |  paste - - - ~            | expand -t10,20,30     |
                cut -c2-9,12-19,22-29    | tr ' 01' AAB         ;;
        (*)     set '' "$1";: ${1:?Invalid opt: "'$2'"}         ;;
        esac
)

Isso irá traduzir o material ABABABA para bytes com -B , então você pode fazer:

ABdc -B <infile

Mas ele converterá entradas arbitrárias em 24 ABABABA strings codificadas bit por perito - da mesma forma que a apresentada, por exemplo, na pergunta - w / -B .

seq 5 | ABdc -A | tee /dev/fd/2 | ABdc -B

AABBAAABAAAABABAAABBAABA
AAAABABAAABBAABBAAAABABA
AABBABAAAAAABABAAABBABAB
AAAABABAAAAAAAAAAAAAAAAA
1
2
3
4
5

Para a saída -A , eu rolei em cut , expand e od aqui, o que ocorrerei em um minuto, mas também adicionei outro dc . Lancei o script line-for-line ? read dc para outro método que trabalha uma matriz no momento com f - que é um comando que imprime a pilha de comandos f ull dc para stdout. Obviamente, porque dc é um tipo de aplicativo last-in, first-out orientado a pilha, isso significa que o f ull-stack é exibido na ordem inversa em que entrou.

Isso pode ser um problema, mas eu uso outro dc com um% radixo utput definido para 100000000 para manipular todo o 0-padding da maneira mais simples possível. E quando ele lê o fluxo last-in-first-out do outro, ele aplica essa lógica a tudo de novo, e tudo sai na lavagem. Os dois dc s trabalham em sintonia assim:

{   echo '[fc[fc]]sp[100000000o]p2o[fc]'
    echo some data | 
    od -An -tu1        ###arbitrary input to unsigned decimal ints
    echo lpx           ###load macro stored in p and execute
} | tee /dev/fd/2  |   ###just using tee to show stream stages
dc| tee /dev/fd/2  |dc 

... o fluxo pelo primeiro tee ...

[fc[fc]]sp[100000000o]pc2o[fc]            ###dc's init cmd from 1st echo
 115 111 109 101  32 100  97 116  97  10  ###od's output
lpx                                       ###load p; execute

... pelo segundo tee , conforme escrito de dc to dc ...

100000000o                             ###first set output radix
1010                                   ###bin/rev vs of od's out
1100001                                ###dc #2 reads it in, revs and pads it 
1110100                                
1100001
1100100
100000
1100101
1101101
1101111                                ###this whole process is repeated
1110011                                ###once per od output line, so
fc                                     ###each worked array is 16 bytes.

... e a saída que o segundo dc escreve é ...

 01110011
 01101111
 01101101
 01100101
 00100000
 01100100
 01100001
 01110100
 01100001
 00001010

A partir daí, a função paste s está em < tabs > ...

 01110011    01101111    01101101
 01100101    00100000    01100100
 01100001    01110100    01100001
 00001010

... expand s < guias > para espaços em intervalos de 10 colunas ...

 01110011  01101111  01101101
 01100101  00100000  01100100
 01100001  01110100  01100001
 00001010

... cut s afastado todos, mas bytes 2-9,12-19,22-29 ...

011100110110111101101101
011001010010000001100100
011000010111010001100001
00001010

... e tr anslates < espaços > e zeros para A e para B ...

ABBBAABBABBABBBBABBABBAB
ABBAABABAABAAAAAABBAABAA
ABBAAAABABBBABAAABBAAAAB
AAAABABAAAAAAAAAAAAAAAAA

Você pode ver na última linha minha principal motivação para incluir expand - é um filtro tão leve, e garante com facilidade que todas as seqüências gravadas - mesmo as últimas - sejam preenchidas com 24 bits codificados. Quando esse processo é invertido e as sequências são decodificadas para -B yte-value, há duas NULs anexadas:

ABdc -B <<\IN | od -tc
ABBBAABBABBABBBBABBABBAB
ABBAABABAABAAAAAABBAABAA
ABBAAAABABBBABAAABBAAAAB
AAAABABAAAAAAAAAAAAAAAAA
IN

... como você pode ver ...

0000000   s   o   m   e       d   a   t   a  \n  
{                            ###dunno why, but I often use man man
    (                        ###as a test input source
        {   man man       |  ###streamed to tee
            tee /dev/fd/3 |  ###branched to stdout
            wc -c >&2        ###and to count source bytes
        }   3>&1          |  ###the branch to stdout is here
        ABdc -A           |  ###converted to ABABABA
        tee /dev/fd/3     |  ###branched again
        ABdc -B              ###converted back to bytes
        times >&2            ###the process is timed
    ) | wc -c >&2            ###ABdc -B's output is counted
} 3>&1| wc -c                ###and so is the output of ABdc -A
man man | ABdc -A | ABdc -B
0000014

dados do mundo real

Eu brinquei com ele e tentei com alguns fluxos simples e realistas. Eu construí este pipeline elaborado para relatórios encenados ...

37595                       ###source byte count
0m0.000000s 0m0.000000s     ###shell processor time nil
0m0.720000s 0m0.250000s     ###shell children's total user, system time
37596                       ###ABdc -B output byte count
313300                      ###ABdc -A output byte count

Eu não tenho nenhuma boa base para comparação de desempenho, aqui, no entanto. Eu só posso dizer que fui levado a este teste quando estava (talvez ingenuamente) impressionado o suficiente para fazê-lo por ...

printf %s ABBBAAAABBBBBABBABBBABBB|
tee - - - - - - - -|
tee - - - - - - - - - - - - - - - |
{   printf '2i[q]sq[?z0=qPl?x]s?l?x'
    tr -dc AB | tr AB 01 | fold -b24
} | dc        | od -tx1

... que pintou minha tela de terminal com saída de man na mesma velocidade discernível que o comando não filtrado poderia fazer. A saída do teste foi ...

0000000 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000020 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000040 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000060 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000100 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000120 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000140 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000160 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000200 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000220 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000240 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000260 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000300 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000320 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000340 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000360 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000400 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000420 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000440 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000460 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000500 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000520 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000540 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000560 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000600 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70
0000620 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb
0000640 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77 70 fb 77
0000660

testes iniciais

O resto é apenas uma prova de conceito mais simples de que funciona ...

{   printf '2i[q]sq[?z0=qPl?x]s?l?x'
    tr -dc AB | tr AB 01 | fold -b24
}   <infile   | dc
ABdc()( HOME=/dev/null  A='[fc[fc]]sp[100000000o]p2o[fc]' B=2i
        case    $1      in
        (-B) {  echo "$B"; tr AB 01      | paste -dP - ~      ; }| dc;;
        (-A) {  echo "$A"; od -vAn -tu1  | paste -dlpx - ~ ~ ~; }| dc|
         dc  |  paste - - - ~            | expand -t10,20,30     |
                cut -c2-9,12-19,22-29    | tr ' 01' AAB         ;;
        (*)     set '' "$1";: ${1:?Invalid opt: "'$2'"}         ;;
        esac
)
    
por 25.06.2015 / 18:15
1

Perl:

my $len = 24;
my $str = "ABBBAAAABBBBBABBABBBABBB\n";
$str =~ s/\s//g;
(my $bin = $str) =~ y/AB/01/;
my $val = oct("0b".$bin);
printf "%s -> %s -> %X\n", $str, $bin, $val;

my ($filename, $fh) = ("temp.out");

# write the file
open $fh, '>', $filename;
print $fh pack("N", $val);      # this actually writes 4 bytes
close $fh;

# now read it, and convert back to a string:
open $fh, '<', $filename;
read $fh, my $data, 4;
close $fh;

my $new_val = unpack "N", $data;
my $new_bin = substr unpack("B32", $data), -$len;
(my $new_str = $new_bin) =~ y/01/AB/;

printf "%X -> %s -> %s\n", $new_val, $new_bin, $new_str;
ABBBAAAABBBBBABBABBBABBB -> 011100001111101101110111 -> 70FB77
70FB77 -> 011100001111101101110111 -> ABBBAAAABBBBBABBABBBABBB

Graças à resposta perfeita do lcd047, a minha se torna:

my $str = "ABBBAAAABBBBBABBABBBABBB\n";
$str =~ s/\s//g;
(my $bin = $str) =~ y/AB/01/;
printf "%s -> %s\n", $str, $bin;

my ($filename, $fh) = ("temp.out");

# write the file
open $fh, '>', $filename;
print $fh pack("B*", $bin);
close $fh;

my $size = -s $filename;
print $size, "\n";

# now read it, and convert back to a string:
open $fh, '<', $filename;
read $fh, my $data, 1024;
close $fh;

my $new_bin = unpack("B*", $data);
(my $new_str = $new_bin) =~ y/01/AB/;

printf "%s -> %s\n", $new_bin, $new_str;
ABBBAAAABBBBBABBABBBABBB -> 011100001111101101110111
3
011100001111101101110111 -> ABBBAAAABBBBBABBABBBABBB
    
por 25.06.2015 / 19:27
1

A seguinte solução é baseada apenas em xxd (uma das ferramentas mencionadas na questão), Bash e GNU sed.

Ele assume que a entrada consiste em bytes completos (grupos de oito letras), arbitrariamente separados por novas linhas.

A abordagem é:

  1. Remover todas as novas linhas.

  2. Agrupe as letras em grupos de quatro letras terminadas por espaços.

  3. Filtre esses quadrantes em dígitos hexadecimais, não separados um do outro.

  4. Agrupa pares de dígitos hexadecimais juntos, cada par em uma linha separada.

  5. Leia esses valores de bytes com um loop de shell e reconstrua um dump hexadecimal xxd compatível.

  6. Canalize em xxd -r para converter o despejo em dados hexadecimais.

Código:

#!/bin/bash

addr=0
tr -d '\n' | \
sed -e 's/..../& /g' |
sed -e 's/AAAA /0/g' \
    -e 's/AAAB /1/g' \
    -e 's/AABA /2/g' \
    -e 's/AABB /3/g' \
    -e 's/ABAA /4/g' \
    -e 's/ABAB /5/g' \
    -e 's/ABBA /6/g' \
    -e 's/ABBB /7/g' \
    -e 's/BAAA /8/g' \
    -e 's/BAAB /9/g' \
    -e 's/BABA /A/g' \
    -e 's/BABB /B/g' \
    -e 's/BBAA /C/g' \
    -e 's/BBAB /D/g' \
    -e 's/BBBA /E/g' \
    -e 's/BBBB /F/g' |
sed -e 's/../&\n/g' |
while read hexbyte ; do
  if [ $((addr % 16)) == 0 ] ; then
     printf "%08x: " $addr
  fi
  printf "%s" $hexbyte
  if [ $((addr % 16)) == 15 ] ; then
    printf "\n"
  else
    printf " "
  fi
  : $(( addr++ ))
done |
xxd -r - -

Execução de amostra modesta:

$ cat data
AAAABBB
B
ABABABAB
ABABAABBBBAABBAB

$ ./ab.sh < data > data.bin

$ xxd data.bin
0000000: 0f55 53cd                                .US.

Aqui está uma modificação do código para manipular um grupo à direita de sete ou menos bits, preenchendo-o com zeros para fazer um byte completo, para que, por exemplo, um arquivo contendo nada, mas B mapeie para 0x80 byte:

#!/bin/bash

addr=0
tr -d '\n' | \
sed -e 's/..../& /g' |
sed -e 's/AAAA /0/g' \
    -e 's/AAAB /1/g' \
    -e 's/AABA /2/g' \
    -e 's/AABB /3/g' \
    -e 's/ABAA /4/g' \
    -e 's/ABAB /5/g' \
    -e 's/ABBA /6/g' \
    -e 's/ABBB /7/g' \
    -e 's/BAAA /8/g' \
    -e 's/BAAB /9/g' \
    -e 's/BABA /A/g' \
    -e 's/BABB /B/g' \
    -e 's/BBAA /C/g' \
    -e 's/BBAB /D/g' \
    -e 's/BBBA /E/g' \
    -e 's/BBBB /F/g' \
    -e 's/AAA$/0/g' \
    -e 's/AAB$/2/g' \
    -e 's/ABA$/4/g' \
    -e 's/ABB$/6/g' \
    -e 's/BAA$/8/g' \
    -e 's/BAB$/A/g' \
    -e 's/BBA$/C/g' \
    -e 's/BBB$/E/g' \
    -e 's/AA$/0/g' \
    -e 's/AB$/4/g' \
    -e 's/BA$/8/g' \
    -e 's/BB$/C/g' \
    -e 's/A$/0/g' \
    -e 's/B$/8/g' |
sed -e 's/../&\n/g' |
sed -e 's/^.$/&0\n/' |
while read hexbyte ; do
  if [ $((addr % 16)) == 0 ] ; then
     printf "%08x: " $addr
  fi
  printf "%s" $hexbyte
  if [ $((addr % 16)) == 15 ] ; then
    printf "\n"
  else
    printf " "
  fi
  : $(( addr++ ))
done |
xxd -r - -
    
por 29.06.2015 / 23:09