Bash script para obtener valores ASCII para el alfabeto


Respuestas:


70

Defina estas dos funciones (generalmente disponibles en otros 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

77
@ dmsk80: +1. Para otros como yo que piensan que una errata: "'A"es correcta, mientras que si se utiliza "A"dirá: A: invalid number. Parece que está hecho en el lado printf (es decir, en el shell, de "'A"hecho hay 2 caracteres, ay 'a A. Esos se pasan a printf. Y en el contexto printf, se convierte al valor ascii de A, (y finalmente se imprime como decimal gracias a '%d'. Úselo 'Ox%x'para mostrarlo en hexa o '0%o'para tenerlo en octal))
Olivier Dulac

3
-1 para no explicar cómo funciona ... en broma: D, pero en serio ¿Qué hacen estos printf "\\$(printf '%03o' "$1")", '%03o', LC_CTYPE=Cy la comilla simple en "'$1"Do?
razzak

1
Lea todos los detalles en la Pregunta frecuente 71 . Un excelente análisis detallado.

19

Puedes ver todo el conjunto con:

$ man ascii

Obtendrá tablas en octal, hexadecimal y decimal.


También hay un paquete ascii para distribuciones basadas en Debian, pero (al menos ahora) la pregunta está etiquetada como bash, por lo que no ayudaría al OP. De hecho, está instalado en mi sistema y todo lo que obtengo de man ascii es su página de manual.
Joe

12

Si desea extenderlo a los caracteres UTF-8:

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

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

Con bash, ksho zshlas órdenes internas:

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

¿Tenía la intención de poner un carácter de cuadro cuadrado o de lo contrario el carácter original no se muestra en la publicación y se reemplaza por un carácter de cuadro cuadrado?
mtk

1
@mtk, necesita un navegador que muestre UTF-8 y una fuente que tenga ese carácter 128520 .
Stéphane Chazelas

Estoy en el último Chrome y no creo que no sea compatible con UTF-8. ¿Te gustaría saber en qué navegador estás?
mtk

@mtk, iceweaselen Debian sid. La fuente confirmada por la consola web de iceweasel es "DejaVu Sans" y tengo instalados los paquetes ttf-dejavu ttf-dejavu-core ttf-dejavu-extra que provienen de Debian con upstream en dejavu-fonts.org
Stéphane Chazelas

¿Cuál es la base de 128520? mi ctbl()me parece activar adecuadamente para mostrarla, y para cortar el carbón de leña de la cabeza de una cadena con printf, sino que pone 4*((o1=360)>=(d1=240)|(o2=237)>=(d2=159)|(o3=230)>=(d3=152)|(o4=210)>=(d4=136))en $OPTARGlos valores de bytes.
mikeserv

12

Esto funciona bien

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.

exactamente equivalente a:

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

3
¿Puedes quizás agregar una pequeña explicación?
Bernhard

tr para eliminar "\ n" (nueva línea) de la entrada. od se usa para -t dC es imprimir en caracteres decimales.
Saravanan

1
echo -nsuprime la nueva línea final eliminando la necesidad detr -d "\n"
Gowtham

2
@Gowtham, solo con algunas implementaciones de echo, no en ecos compatibles con Unix, por ejemplo. printf %s ASería el portátil.
Stéphane Chazelas

6

Voy por la solución Bash simple (y elegante?):

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

En un script puedes usar lo siguiente:

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

Observe la comilla simple antes de CharValue. Está obligado ...


1
¿En qué se diferencia su respuesta de la respuesta de dsmsk80?
Bernhard

1
Mi interpretación de la pregunta es "cómo obtener los valores ASCII para los valores del alfabeto". No cómo definir una función para recuperar el valor ASCII para un carácter. Entonces, mi primera respuesta es un comando corto de una línea para obtener los valores ASCII para el alfabeto.
phulstaert

Entiendo tu punto, pero sigo pensando que el resultado final de ambas respuestas es printf "%d".
Bernhard

2
Estoy de acuerdo en que esta es una parte crucial del proceso para llegar al resultado, sin embargo, no quería suponer que xmpirate sabía sobre el "for i in" y el uso de un rango. Si él quisiera una lista, esto podría ser un verdadero ahorro de tiempo ;-). Además, los futuros lectores pueden encontrar útiles mis adiciones.
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))"
}'

