shell-script-pt
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [shell-script] Re: Operação Bitwise em Bash


From: Arkanon
Subject: Re: [shell-script] Re: Operação Bitwise em Bash
Date: Sat, 19 Oct 2019 02:18:14 -0300

Salve, Meshtre Júlio! :)

Achei melhor responder à sua saudação apenas se me ocorresse algo para acrescentar à thread (escopo!)

Como o problema do Alfredo está resolvido, tomei a liberdade de divagar sobre o assunto. A análise abaixo pode parecer desnecessária para os mais pragmáticos. Peço que sobrelevem, se for o caso.

Devido à minha formação matemática, aprecio o fato do bash trabalhar nativamente com bases numéricas arbitrárias. Pena que (por enquanto?) ele faz nativamente isso apenas num sentido, o que nos obriga a recorrer ao bc, por ex, para converter de decimal para outras bases.

Um detalhe a ser mencionado é o intervalo de bases com as quais o bash trabalha nativamente:

echo $((  1#1 ))     # -bash: 1#1: base aritmética inválida (token de erro é "1#1")
echo $((  2#1 ))     # 1
echo $((  3#1 ))     # 1
echo $(( 16#1 ))     # 1
echo $(( 60#1 ))     # 1
echo $(( 70#1 ))     # -bash: 70#1: base aritmética inválida (token de erro é "70#1")
echo $(( 65#1 ))     # -bash: 65#1: base aritmética inválida (token de erro é "65#1")
echo $(( 64#1 ))     # 1

Até a base 64, portanto.

O bc também tem sua limitação:

bc <<< "obase=10  ; ibase=36; 1"     # 1
bc <<< "obase=10  ; ibase=37; 1"     # Runtime warning (func=(main), adr=13): ibase too large, set to 36      1
bc <<< "obase=100 ; ibase=36; 1"     # 01
bc <<< "obase=1000; ibase=36; 1"     # 001

Até a base 36 (de entrada), mas aparentemente "nenhuma" limitação na base de saída. Uma consequência disso é que, ao converter para bases "grandes", coisas estranhas acontecem:

bc <<< "obase=16  ; ibase=10; 16771328"     # FFE900
bc <<< "obase=17  ; ibase=10; 16771328"     # 11 13 13 11 04 12
bc <<< "obase=64  ; ibase=10; 16771328"     # 63 62 36 00
bc <<< "obase=100 ; ibase=10; 16771328"     # 16 77 13 28
bc <<< "obase=1000; ibase=10; 16771328"     # 016 771 328 (cada "dígito" é representado com seu valor decimal formatado com a quantidade de dígitos decimais do maior possível na base, 999 nesse caso)

Claramente o bc não assume caracteres para dígitos em bases de saída maiores que 16, apesar de fazê-lo para as 36 que reconhece de entrada:

bc <<< "obase=10; ibase=36; F"     # 15
bc <<< "obase=10; ibase=36; G"     # 16
bc <<< "obase=10; ibase=36; Z"     # 35
bc <<< "obase=10; ibase=36; a"     # 0
bc <<< "obase=10; ibase=36; z"     # 0

E aceita apenas A..Z como dígitos das bases 11..36, não a..z.

No bash é um pouco diferente:

echo $((16#f))     # 15
echo $((16#F))     # 15

echo $((16#g))     # -bash: 16#g: valor muito grande para esta base de numeração (token de erro é "16#g")
echo $((16#G))     # -bash: 16#G: valor muito grande para esta base de numeração (token de erro é "16#G")
                   # (evidente: um dígito não pode ter valor decimal maior ou igual à base)

echo $((17#g))     # 16
echo $((17#G))     # 16

echo $((36#g))     # 16
echo $((36#G))     # 16

echo $((36#z))     # 35
echo $((36#Z))     # 35

Até a base 36 tanto faz usar letras maiúsculas ou minúsculas como dígitos.
A partir da base 37 até a última reconhecida (64), a coisa muda de figura:

echo $((37#z))     # 35
echo $((37#Z))     # -bash: 37#Z: valor muito grande para esta base de numeração (token de erro é "37#Z")

echo $((64#z))     # 35
echo $((64#Z))     # 61

Ou seja, em bases maiores que 37, o bash assume os dígitos na sequência 0..9, a..z e A..Z.
Mas Z é apenas o dígito 61. E os dígitos 62 e 63 (na base 64)?
Poderíamos recorrer à documentação (depois do oráculo) mas, né, por favor... :-p

candidatos="@ $ % - _ = + , . : / ? [ ] { }"

Já vamos de cara deixar fora os caracteres '"!#&*;()<> . Sendo especiais para o bash, dificilmente seriam adotados como dígito.

for c in $candidatos; { echo -n "# "; ( echo $c: $((64#$c)) ); }
# @: 62
# -bash: 64#$: erro de sintaxe: operador aritmético inválido (token de erro é "$")
# -bash: 64#%: erro de sintaxe: esperava operando (token de erro é "%")
# -bash: 64#-: erro de sintaxe: esperava operando (token de erro é "-")
# _: 63
# -bash: 64#=: tentativa de atribuição para algo que não é uma variável (token de erro é "=")
# -bash: 64#+: erro de sintaxe: esperava operando (token de erro é "+")
# -bash: 64#,: erro de sintaxe: esperava operando (token de erro é ",")
# -bash: 64#.: erro de sintaxe: operador aritmético inválido (token de erro é ".")
# -bash: 64#:: erro de sintaxe na expressão (token de erro é ":")
# -bash: 64#/: erro de sintaxe: esperava operando (token de erro é "/")
# -bash: 64#?: esperava uma expressão (token de erro é "?")
# -bash: 64#[: erro de sintaxe: operador aritmético inválido (token de erro é "[")
# -bash: 64#]: erro de sintaxe: operador aritmético inválido (token de erro é "]")
# -bash: 64#{: erro de sintaxe: operador aritmético inválido (token de erro é "{")
# -bash: 64#}: erro de sintaxe: operador aritmético inválido (token de erro é "}")

E descobrimos os dígitos 62 e 63 :)

Então:

echo $((64#a))     # 10
echo $((64#z))     # 35
echo $((64#A))     # 36
echo $((64#Z))     # 61
echo $((64#@))     # 62
echo $((64#_))     # 63

Resumindo:
- o bash converte nativamente até da base 64 para decimal, mas não faz o contrário (nativamente).
- o bc converte para "qualquer" base, mas apenas até a 36 de entrada, e mesmo assim, sem adotar dígitos para as bases de saída superiores a 17. E, como se não bastasse (indignado :-p), adota dígitos diferentes do bash até a base 36: letras maiúsculas, enquanto o bash adota minúsculas.

Conclusão: não dá pra escapar da implementação do algoritmo de conversão de decimal para uma base qualquer. Ainda que seja só por diversão :D
Dá até pra pensar em mais um motivozinho: o bc é lindo e maravilhoso, mas vamos supor que queiramos contar apenas com o bash, sem depender do bc instalado (por algum motivo questionável qualquer).

Então, agora que apresentei minha desculpa esfarrapada para implementar a conversão de decimal para outra base, seguem minhas sugestões:



d=( {0..9} {a..z} {A..Z} @ _ ) # array com a sequência de dígitos conforme adotados pelo bash

# conversão de um valor [D] decimal para um valor [B] na base [b] (2 por default, <=64, limite da notação nativa do bash)
# opcional: quantidade [n] de dígitos de D na base b (completados com 0's à esquerda).

# algoritmo repetitivo
d2bl () { local D=$1 b=${2:-2} n=${3:-0} B; while ((D>0||n>0)); do B=${d[$((D%b))]}$B; D=$((D/b)); ((n--)); done; echo $B; }

# algoritmo recursivo
d2br1() { local D=$1 b=${2:-2} n=${3:-0} B; ((D>0||n>0)) && echo $($FUNCNAME $((D/b)) $b $((n-1)))${d[$((D%b))]} || echo; }

# algoritmo recursivo "enxugado". Necessário indicar os 3 parâmetros (D b n)
d2br2() { (($1>0||$3>0)) && echo $( $FUNCNAME $(($1/$2)) $2 $(($3-1)) )${d[$(($1%$2))]} || echo; }



{
  echo  $((2#111101))     # 61
  d2bl  61                # 111101
  d2bl  61 2              # 111101
  d2bl  61 2  8           # 00111101
  d2br1 61                # 111101
  d2br1 61 2              # 111101
  d2br1 61 2  8           # 00111101
 
  echo  $((16#3d))        # 61
  d2bl  61 16             # 3d
  d2bl  61 16 4           # 003d
  d2br1 61 16             # 3d
  d2br1 61 16 4           # 003d
 
  echo  $((64#_@A0))      # 16771328
  d2bl  16771328 64       # _@A0
  d2br1 16771328 64       # _@A0
  d2br2 16771328 64 0     # _@A0
  d2br2 16771328 64 6     # 00_@A0
}



Att,

Em qui, 17 de out de 2019 às 10:18, 'Julio C. Neves' address@hidden [shell-script] <address@hidden> escreveu:
 

Arkanon!!!!!! Que bom tê-lo de volta na lista!!! Nos conte como anda o LSD, pois tem tudo a ver com Shell.

A ideia era te cumprimentar, mas só para não sair do escopo da lista e já que vc falou em bc e [io]base, vou mostrar uma coisa, que por não ser muito conhecida, alguns pensam ser um erro do utilitário:

$ bc <<< "ibase=8; obase=16; 10+10"
12
$ bc <<< "obase=8; ibase=16; 10+10"
40

Isso ocorreu pq na 1a linha primeiro declaramos a input base como 8 e qdo declaramos a 2a como 16 ele já entendeu que esse 16 era na base 8, ou seja 14.

Conclusão: qdo for declarar a duas bases, declare 1o a de saída.
 
Abraços,
Julio

» Não tem tempo para fazer um curso presencial?
» Na sua cidade não tem nenhum bom curso de Linux?
» Em outubro abriremos uma semana de inscrições
» para uma nova turma. Veja mais detalhes em:

Também damos treinamento em sua empresa
em qualquer cidadecom certificado e nota fiscal.







Em qua, 16 de out de 2019 às 19:52, Arkanon address@hidden [shell-script] <address@hidden> escreveu:
 

O bash considera números iniciando com 0 como na base octal:
$  echo $(( 01010 & 01010 ))
520

Se não começar com 0, considerará na base decimal:
$  echo $(( 1010 & 1010 ))
1010

Você pode indicar a base explicitamente:
$  echo $(( 8#1010 & 8#1010 ))
520
$  echo $(( 10#01010 & 10#01010 ))
1010

Portanto, se quer que seja considerada a base binária, indique-a:
$  echo $(( 2#00001010 & 2#00001010 ))
10
$  echo $(( 2#1010 & 2#1010 ))
10

Aliás, a base hexadecimal também tem sua sintaxe específica:
$  echo $(( 0x01010 & 0x01010 ))
4112
$  echo $(( 16#01010 & 16#01010 ))
4112

Observe que o resultado é dado na base decimal. Se quiser o resultado na mesma base de entrada, pode convertê-lo. Aqui tem algumas sugestões de procedimento:

$ dec=844
$ printf '%x\n' $dec # para hexadecimal
34c
$ printf '%X\n' $dec
34C
$ printf '%o\n' $dec # para octal
1514
$ bc <<< "obase=2;$dec" # para binario
1101001100  $ dec=844
$ bc <<< "obase=21;$dec" # para base 21 -> QUALQUER BASE
01 19 04

Att,

Em qua, 16 de out de 2019 às 19:25, Alfredo Casanova address@hidden [shell-script] <address@hidden> escreveu:
 

Pelo visto ele está fazendo bitwise comparando 1.100 com 1.010.

Como fugir disso?


Em qua, 16 de out de 2019 19:07, Alfredo Casanova <address@hidden> escreveu:
Galera, to fazendo um script aqui pra detectar se uma sub-rede menos abrangente está compreendida dentro de outra mais abrangente (exemplo: saber se a rede 10.100.100.128/25 está dentro da rede 10.100.100.0/24)

E aí no meio das operacoes de bitwise me peguei com um erro que nao consigo explicar:

$ echo $(( 11111111 & 11111111))
11111111
$ echo $(( 11111111 & 11111110))
11111110

Essas duas operações estão com comportamento adequado
Porém:

$ echo $(( 00001010 & 00001010 ))
520
$ echo $(( 00001010 & 00001100 ))
512

eu não faço ideia do q está acontecendo nesses casos.
Alguém ilumina?

--
[]'s
Alfredo Tristão Casanova .͘.
Linux User #228230
tel: +55 61 9655 9619



--
(o_  @arkanon  (Twitter)     __o
//\   address@hidden   _`\<,
V_/_      www.lsd.org.br  (_)/(_)
---------------------------------



--
(o_  @arkanon  (Twitter)     __o
//\   address@hidden   _`\<,
V_/_      www.lsd.org.br  (_)/(_)
---------------------------------

reply via email to

[Prev in Thread] Current Thread [Next in Thread]