Skrypt Bash, aby uzyskać wartości ASCII dla alfabetu


Odpowiedzi:


70

Zdefiniuj te dwie funkcje (zwykle dostępne w innych językach):

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

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

Stosowanie:

chr 65
A

ord A
65

7
@ dmsk80: +1. Dla innych, takich jak ja, którzy uważają, że spot literówkę: "'A"jest prawidłowa, natomiast jeśli używasz "A"powie: A: invalid number. Wygląda na to, że zostało to zrobione po stronie printf (tzn. W powłoce "'A"jest rzeczywiście 2 znaki, a 'i a A. Są one przekazywane do printf. A w kontekście printf jest konwertowane na wartość ascii A, (i w końcu jest drukowane jako ułamek dziesiętny dzięki '%d'. Użyj, 'Ox%x'aby pokazać go w heksie lub '0%o'mieć go w formacie ósemkowym))
Olivier Dulac

3
-1 nie do wyjaśnienia, jak to działa ... żartuję: D, ale poważnie, co robią ci printf "\\$(printf '%03o' "$1")", '%03o', LC_CTYPE=Ca apostrof w "'$1"zrobienia?
razzak

1
Przeczytaj wszystkie szczegóły w FAQ 71 . Doskonała szczegółowa analiza.

19

Możesz zobaczyć cały zestaw za pomocą:

$ man ascii

Otrzymasz tabele ósemkowe, szesnastkowe i dziesiętne.


Istnieje również pakiet ascii dla dystrybucji opartych na Debianie, ale (przynajmniej teraz) pytanie jest oznaczone jako bash, więc nie pomogłoby to OP. W rzeczywistości jest zainstalowany w moim systemie i wszystko, co otrzymuję od man ascii, to jego strona podręcznika man.
Joe

12

Jeśli chcesz rozszerzyć go na znaki UTF-8:

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

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

Z bash, kshlub zshbuiltins:

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

Czy zamierzasz umieścić znak kwadratowego pola, czy oryginalny znak nie jest wyświetlany w poście i jest zastępowany znakiem kwadratowym.
mtk

1
@mtk, Potrzebujesz przeglądarki, która wyświetla UTF-8 i czcionki, która ma ten znak 128520 .
Stéphane Chazelas

Korzystam z najnowszego Chrome i nie sądzę, że nie obsługuje UTF-8. Czy chcesz wiedzieć, na jakiej przeglądarce jesteś?
Mt

@mtk, iceweaselon Debian sid. Czcionką potwierdzoną przez konsolę internetową iceweasel jest „DejaVu Sans” i mam zainstalowane ttf-dejavu ttf-dejavu-core ttf-dejavu-extra, które pochodzą z Debiana z upstream na dejavu-fonts.org
Stéphane Chazelas

jaka jest podstawa 128520? mój własny ctbl()wydaje się prawidłowo umożliwi mi go wyświetlić, i pokroić char z głowy sznurku z printf, ale kładzie 4*((o1=360)>=(d1=240)|(o2=237)>=(d2=159)|(o3=230)>=(d3=152)|(o4=210)>=(d4=136))się $OPTARGna wartościach bajtowych.
mikeserv

12

To działa dobrze,

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.

dokładnie odpowiada:

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

3
Czy możesz dodać małe wyjaśnienie?
Bernhard

tr, aby usunąć „\ n” (nowy wiersz) z wejścia. od jest używane do -t dC oznacza drukowanie w postaci dziesiętnej.
Saravanan

1
echo -ntłumi końcowe znaki nowej linii, eliminując potrzebętr -d "\n"
Gowtham,

2
@Gowtham, tylko z niektórymi implementacjami echo, nie na przykład w echach zgodnych z Uniksem. printf %s Abyłby przenośny.
Stéphane Chazelas

6

Idę po proste (i eleganckie?) Rozwiązanie Bash:

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

W skrypcie możesz użyć następujących opcji:

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

Zwróć uwagę na pojedynczy cytat przed CharValue. Jest to obowiązkowe ...


1
Czym różni się twoja odpowiedź od odpowiedzi dsmsk80?
Bernhard

1
Moja interpretacja pytania brzmi: „jak uzyskać wartości ASCII dla wartości alfabetu”. Nie jak zdefiniować funkcję pobierania wartości ASCII dla jednego znaku. Tak więc moją pierwszą odpowiedzią jest krótkie, jednowierszowe polecenie, aby uzyskać wartości ASCII dla alfabetu.
phulstaert

Rozumiem twój punkt widzenia, ale nadal myślę, że sedno obu odpowiedzi brzmi printf "%d".
Bernhard

2
Zgadzam się, że jest to kluczowa część procesu, aby dojść do wyniku, ale nie chciałem zakładać, że xmpirate wiedział o „for i in” i zastosowaniu zakresu. Gdyby chciał listę, może to być oszczędność czasu ;-). Również przyszli czytelnicy mogą uznać moje dodatki za pomocne.
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))"
}'