El primero ctbl(), en la parte superior, solo se ejecuta una vez. Genera el siguiente resultado (que se ha filtrado a través sed -n lde la capacidad de impresión) :

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 son todos bytes de 8 bits (menos NUL) , divididos en cuatro cadenas entre comillas divididas de manera uniforme en los límites de 64 bytes. Las cuerdas pueden ser representados con rangos octales gusta \200\1-\77, \100-\177, \200-\277, \300-\377, donde byte 128 se utiliza como un lugar-soporte para NUL.

El primer ctbl()propósito de la existencia del primero es generar esas cadenas para que evalpuedan definir la segunda ctbl()función con ellas literalmente incrustadas a partir de entonces. De ese modo, se puede hacer referencia a ellos en la función sin necesidad de generarlos nuevamente cada vez que se necesiten. Cuando evaldefine la segunda ctbl()función, la primera dejará de ser.

La mitad superior de la segunda ctbl()función es principalmente auxiliar aquí: está diseñada para serializar de forma portátil y segura cualquier estado de shell actual que pueda afectar cuando se llama. El bucle superior citará las comillas en los valores de cualquier variable que quiera usar, y luego apilará todos los resultados en sus parámetros posicionales.

Sin embargo, las dos primeras líneas devuelven inmediatamente 0 y se establecen $OPTARGen el mismo si el primer argumento de la función no contiene al menos un carácter. Y si lo hace, la segunda línea trunca inmediatamente su primer argumento solo a su primer carácter, porque la función solo maneja un carácter a la vez. Es importante destacar que hace esto en el contexto local actual, lo que significa que si un carácter puede comprender más de un byte, entonces, siempre que el shell admita caracteres de varios bytes, no descartará ningún byte excepto aquellos que no están en el primer personaje de su primer argumento.

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

Luego realiza el ciclo de guardar si es necesario, y luego redefine el contexto de la configuración regional actual a la configuración regional C para cada categoría mediante la asignación a la LC_ALLvariable. A partir de este momento, un carácter solo puede consistir en un solo byte, por lo que si hubiera varios bytes en el primer carácter de su primer argumento, ahora estos deberían ser direccionables como caracteres individuales por derecho propio.

        LC_ALL=C

Es por esta razón que la segunda mitad de la función es un while bucle , a diferencia de una secuencia de ejecución individual. En la mayoría de los casos, probablemente se ejecutará solo una vez por llamada, pero, si el shell en el que ctbl()se define correctamente maneja los caracteres de varios bytes, podría repetirse.

        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)"'     "$@"

Tenga en cuenta que la anterior $(ctbl)sustitución comando sólo se evalúa una vez cada vez - por evalcuando la función se define inicialmente - y que siempre después de que el distintivo se sustituye por la salida literal de que la sustitución de comandos como guarda en la memoria de la cáscara. Lo mismo es cierto para las dos casesustituciones de comando de patrón. Esta función nunca llama a un subshell ni a ningún otro comando. Tampoco intentará leer o escribir entradas / salidas (excepto en el caso de algún mensaje de diagnóstico de shell, que probablemente indica un error) .

También tenga en cuenta que la prueba de continuidad del bucle no es simplemente [ -n "$a" ], porque, como descubrí para mi frustración, por alguna razón un bashshell lo hace:

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

but it's not null!

... y entonces comparo explícitamente $alen a 0 para cada iteración, que, también inexplicablemente, se comporta de manera diferente (léase: correctamente) .

Los casecheques el primer byte para su inclusión en cualquiera de nuestros cuatro cuerdas y almacena una referencia al conjunto del byte $b. Luego, los primeros cuatro parámetros posicionales del shell son setlas cadenas incrustadas evaly escritas por ctbl()el predecesor.

A continuación, lo que queda del primer argumento se vuelve a truncar temporalmente a su primer carácter, que ahora debe asegurarse de que sea un solo byte. Este primer byte se utiliza como una referencia a la tira de la cola de la cadena de la que corresponde y la referencia en $bestá eval'd para representar un parámetro de posición de modo de todo, desde el byte de referencia al último byte en la cadena puede ser sustituido de distancia. Las otras tres cadenas se eliminan completamente de los parámetros posicionales.

               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)))|"

En este punto, el valor del byte (módulo 64) puede referenciarse como el len de la cadena:

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

4

Luego se hace un poco de matemática para conciliar el módulo en función del valor $b, el primer byte $ase elimina permanentemente y la salida para el ciclo actual se agrega a una pila hasta que se complete antes de que el ciclo se recicle para verificar si $aestá realmente vacío.

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

