Como imprimo um caractere ASCII por diferentes pontos de código no Bash?


12

Na tabela ASCII, existe o caractere 'J' que possui pontos de código em diferentes sistemas numéricos:

Oct   Dec   Hex   Char
112   74    4A    J

É possível imprimir esse caractere por um ponto de código octal, imprimindo printf '\112'ou echo $'\112'. Como imprimo o mesmo caractere em apresentações de ponto de código decimal e hexadecimal?


Respostas:




6

Em geral, o shell pode entender números hex, oct e decimais em variáveis, desde que tenham sido definidos como integers:

$ declare -i v1 v2 v3 v4 v5 v6 v7
$ v1=0112
$ v2=74
$ v3=0x4a
$ v4=8#112
$ v5=10#74
$ v6=16#4a
$ v7=18#gg
echo "$v1 $v2 $v3 $v4 $v5 $v6 $v7"
74 74 74 74 74 74 304

Ou são o resultado de uma "Expansão Aritmética":

$ : $(( v1=0112, v2=74, v3=0x4a, v4=8#112, v5=10#74, v6=16#4a, v7=18#gg ))
$ echo "$v1 $v2 $v3 $v4 $v5 $v6 $v7"
74 74 74 74 74 74 304

Portanto, você só precisa de uma maneira de imprimir o caractere que pertence a um valor variável.
Mas aqui estão duas maneiras possíveis:

$ var=$((0x65))
$ printf '%b\n' "\\$(printf '0%o' "$var")"
e

$ declare -i var
$ var=0x65; printf '%b\n' "\U$(printf '%08x' "$var")"
e

Os dois printf são necessários, um para transformar o valor em uma sequência hexadecimal e o segundo para realmente imprimir o caractere.

O segundo imprimirá qualquer ponto UNICODE (se o console estiver configurado corretamente).
Por exemplo:

$ var=0x2603; printf '%b\n' "\U$(printf '%08x' "$var")"

Um homem da neve.

O caractere que possui uma representação utf-8 como f0 9f 90 aeestá 0x1F42E. Procurar cow face site:fileformat.infopara obtê-lo :

$ var=0x1F42F; printf '%b\n' "\U$(printf '%08x' "$var")"
🐮

Nota : Há um problema com a maneira UNICODE no bash anterior a 4.3 (corrigido nessa versão e acima), os caracteres entre os pontos 128 e 255 do UNICODE (em decimal) podem ser impressos incorretamente.


Referências

Quarto parágrafo dentro PARAMETERSde man bash:

Se a variável tiver seu atributo inteiro definido, o valor será avaliado como uma expressão aritmética, mesmo que a expansão $ ((...)) não seja usada (consulte Expansão aritmética abaixo).

Dentro de "AVALIAÇÃO ARITMÉTICA" em man bash:

As constantes com um 0 inicial são interpretadas como números octais. Um 0x ou 0X inicial indica hexadecimal. Caso contrário, os números assumem a forma [base #] n, onde a base opcional é um número decimal entre 2 e 64 representando a base aritmética, e n é um número nessa base. Se o número da base for omitido, a base 10 será usada. Os dígitos maiores que 9 são representados pelas letras minúsculas, maiúsculas, @ e _, nessa ordem. Se a base for menor ou igual a 36, ​​as letras minúsculas e maiúsculas poderão ser usadas alternadamente para representar números entre 10 e 35.


@ StéphaneChazelas Bem, um ponto de código não é (sempre) um valor de byte. O Bash (nas versões anteriores à 4.3) fornece o valor de byte do ponto de código. Ou seja: o caractere é(Octal: 351, Dec: 233, Hex: 0xE9) é impresso incorretamente com printf '\351', pois imprime um valor de byte de 0xE9sempre. Para um terminal com uma codificação de ISO-8859-1(e primos) que pode funcionar, mas em terminais codificados utf-8, um valor de byte 0xE9deve aparecer como . cont ...
Isaac

@ StéphaneChazelas Não sou o primeiro a perceber e procurar por "o bash 4.2 codifica incorretamente" para um exemplo. Foi corrigido do bash 4.3 e superior.
Isaac

ESTÁ BEM. Entendo o que você quer dizer agora (eu estava testando com o 4.3 conforme a versão anterior da sua resposta). Note que apenas o bash-4.2, o bash-4.1 não suporta \u(que vem do zsh).
Stéphane Chazelas 26/09/16

5

Decimal:

chr() {
    local c
    for c
    do
        printf "\\$((c/64*100+c%64/8*10+c%8))"
    done
}

chr 74

Hex:

chr $((16#4a))

A função pode fazer sequências:

$ chr 74 75 76; echo
JKL
$

0

Você pode usar a biblioteca stdlib do POSIX Awk :

$ awklib 'BEGIN {print str_chr(74)}'
J

$ awklib 'BEGIN {print str_chr(+base_conv("4A", 16, 10))}'
J

$ awklib 'BEGIN {print str_chr(+base_conv(112, 8, 10))}'
J

$ awklib 'BEGIN {print str_chr(+base_conv(1001010, 2, 10))}'
J

0

Se você possui uma lista de números para converter e deseja evitar uma chamada de função e criar um subshell para cada caractere, é possível definir o conjunto de ascii previamente:

ascii=$(for x in {0..9} {A..F}; do for y in {0..9} {A..F}; do echo -ne "\x$x$y"; done; done)

Observe que char nulo é excluído, portanto, cada char é compensado por 1.

Em seguida, use algo como isto (assume 1 número por linha):

while read c; do out+="${ascii:$c-1:1}"; done <<< "$in"
echo "$out"

0

Aqui estão todas as conversões usando printf:

printf "%o" "'J" # 112 (oct)
printf "%d" "'J" # 74 (dec)
printf "%x" "'J" # 4a (hex)

printf '\112' # J (oct)
printf "\x$(printf %x 74)" # J (dec, requires double conversion)
printf '\x4a' # J (hex)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.