Script Bash para obter valores ASCII para alfabeto


Respostas:


70

Defina estas duas funções (geralmente disponíveis em outros idiomas):

chr() {
  [ "$1" -lt 256 ] || return 1
  printf "\\$(printf '%03o' "$1")"
}

ord() {
  LC_CTYPE=C printf '%d' "'$1"
}

Uso:

chr 65
A

ord A
65

7
@ dmsk80: +1. Para outros como eu que pensam que detectar um erro de digitação: "'A"está correto enquanto que se usar "A", ele vai dizer: A: invalid number. Parece que é feito no lado printf (ou seja, no shell, "'A"é de fato 2 caracteres, a 'e a A. Esses são passados ​​para printf. E, no contexto printf, é convertido no valor ascii de A (e é finalmente impresso como decimal, graças a '%d'. Use 'Ox%x'para mostrá-lo em hexa ou '0%o'em octal))
Olivier Dulac 26/09

3
-1 por não explicar como funciona ... brincando: D, mas a sério o que fazer estes printf "\\$(printf '%03o' "$1")", '%03o', LC_CTYPE=Cea única citação em "'$1"tarefas?
Razzak

1
Leia todos os detalhes na FAQ 71 . Uma excelente análise detalhada.

19

Você pode ver o conjunto inteiro com:

$ man ascii

Você receberá tabelas em octal, hex e decimal.


Há também um pacote ascii para distribuições baseadas no debian, mas (pelo menos agora) a questão está marcada como bash, então isso não ajudaria o OP. Na verdade, ele está instalado no meu sistema e tudo o que recebo do man ascii é sua página de manual.
Joe

12

Se você deseja estendê-lo para caracteres UTF-8:

$ perl -CA -le 'print ord shift' 😈
128520

$ perl -CS -le 'print chr shift' 128520
😈

Com bash, kshou zshbuiltins:

$ printf "\U$(printf %08x 128520)\n"
😈

Você pretendia colocar um caractere de caixa quadrada ou o caractere original não está sendo exibido na postagem e está sendo substituído por um caractere de caixa quadrada.
mtk

1
@mtk, você precisa de um navegador que exiba UTF-8 e uma fonte com esse caractere 128520 .
Stéphane Chazelas

Estou no Chrome mais recente e não acho que ele não suporte UTF-8. Gostaria de saber em qual navegador você está?
mtk

@mtk, iceweaselon Debian sid. A fonte confirmada pelo console da web do iceweasel é "DejaVu Sans" e eu tenho os pacotes ttf-dejavu ttf-dejavu-core ttf-dejavu-extra instalados que vêm do Debian com upstream em dejavu-fonts.org
Stéphane Chazelas

Qual é a base de 128520? minha própria ctbl()me parece permitir corretamente para exibi-lo, e para cortar o caractere da cabeça de uma cadeia com printf, mas põe 4*((o1=360)>=(d1=240)|(o2=237)>=(d2=159)|(o3=230)>=(d3=152)|(o4=210)>=(d4=136))em $OPTARGpara os valores de byte.
mikeserv

12

Isso funciona bem,

echo "A" | tr -d "\n" | od -An -t uC

echo "A"                              ### Emit a character.
         | tr -d "\n"                 ### Remove the "newline" character.
                      | od -An -t uC  ### Use od (octal dump) to print:
                                      ### -An  means Address none
                                      ### -t  select a type
                                      ###  u  type is unsigned decimal.
                                      ###  C  of size (one) char.

exatamente equivalente a:

echo -n "A" | od -An -tuC        ### Not all shells honor the '-n'.

3
Você pode adicionar uma pequena explicação?
Bernhard

tr para remover "\ n" (nova linha) da entrada. od é usado para -t dC é para imprimir em caracteres decimais.
Saravanan

1
echo -nsuprime de arrasto nova linha eliminando a necessidade detr -d "\n"
Gowtham

2
@Gowtham, apenas com algumas implementações de echo, não em ecos compatíveis com Unix, por exemplo. printf %s Aseria o portátil.
Stéphane Chazelas

6

Estou optando pela solução Bash simples (e elegante?):

for i in {a..z}; do echo $(printf "%s %d" "$i" "'$i"); done

Para em um script você pode usar o seguinte:

CharValue="A"
AscValue=`printf "%d" "'$CharValue"

Observe a aspas simples antes do CharValue. É obrigatório ...


1
Qual é a sua resposta diferente da resposta de dsmsk80?
Bernhard

1
Minha interpretação da pergunta é "como obter os valores ASCII para os valores do alfabeto". Não é como definir uma função para recuperar o valor ASCII para um caractere. Portanto, minha primeira resposta é um comando curto de uma linha para obter os valores ASCII do alfabeto.
phulstaert

Entendi seu ponto de vista, mas ainda acho que a conclusão de ambas as respostas é printf "%d".
Bernhard

2
Concordo que essa é uma parte crucial do processo para chegar ao resultado, mas não queria assumir que o xmpirate sabia sobre o "for i in" e o uso de um intervalo. Se ele quisesse uma lista, isso poderia economizar muito tempo ;-). Além disso, futuros leitores podem achar minhas adições úteis.
phulstaert

6
ctbl()  for O                   in      0 1 2 3
        do  for o               in      0 1 2 3 4 5 6 7
                do for  _o      in      7 6 5 4 3 2 1 0
                        do      case    $((_o=(_o+=O*100+o*10)?_o:200)) in
                                (*00|*77) set   "${1:+ \"}\\$_o${1:-\"}";;
                                (140|42)  set   '\\'"\\$_o$1"           ;;
                                (*)       set   "\\$_o$1"               ;esac
                        done;   printf   "$1";   shift
                done
        done
eval '
ctbl(){
        ${1:+":"}       return "$((OPTARG=0))"
        set     "" ""   "${1%"${1#?}"}"
        for     c in    ${a+"a=$a"} ${b+"b=$b"} ${c+"c=$c"}\
                        ${LC_ALL+"LC_ALL=$LC_ALL"}
        do      while   case  $c in     (*\'\''*) ;; (*) ! \
                                 set "" "${c%%=*}='\''${c#*=}$1'\'' $2" "$3"
                        esac;do  set    "'"'\''\${c##*\'}"'$@";  c=${c%\'\''*}
        done;   done;   LC_ALL=C a=$3 c=;set "" "$2 OPTARG='\''${#a}*("
        while   [ 0 -ne "${#a}" ]
        do      case $a in      ([[:print:][:cntrl:]]*)
                        case    $a in   (['"$(printf \\1-\\77)"']*)
                                        b=0;;   (*)     b=1
                        esac;;  (['"$(  printf  \\200-\\277)"']*)
                                        b=2;;   (*)     b=3
                esac;    set    '"$(ctbl)"'     "$@"
                eval "   set    \"\${$((b+1))%"'\''"${a%"${a#?}"}"*}" "$6"'\''
                a=${a#?};set    "$((b=b*100+${#1}+${#1}/8*2)))" \
                                "$2(o$((c+=1))=$b)>=(d$c=$((0$b)))|"
        done;   eval "   unset   LC_ALL  a b c;${2%?})'\''"
        return  "$((${OPTARG%%\**}-1))"
}'

O primeiro ctbl()- no topo - apenas funciona uma vez. Ele gera a seguinte saída (que foi filtrada sed -n lpara fins de impressão) :

ctbl | sed -n l

 "\200\001\002\003\004\005\006\a\b\t$
\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\
\035\036\037 !\\"#$%&'()*+,-./0123456789:;<=>?" "@ABCDEFGHIJKLMNOPQRS\
TUVWXYZ[\\]^_\\`abcdefghijklmnopqrstuvwxyz{|}~\177" "\200\201\202\203\
\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\
\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\
\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\
\267\270\271\272\273\274\275\276\277" "\300\301\302\303\304\305\306\
\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\
\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\
\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\
\372\373\374\375\376\377"$

... que são todos os bytes de 8 bits (menos NUL) , divididos em quatro cadeias de caracteres entre aspas, divididas igualmente nos limites de 64 bytes. As cordas podem ser representados com faixas octal gosto \200\1-\77, \100-\177, \200-\277, \300-\377, na qual o byte 128 é utilizada como um espaço reservado para NUL.

O primeiro ctbl()objetivo da existência é gerar essas strings para que evalpossam definir a segunda ctbl()função com elas literalmente incorporadas posteriormente. Dessa forma, eles podem ser referenciados na função sem precisar gerá-los novamente cada vez que forem necessários. Quando evaldefine a segunda ctbl()função, a primeira deixará de existir.

A metade superior da segunda ctbl()função é principalmente auxiliar aqui - ela foi projetada para serializar com segurança e portabilidade qualquer estado atual do shell que possa afetar quando for chamado. O loop superior citará quaisquer aspas nos valores de quaisquer variáveis ​​que ele queira usar e, em seguida, empilhará todos os resultados em seus parâmetros posicionais.

As duas primeiras linhas, no entanto, retornam 0 imediatamente e são definidas $OPTARGcomo iguais se o primeiro argumento da função não contiver pelo menos um caractere. E se isso acontecer, a segunda linha imediatamente trunca seu primeiro argumento apenas para seu primeiro caractere - porque a função lida apenas com um caractere de cada vez. É importante ressaltar que isso é feito no contexto de localidade atual, o que significa que, se um caractere puder incluir mais de um byte, então, desde que o shell suporte adequadamente caracteres de vários bytes, ele não descartará nenhum bytes, exceto aqueles que não estão no primeiro caractere de seu primeiro argumento.

        ${1:+":"}       return "$((OPTARG=0))"
        set     "" ""   "${1%"${1#?}"}"

Em seguida, ele executa o loop de salvamento, se for necessário, e depois redefine o contexto de local atual para o local C para cada categoria, atribuindo à LC_ALLvariável. Desse ponto em diante, um caractere pode consistir apenas em um único byte; portanto, se houver vários bytes no primeiro caractere de seu primeiro argumento, agora eles deverão ser endereçáveis ​​como caracteres individuais.

        LC_ALL=C

É por esse motivo que a segunda metade da função é um while loop , em oposição a uma sequência de execução individual. Na maioria dos casos, provavelmente será executado apenas uma vez por chamada, mas, se o shell ctbl()definido corretamente manipular adequadamente caracteres de vários bytes, ele poderá executar um loop.

        while   [ 0 -ne "${#a}" ]
        do      case $a in      ([[:print:][:cntrl:]]*)
                        case    $a in   (['"$(printf \\1-\\77)"']*)
                                        b=0;;   (*)     b=1
                        esac;;  (['"$(  printf  \\200-\\277)"']*)
                                        b=2;;   (*)     b=3
                esac;    set    '"$(ctbl)"'     "$@"

Observe que a $(ctbl)substituição do comando acima é avaliada apenas uma vez - evalquando a função é definida inicialmente - e que para sempre após esse token é substituída pela saída literal da substituição do comando, salva na memória do shell. O mesmo vale para as duas casesubstituições de comando padrão. Essa função nunca chama um subshell ou qualquer outro comando. Também nunca tentará ler ou gravar entrada / saída (exceto no caso de alguma mensagem de diagnóstico do shell - o que provavelmente indica um erro) .

Observe também que o teste para continuidade de loop não é simplesmente [ -n "$a" ]porque, como descobri minha frustração, por algum motivo um bashshell faz:

char=$(printf \\1)
[ -n "$char" ] || echo but it\'s not null\!

but it's not null!

... e, por isso, comparo explicitamente $alen a 0 para cada iteração, que, também inexplicavelmente, se comporta de maneira diferente (leia-se: corretamente) .

Ele caseverifica o primeiro byte para inclusão em qualquer uma das quatro seqüências de caracteres e armazena uma referência ao conjunto de bytes $b. Posteriormente, os quatro primeiros parâmetros posicionais do shell são setpara as cadeias incorporadas evale escritas pelo ctbl()predecessor.

Em seguida, tudo o que resta do primeiro argumento é novamente temporariamente truncado para seu primeiro caractere - que agora deve ser garantido como um único byte. Esse primeiro byte é usado como referência para retirar a cauda da string com a qual correspondeu e a referência $bé evalpara representar um parâmetro posicional, para que tudo, desde o byte de referência até o último byte na string, possa ser substituído. As outras três strings são retiradas inteiramente dos parâmetros posicionais.

               eval "   set    \"\${$((b+1))%"'\''"${a%"${a#?}"}"*}" "$6"'\''
               a=${a#?};set    "$((b=b*100+${#1}+${#1}/8*2)))" \
                                "$2(o$((c+=1))=$b)>=(d$c=$((0$b)))|"

Nesse ponto, o valor do byte (módulo 64) pode ser referenciado como len da string:

str=$(printf '\200\1\2\3\4\5\6\7')
ref=$(printf \\4)
str=${str%"$ref"*}
echo "${#str}"

4

Um pouco de matemática é feito para reconciliar o módulo com base no valor em $b, o primeiro byte $aé permanentemente removido e a saída do ciclo atual é anexada a uma pilha pendente de conclusão antes que o loop seja reciclado para verificar se $aestá realmente vazio.

    eval "   unset   LC_ALL  a b c;${2%?})'\''"
    return  "$((${OPTARG%%\**}-1))"

Quando $adefinitivamente está vazio, todos os nomes e estados - com exceção de $OPTARG- que a função afetada ao longo de sua execução são restaurados ao estado anterior - definidos e não nulos, definidos e nulos ou não definidos - e a saída é salva para $OPTARGquando a função retornar. O valor de retorno real é um a menos do que o número total de bytes no primeiro caractere de seu primeiro argumento - portanto, qualquer caractere de byte único retorna zero e qualquer caractere de vários bytes retornará mais de zero - e seu formato de saída é um pouco estranho.

O valor ctbl()salva em $OPTARGuma expressão aritmética shell válido que, se avaliado, irá simultaneamente definir nomes de variáveis das formas $o1, $d1, $o2, $d2para decimal e os valores octais de todos os respectivos bytes no primeiro caractere de seu primeiro argumento, mas em última análise avaliar ao total número de bytes em seu primeiro argumento. Eu tinha um tipo específico de fluxo de trabalho em mente ao escrever isso e acho que talvez uma demonstração esteja em ordem.

Costumo encontrar um motivo para separar uma string com o seguinte getopts:

str=some\ string OPTIND=1
while   getopts : na  -"$str"
do      printf %s\\n "$OPTARG"
done

s
o
m
e

s
t
r
i
n
g

Provavelmente faço um pouco mais do que apenas imprimir um caractere por linha, mas tudo é possível. Em qualquer caso, eu ainda não encontrei um getoptsque vai fazer corretamente (greve que - dashé getoptso faz carbonizar por char, mas bashdefinitivamente não) :

str=ŐőŒœŔŕŖŗŘřŚśŜŝŞş  OPTIND=1
while   getopts : na  -"$str"
do      printf %s\\n "$OPTARG"
done|   od -tc

0000000 305  \n 220  \n 305  \n 221  \n 305  \n 222  \n 305  \n 223  \n
0000020 305  \n 224  \n 305  \n 225  \n 305  \n 226  \n 305  \n 227  \n
0000040 305  \n 230  \n 305  \n 231  \n 305  \n 232  \n 305  \n 233  \n
0000060 305  \n 234  \n 305  \n 235  \n 305  \n 236  \n 305  \n 237  \n
0000100

Está bem. Então eu tentei ...

str=ŐőŒœŔŕŖŗŘřŚśŜŝŞş
while   [ 0 -ne "${#str}" ]
do      printf %c\\n "$str"    #identical results for %.1s
        str=${str#?}
done|   od -tc

#dash
0000000 305  \n 220  \n 305  \n 221  \n 305  \n 222  \n 305  \n 223  \n
0000020 305  \n 224  \n 305  \n 225  \n 305  \n 226  \n 305  \n 227  \n
0000040 305  \n 230  \n 305  \n 231  \n 305  \n 232  \n 305  \n 233  \n
0000060 305  \n 234  \n 305  \n 235  \n 305  \n 236  \n 305  \n 237  \n
0000100

#bash
0000000 305  \n 305  \n 305  \n 305  \n 305  \n 305  \n 305  \n 305  \n
*
0000040

Esse tipo de fluxo de trabalho - o byte por byte / char para o tipo char - é aquele em que geralmente entro ao fazer coisas tty. Na extremidade principal da entrada, você precisa conhecer os valores de caracteres assim que os ler, e os tamanhos (principalmente ao contar colunas) , e os caracteres para serem caracteres inteiros .

E agora eu tenho ctbl():

str=ŐőŒœŔŕŖŗŘřŚśŜŝŞş
while [ 0 -ne "${#str}" ]
do    ctbl "$str"
      printf "%.$(($OPTARG))s\t::\t$OPTARG\t::\t$?\t::\t\\$o1\\$o2\n" "$str"
      str=${str#?}
done

Ő   ::  2*((o1=305)>=(d1=197)|(o2=220)>=(d2=144))   ::  1   ::  Ő
ő   ::  2*((o1=305)>=(d1=197)|(o2=221)>=(d2=145))   ::  1   ::  ő
Œ   ::  2*((o1=305)>=(d1=197)|(o2=222)>=(d2=146))   ::  1   ::  Œ
œ   ::  2*((o1=305)>=(d1=197)|(o2=223)>=(d2=147))   ::  1   ::  œ
Ŕ   ::  2*((o1=305)>=(d1=197)|(o2=224)>=(d2=148))   ::  1   ::  Ŕ
ŕ   ::  2*((o1=305)>=(d1=197)|(o2=225)>=(d2=149))   ::  1   ::  ŕ
Ŗ   ::  2*((o1=305)>=(d1=197)|(o2=226)>=(d2=150))   ::  1   ::  Ŗ
ŗ   ::  2*((o1=305)>=(d1=197)|(o2=227)>=(d2=151))   ::  1   ::  ŗ
Ř   ::  2*((o1=305)>=(d1=197)|(o2=230)>=(d2=152))   ::  1   ::  Ř
ř   ::  2*((o1=305)>=(d1=197)|(o2=231)>=(d2=153))   ::  1   ::  ř
Ś   ::  2*((o1=305)>=(d1=197)|(o2=232)>=(d2=154))   ::  1   ::  Ś
ś   ::  2*((o1=305)>=(d1=197)|(o2=233)>=(d2=155))   ::  1   ::  ś
Ŝ   ::  2*((o1=305)>=(d1=197)|(o2=234)>=(d2=156))   ::  1   ::  Ŝ
ŝ   ::  2*((o1=305)>=(d1=197)|(o2=235)>=(d2=157))   ::  1   ::  ŝ
Ş   ::  2*((o1=305)>=(d1=197)|(o2=236)>=(d2=158))   ::  1   ::  Ş
ş   ::  2*((o1=305)>=(d1=197)|(o2=237)>=(d2=159))   ::  1   ::  ş

Observe que ctbl(), na verdade, não define as $[od][12...]variáveis ​​- nunca tem efeito duradouro em nenhum estado, mas $OPTARG- apenas coloca a string $OPTARGque pode ser usada para defini-las - e é assim que obtenho a segunda cópia de cada caractere acima, fazendo isso printf "\\$o1\\$o2"porque eles são definidos toda vez que eu avalio $(($OPTARG)). Mas onde eu faço isso, também estou declarando um modificador de tamanho de campo para printfo %sformato de argumento de sequência de caracteres e, como a expressão sempre avalia o número total de bytes em um caractere, recebo o caractere inteiro na saída quando:

printf %.2s "$str"

Você deve competir em um concurso de código bash ofuscado!
HelloGoodbye

1
@HelloGoodbye este não é o código do bash . nem isso é ofuscado. para ver a ofuscação, por favor, consulte [ "$(printf \\1)" ]|| ! echo but its not null!enquanto isso , fique à vontade para se familiarizar melhor com a prática de comentários significativos, a menos que você recomende um concurso real ...?
mikeserv

Não, não sei, o que escrevi foi apenas outra maneira de dizer que seu código é muito confuso (pelo menos para mim), mas talvez não devesse ser facilmente compreensível. Se não é bash, então que idioma é esse?
HelloGoodbye

@ HelloGoodbye - esta é a shlinguagem de comando POSIX . bashé um bourne novamente supra - definido do mesmo e, em grande parte, um motivador precipitado de grande parte dos cuidados prestados acima a tamanhos de caracteres honrosos amplamente portáteis, auto-expansíveis e com espaço para nome de qualquer tipo. bashjá deve lidar com muito disso, mas o cidioma printfestava, e talvez seja, deficiente na capacidade acima fornecida.
mikeserv

Ainda estou inclinado a usar printf "% d" "'$ char" por uma questão de simplicidade e legibilidade. Estou curioso para saber que tipo de problemas isso me expõe aos endereços de solução do @ mikeserv. Há mais do que apenas alguns caracteres de controle que afetam o código de retorno (que eu acredito que foi o ponto dele no comentário acima)?
Alex Jansen

3

Não é um script de shell, mas funciona

awk 'BEGIN{for( i=97; i<=122;i++) printf "%c %d\n",i,i }'  

Saída de amostra

xieerqi:$ awk 'BEGIN{for( i=97; i<=122;i++) printf "%c %d\n",i,i }' | head -n 5                                    
a 97
b 98
c 99
d 100
e 101

2
  • selecione o símbolo e pressione CTRL + C
  • abrir konsole
  • e digite: xxd<press enter>
  • então aperte <SHIFT+INSERT><CTRL+D>

você obtém algo como:

mariank@dd903c5n1 ~ $ xxd
û0000000: fb 

você sabe que o símbolo que você colou tem código hexadecimal 0xfb

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.