Cuando $adefinitivamente está vacío, todos los nombres y estados, con la excepción de $OPTARGque la función afectada a lo largo de su ejecución se restaura a su estado anterior, ya sea establecido y no nulo, establecido y nulo o sin establecer, y la salida se guarda a $OPTARGcomo vuelve la función. El valor de retorno real es uno menos que el número total de bytes en el primer carácter de su primer argumento, por lo que cualquier carácter de un solo byte devuelve cero y cualquier carácter de varios bytes devolverá más de cero, y su formato de salida es un poco extraño.

El valor ctbl()se guarda en $OPTARGuna cáscara de expresión aritmética válido que, si se evalúa, fijará al mismo tiempo los nombres de variables de las formas $o1, $d1, $o2, $d2al decimal y los valores octales de todos los bytes respectivos en el primer carácter de su primer argumento, pero en última instancia evaluar al total número de bytes en su primer argumento. Tenía un tipo específico de flujo de trabajo en mente al escribir esto, y creo que tal vez sea necesario hacer una demostración.

A menudo encuentro una razón para separar una cadena con getoptslike:

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

Probablemente haga un poco más que imprimirlo en un carácter por línea, pero todo es posible. En cualquier caso, todavía no he encontrado una getoptsque va a hacer correctamente (huelga que - dashEs getoptslo hace charla por char, pero bashdefinitivamente no lo hace) :

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

Okay. Entonces intenté ...

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

Ese tipo de flujo de trabajo, el byte para byte / char para char kind, es uno en el que a menudo me meto cuando hago cosas de tty. En la vanguardia de la entrada, debe conocer los valores de caracteres tan pronto como los lea, y necesita sus tamaños (especialmente al contar columnas) , y necesita que los caracteres sean caracteres enteros .

Y ahora tengo 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   ::  ş

Tenga en cuenta que en ctbl()realidad no define las $[od][12...]variables, nunca tiene un efecto duradero en ningún estado $OPTARG, pero solo coloca la cadena $OPTARGque se puede usar para definirlas, que es la forma en que obtengo la segunda copia de cada carácter anterior printf "\\$o1\\$o2"porque se establecen cada vez que evalúo $(($OPTARG)). Pero cuando lo hago también estoy declarando un modificador de la longitud del campo de printf's %sformato argumento de cadena, y debido a la expresión da siempre como resultado el número total de bytes de un personaje, me sale todo el carácter en la salida cuando lo haga:

printf %.2s "$str"

¡Debes competir en un concurso de código de bash ofuscado!
Hola

1
@HelloGoodbye, este no es el código bash . ni es esto ofuscado. para ver la ofuscación, consulte [ "$(printf \\1)" ]|| ! echo but its not null!mientras tanto, siéntase libre de familiarizarse mejor con la práctica de comentarios significativos, a menos que recomiende un concurso real de este tipo ...?
mikeserv

No, no lo sé, lo que escribí fue solo otra forma de decir que su código es muy confuso (al menos para mí), pero tal vez no se suponía que fuera fácilmente comprensible. Si no es bash, entonces, ¿qué idioma es?
Hola

@HelloGoodbye: este es el shlenguaje de comando POSIX . bashes un bourne nuevamente superior de lo mismo, y en gran parte un motivador precipitado para gran parte de la atención brindada anteriormente hacia tamaños de caracteres honorables de gran tamaño, portátiles y autoexpandibles de cualquier tipo. bashya debería manejar gran parte de esto, pero el clenguaje printfera, y tal vez es, deficiente la capacidad proporcionada anteriormente.
mikeserv

Todavía estoy inclinado a usar printf "% d" "'$ char" por simplicidad y legibilidad. Tengo curiosidad por saber qué tipo de problemas me expone a las soluciones de esa @ mikeserv. ¿Hay algo más que algunos caracteres de control que afectan el código de retorno (que creo que fue su punto en el comentario anterior)?
Alex Jansen

3

No es un script de shell, pero funciona

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

Salida de muestra

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
  • seleccione el símbolo, luego presione CTRL + C
  • abierto konsole
  • y escriba: xxd<press enter>
  • entonces presione <SHIFT+INSERT><CTRL+D>

obtienes algo como:

mariank@dd903c5n1 ~ $ xxd
û0000000: fb 

sabes que el símbolo que pegaste tiene código hexadecimal 0xfb

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.