Bash: para loop com intervalo binário mantendo o valor de controle binário

3

Digamos que eu tenha duas variáveis básicas que contêm valores binários:

a=0011 # decimal 3
b=1000 # decimal 8

Existe uma maneira de percorrer todos os valores possíveis entre $a e $b mantendo-a binária? Algo como:

for blah in $(seq $a $b) ; do
    print "Blah is: $blah"
done

Então, será exibido:

Blah is: 0011
Blah is: 0100
Blah is: 0101
Blah is: 0110
Blah is: 0111
Blah is: 1000

Eu tentei:

for blah in $(seq "$((2#$a))" "$((2#$b))") ; do

Mas então $blah se torna decimal, e eu gostaria de manter um binário (eu sempre posso transformar o decimal de volta em binário, mas isso parece um desperdício, já que eu tenho os extremos em binário)

Este código deve ser executado em um linux limitado ( OpenWRT ) que não possui obase disponível. Se a resposta é que não é possível manter o valor binário , essa é uma resposta útil também (eu posso criar uma função que converta decimal em binário sem usar obase ). Além disso, pode ser um resposta útil para pessoas que usam bash regular.

    
por BorrajaX 22.11.2013 / 04:27

4 respostas

1

seq não é interno. Também não faz parte do padrão Posix. Mas as implementações usuais de seq não têm nenhuma capacidade de sequenciar em bases diferentes de 10.

No bash, você pode especificar um intervalo como {start..finish} . No entanto, isso também não funciona em bases diferentes de 10 (embora funcione com letras: {a..f} expande para a b c d e f .

E, tanto quanto eu sei, é isso para geradores de sequência simples, o que deixa você com algumas possibilidades.

A maneira tola de fazer isso é filtrar os valores não binários. Isso é simples, mas extremamente ineficiente se a e b não forem minúsculos:

for x in $(seq -w $a $b); do
  if [[ ! ($x =~ [2-9]) ]]; then
    echo $x
  fi
done

Aqui está uma solução melhor. Assumindo que a e b tenham o mesmo tamanho (se não, você pode usar printf para corrigir isso), o seguinte irá percorrer todos os números binários de a para b, inclusive:

# We need a string of 0s at least as long as a:
z=${a//1/0}
while [[ ! ($a > $b) ]]; do
  # do something with $a
  # The following "increments" a by removing the last 0 (and trailing 1s)
  # and replacing that with a 1 and the same number of 0s.
  a=$(printf "%.*s" ${#a} ${a%0*}1$z)
done
    
por 22.11.2013 / 08:14
3

Você está perto disso:

for blah in $(seq "$((2#$a))" "$((2#$b))") ; do

Você só precisa converter os valores decimais do loop for de volta para o binário com algo como dc .

Exemplo

$ for blah in $(seq "$((2#0011))" "$((2#1000))"); do \
     printf "%04d\n" $(echo "obase=2;$blah" | bc);done
0011
0100
0101
0110
0111
1000

O printf está sendo usado para controlar a saída de forma que ela seja preenchida com zeros à esquerda e formatada para uma determinada largura. Os argumentos, %04d , é o que especifica a saída.

A outra chave inserida neste comando é o uso de bc - uma calculadora de linha de comando. Este comando por exemplo:

echo "obase=2;$blah" | bc

Está pegando o valor, $blah e convertendo-o para a base 2 (aka. Binary) através do uso de bc .

Sem bc ou dc

Se você estiver em um sistema limitado, de modo que nenhuma dessas ferramentas esteja presente, poderá fazer a conversão diretamente usando apenas awk e esta função no manual awk .

Exemplo

Crie um arquivo com o seguinte conteúdo, chame de dec2bin.awk .

# bits2str --- turn a byte into readable 1's and 0's

function bits2str(bits, data, mask)
{
    if (bits == 0)
        return "0"

    mask = 1
    for (; bits != 0; bits = rshift(bits, 1))
        data = (and(bits, mask) ? "1" : "0") data

    while ((length(data) % 8) != 0)
        data = "0" data

    return data
}

{
  printf("%s\n", bits2str($1))
}

Agora, use a função acima:

$ for blah in $(seq "$((2#0011))" "$((2#1000))"); do echo $blah \
     | awk -f dec2bin.awk; done
00000011
00000100
00000101
00000110
00000111
00001000
    
por 22.11.2013 / 07:58
1

É mais fácil com zsh :

for ((i=2#$a; i<=2#$b; i++)) echo $(([##2]i))

Ou com 0 preenchimento:

for ((i=2#$a; i<=2#$b; i++)) printf '%04d\n' $(([##2]i))

Caso contrário, você pode usar bc :

echo "ibase=obase=2; for (i=$a; i<=$b; i++) i" | bc

Ou dc :

echo "2doi $a [p1+d$b!<a]dsax" | dc

Para 0-pad, você sempre pode enviar a saída para:

sed 's/^/000/;s/^0*\(.\{4\}\)//'
    
por 22.11.2013 / 11:22
0

Este método faz muito processamento, portanto não é bom para grandes intervalos.

seq -f '%04g' 0011 1000 | grep -v '[2-9]'

    
por 22.11.2013 / 11:06

Tags