Pierwszy ctbl()- na górze - działa tylko raz. Generuje następujące dane wyjściowe (które zostały przefiltrowane sed -n lze względu na drukowalność) :

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

... które są 8-bitowymi bajtami (mniej NUL) , podzielonymi na cztery ciągi cytowane w powłoce, podzielone równomiernie na granicach 64-bajtowych. Łańcuchy mogą być reprezentowane ósemkowe zakresach, takich jak \200\1-\77, \100-\177, \200-\277, \300-\377, gdzie bajt 128 jest wykorzystywane jako miejsce uchwytu dla NUL.

Pierwszym ctbl()celem istnienia jest wygenerowanie tych ciągów, aby evalmożna było zdefiniować drugą ctbl()funkcję z nimi dosłownie osadzonymi później. W ten sposób można do nich odwoływać się w funkcji bez potrzeby ich ponownego generowania za każdym razem, gdy są potrzebne. Kiedy evalzdefiniuje drugą ctbl()funkcję, pierwsza przestanie być.

Górna połowa drugiej ctbl()funkcji jest tutaj głównie pomocnicza - jest zaprojektowana do przenośnego i bezpiecznego szeregowania dowolnego bieżącego stanu powłoki, na który może mieć wpływ, gdy zostanie wywołana. Górna pętla cytuje wszelkie cudzysłowy w wartościach dowolnych zmiennych, których może chcieć użyć, a następnie układa wszystkie wyniki w swoich parametrach pozycyjnych.

Pierwsze dwa wiersze jednak najpierw zwracają 0 i ustawiają $OPTARGto samo, jeśli pierwszy argument funkcji nie zawiera co najmniej jednego znaku. A jeśli tak, druga linia natychmiast obcina pierwszy argument tylko do pierwszego znaku - ponieważ funkcja obsługuje tylko znak naraz. Co ważne, robi to w bieżącym kontekście ustawień regionalnych, co oznacza, że ​​jeśli znak może zawierać więcej niż jeden bajt, to pod warunkiem, że powłoka poprawnie obsługuje znaki wielobajtowe, nie odrzuci żadnych bajtów oprócz tych, które nie znajdują się w pierwszy znak pierwszego argumentu.

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

Następnie wykonuje pętlę składowania, jeśli to konieczne, a następnie redefiniuje bieżący kontekst ustawień regionalnych na ustawienia regionalne C dla każdej kategorii, przypisując LC_ALLzmienną. Od tego momentu znak może składać się tylko z jednego bajtu, więc jeśli pierwszy znak pierwszego argumentu zawierał wiele bajtów, każdy z nich powinien być teraz adresowalny jako osobny znak.

        LC_ALL=C

Z tego powodu druga połowa funkcji jest while pętlą , a nie pojedynczą sekwencją. W większości przypadków prawdopodobnie wykona się tylko raz na wywołanie, ale jeśli ctbl()zdefiniowana powłoka poprawnie obsługuje znaki wielobajtowe, może zapętlić się.

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

Zauważ, że powyższe $(ctbl)podstawienie polecenia jest oceniane tylko raz - do evalmomentu, gdy funkcja jest wstępnie zdefiniowana - i że na zawsze po tym token zostanie zastąpiony dosłownym wyjściem tego podstawienia polecenia zapisanym w pamięci powłoki. To samo dotyczy dwóch casepodstawień poleceń wzorca. Ta funkcja nigdy nie wywołuje podpowłoki ani żadnego innego polecenia. Nigdy też nie będzie próbował czytać ani zapisywać danych wejściowych / wyjściowych (z wyjątkiem niektórych komunikatów diagnostycznych powłoki - co prawdopodobnie oznacza błąd) .

Zauważ też, że test ciągłości pętli nie jest po prostu [ -n "$a" ], ponieważ, jak zauważyłem, ku mojej frustracji, z jakiegoś powodu bashpowłoka:

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

but it's not null!

... i dlatego wyraźnie porównuję $alen z wartością 0 dla każdej iteracji, która również w niewytłumaczalny sposób zachowuje się inaczej (czytaj: poprawnie) .

Te casekontrole pierwszy bajt do umieszczenia w każdym z naszych czterech strun i zapisuje odwołanie do zestawu bajt w $b. Następnie pierwsze cztery parametry pozycyjne powłoki odnoszą setsię do łańcuchów osadzonych evali zapisanych przez ctbl()poprzednika.

Następnie wszystko, co pozostało z pierwszego argumentu, jest ponownie tymczasowo obcinane do jego pierwszego znaku - który powinien być teraz zapewniony jako jeden bajt. Ten pierwszy bajt jest używane jako odniesienie do taśmy z ogona ciągu którego dopasowane i odniesienia w $bto eval„d przedstawiają pozycyjną parametr tak począwszy od bajtu odniesieniu do ostatniego bajtu łańcucha mogą być podstawione dalej. Pozostałe trzy łańcuchy są całkowicie usuwane z parametrów pozycyjnych.

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

W tym momencie wartość bajtu (modulo 64) może być określana jako długość łańcucha:

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

4

Następnie wykonuje się małą matematykę w celu uzgodnienia modułu na podstawie wartości in $b, pierwszy bajt w $ajest trwale usuwany, a dane wyjściowe dla bieżącego cyklu są dołączane do stosu w oczekiwaniu na zakończenie, zanim pętla zostanie ponownie przetworzona, aby sprawdzić, czy $arzeczywiście jest pusta.

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

Gdy $azdecydowanie jest pusty, wszystkie nazwy i stan - z wyjątkiem $OPTARG- że funkcja, której dotyczy problem w trakcie jej wykonywania, są przywracane do poprzedniego stanu - ustawionego i nie zerowego, ustawionego i zerowego lub nieuzbrojonego - a dane wyjściowe są zapisywane do $OPTARGjak funkcja powraca. Rzeczywista wartość zwracana jest o jeden mniejsza niż całkowita liczba bajtów w pierwszym znaku pierwszego argumentu - więc dowolny znak jednobajtowy zwraca zero, a dowolny znak wielobajtowy zwróci więcej niż zero - a jego format wyjściowy jest nieco dziwny.

Wartość ctbl()zapisuje się $OPTARGto poprawne wyrażenie shell arytmetyka, że jeśli oceniane będą jednocześnie ustawić nazwy zmiennych form $o1, $d1, $o2, $d2na dziesiętne i ósemkowe wartości wszystkich odpowiednich bajtów pierwszego znaku swojego pierwszego argumentu, ale ostatecznie ocenić do całości liczba bajtów w pierwszym argumencie. Pisząc to, miałem na myśli szczególny rodzaj przepływu pracy i myślę, że może warto przeprowadzić demonstrację.

Często znajduję powód, aby rozdzielić ciąg znaków, na getoptsprzykład:

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

Prawdopodobnie robię coś więcej niż tylko drukuję znak w wierszu, ale wszystko jest możliwe. W każdym razie, ja nie znaleźli jeszcze mają getopts, że będzie prawidłowo zrobić (niepotrzebne, że - dashjest getoptsto char przez char, ale bashna pewno nie robi) :

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

Dobrze. Więc próbowałem ...

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

Tego rodzaju przepływ pracy - bajt dla bajtu / char dla rodzaju char - często wchodzę podczas robienia tty. Na początku wejścia musisz znać wartości znaków, gdy tylko je przeczytasz, i potrzebujesz ich rozmiarów (szczególnie przy liczeniu kolumn) i potrzebujesz znaków, aby były to całe znaki.

I tak teraz mam 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   ::  ş

Zauważ, że ctbl()tak naprawdę nie definiuje $[od][12...]zmiennych - nigdy nie ma żadnego trwałego wpływu na żaden stan, ale $OPTARG- ale tylko umieszcza ciąg znaków, $OPTARGktóry można wykorzystać do ich zdefiniowania - w ten sposób otrzymuję drugą kopię każdego znaku powyżej, printf "\\$o1\\$o2"ponieważ są ustawiane za każdym razem, gdy oceniam $(($OPTARG)). Ale gdzie to zrobić ja też deklarowania długości pola modyfikator printf„s %sformatu argumentem ciąg, a ponieważ zawsze ocenia wyrażenie do całkowitej liczby bajtów w charakterze, mam całą postać na wyjściu kiedy zrobić:

printf %.2s "$str"

Powinieneś wziąć udział w konkursie zaciemnionego kodu bash!
HelloGoodbye,

1
@HelloGoodbye to nie jest kod bash . nie jest to również zaciemnione. aby zobaczyć zaciemnienie, zapoznaj się w [ "$(printf \\1)" ]|| ! echo but its not null!międzyczasie, nie krępuj się lepiej zapoznać ze znaczącą praktyką komentowania, chyba że polecasz taki konkurs ...?
mikeserv

Nie, nie wiem, to, co napisałem, było po prostu innym sposobem na powiedzenie, że twój kod jest bardzo mylący (przynajmniej dla mnie), ale może nie powinien być łatwy do zrozumienia. Jeśli to nie jest bash, to w jakim języku?
HelloGoodbye,

@HelloGoodbye - jest to shjęzyk poleceń POSIX . bashjest znowu bourne tego samego, a w dużej mierze wyśmienity motywator do dużej staranności, jaką daje wyżej, w kierunku szeroko przenośnych, samorozwijających się i honorowych przestrzeni postaci o dowolnym rozmiarze. bashpowinien już poradzić sobie z większością tego problemu, ale cjęzyk printfbył i być może ma niewystarczające możliwości powyżej podanych.
mikeserv

Nadal jestem skłonny używać printf „% d” „$ char” ze względu na prostotę i czytelność. Jestem ciekawy, jakie problemy to naraża mnie na adresy rozwiązania @ Mikeike? Czy istnieje więcej niż tylko kilka znaków kontrolnych wpływających na kod powrotu (co, jak sądzę, miał na myśli w powyższym komentarzu)?
Alex Jansen

3

Nie skrypt powłoki, ale działa

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

Próbka wyjściowa

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
  • wybierz symbol, a następnie naciśnij CTRL + C
  • otwarty konsole
  • i wpisz: xxd<press enter>
  • następnie wciśnij <SHIFT+INSERT><CTRL+D>

dostajesz coś takiego:

mariank@dd903c5n1 ~ $ xxd
û0000000: fb 

wiesz, że wklejony symbol ma kod szesnastkowy 0xfb

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.