Jak echo 4-znakowego znaku Unicode w Bash?

224

Chciałbym dodać czaszkę i skrzyżowane piszczele Unicode do mojego monitu powłoki (konkretnie „CZASZKA I KRZYŻOWCE” (U + 2620)), ale nie mogę rozgryźć magicznej inkantacji, która sprawiłaby, że echo wyplułoby ją, lub jakiekolwiek inne, 4-cyfrowy znak Unicode. Dwucyfrowe są łatwe. Na przykład echo -e "\ x55",.

Oprócz poniższych odpowiedzi należy zauważyć, że oczywiście twój terminal musi obsługiwać Unicode, aby wynik był zgodny z oczekiwaniami. gnome-terminal robi to dobrze, ale niekoniecznie jest domyślnie włączony.

W aplikacji terminalowej systemu macOS przejdź do Preferencje-> Kodowania i wybierz Unicode (UTF-8).

masukomi
źródło
7
Zauważ, że twój komentarz „dwucyfrowe są łatwe (echo)” jest ważny tylko dla wartości do "\x7F"ustawień regionalnych UTF-8 (które bashznacznik sugeruje, że masz )… wzorce reprezentowane przez pojedynczy bajt nigdy nie są w zakresie \x80-\xFF. Ten zakres jest nielegalny w jednobajtowych znakach UTF-8. np. wartość Unicode Codepoint wynosząca U+0080(tj. \x80) w rzeczywistości wynosi 2 bajty w UTF-8 .. \xC2\x80..
Peter.O
4
Np printf "\\u007C\\u001C".
kenorb
Uwaga: dla mnie gnome-terminal, echo -e '\ufc'nie wywołuje u, nawet w postaci zestawu do kodowania UTF-8. Jednak np. urxvtDrukuje np. printf "\\ub07C\\ub01C"Zgodnie z oczekiwaniami (bez znaku lub ramki).
izomorfizmy
@ Peter.O Dlaczego bashtag jest tak przydatną wskazówką? Czy różne terminale są wspólne w CJK lub…?
izomorfizmy
1
@ Peter.O zsh, fish, scsh, elvish itp. ... istnieje wiele różnych powłok, każda z nich może obsługiwać znaki Unicode, jak tylko chce (lub nie). „bash” wyjaśnia, że ​​to pytanie nie dotyczy jakiejś dziwnej powłoki, która działa inaczej.
masukomi

Odpowiedzi:

237

W UTF-8 jest to właściwie 6 cyfr (lub 3 bajty).

$ printf '\xE2\x98\xA0'

Aby sprawdzić, jak jest zakodowany przez konsolę, użyj hexdump:

$ printf  | hexdump
0000000 98e2 00a0                              
0000003
vartec
źródło
5
Moje wyjścia „ ” zamiast ☠ ... Dlaczego tak jest?
trusktr
8
To prawda. Odkryłem, że używam LANG=Czamiast LANG=en_US.UTF-8. Teraz moje terminale w Gnome poprawnie wyświetlają symbole ... Prawdziwe terminale (tty1-6) wciąż tego nie robią.
trusktr
6
Dla osób próbujących zrzutu heksowego: 0000000 f0 9f 8d batłumaczy na \xf0\x9f\x8d\xba. Przykład echo: echo -e "\xf0\x9f\x8d\xba".
Blaise
8
Można również użyć $'...'składni, aby uzyskać zakodowaną postać w zmiennej bez używania $(...)powłoki w tle przechwytywania, do stosowania w sytuacjach, które same nie interpretować sekwencje ucieczki:skull=$'\xE2\x98\xA0'
Andrew Janke
7
Kolejna rzecz o hexdump: na moim komputerze drugie polecenie w wynikach odpowiedzi 0000000 98e2 00a0. Oczywiście 0000000jest to tylko nieważne przesunięcie, ale bajty po nim tłumaczą się \xe2\x98\xa0, ponieważ maszyna używa małej kolejności bajtów endian.
sigalor
98
% echo -e '\u2620'     # \u takes four hexadecimal digits

% echo -e '\U0001f602' # \U takes eight hexadecimal digits
😂

Działa to w Zsh (sprawdziłem wersję 4.3) oraz w Bash 4.2 lub nowszym.

Juliano
źródło
16
to po prostu wypluwa, kiedy to robię.
masukomi
Dla mnie też. Jakiej powłoki używasz, Juliano?
Joachim Sauer
2
Przepraszam, zapomniałem powiedzieć, że używam zsh.
Juliano,
32
Wsparcie dla \ u zostało dodane w Bash 4.2.
Lri
4
NIE działa dla mnie, Mac OS 10.14.2, bash (GNU bash, wersja 3.2.57 (1) -release (x86_64-apple-darwin18)). Po prostu wypisuje dane wejściowe - $ echo -e '\ u2620' <enter> po prostu wypisuje: \ u2620
Motti Shneor
68

Tak długo, jak edytory tekstu radzą sobie z Unicode (przypuszczalnie zakodowanym w UTF-8), możesz bezpośrednio wprowadzić kod-Unicode.

Na przykład, w edytorze tekstów Vima wchodzisz w tryb wstawiania i naciskasz Ctrl+ V+, Ua następnie liczbę kodową jako 4-cyfrową liczbę szesnastkową (w razie potrzeby z zerami). Więc wpisz Ctrl+ V+ U 2 6 2 0. Zobacz: Jaki jest najłatwiejszy sposób wstawienia znaków Unicode do dokumentu?

Na terminalu, na którym działa Bash, wpisz CTRL+ SHIFT+ Ui wpisz szesnastkowy kodowy znak żądanego znaku. Podczas wprowadzania kursor powinien pokazywać podkreślenie u. Pierwsza niecyfrowana cyfra kończy wprowadzanie i renderuje znak. Możesz więc móc wydrukować U + 2620 w Bash, korzystając z następujących czynności:

echo CTRL+ SHIFT+U2620ENTERENTER

(Pierwsze wejście kończy wejście Unicode, a drugie uruchamia echopolecenie).

Źródło: Zapytaj Ubuntu SE

RobM
źródło
1
Dobrym źródłem kodów heksademickich
RobM
1
Wersja vima, której używam (7.2.411 na RHEL 6.3) nie reaguje zgodnie z życzeniem, gdy między ctrl-v i u występuje kropka, ale działa dobrze, gdy ta kropka zostanie pominięta.
Chris Johnson
@ChrisJohnson: Usunąłem kropkę z instrukcji, nie było to zamierzone naciśnięcie klawisza (dlatego nie pojawiło się to z efektem klawiatury). Przepraszam za zamieszanie.
RobM
5
Uwaga: działa to w terminalu z uruchomioną wersją Bash, tylko jeśli używasz go w środowisku GTK + , jako Gnome.
nr
1
Możliwość C-S-u 2 6 2 0jest funkcją emulatora terminala, X Input Method (XIM) lub podobną. AFAIK, nie będzie można wysłać obu SHIFTi CTRLdo warstwy terminalowej. Terminal mówi tylko znakami, a nie kluczami i kodami kluczy, takimi jak Twój serwer X (jest również 7-bitowy do wszystkich celów i celów). W tym świecie CTRLmaskuje 4 najbardziej znaczące bity (i 0b00001111), co powoduje
nabin-info
31

Oto w pełni wewnętrzna implementacja Bash, bez rozwidlania, nieograniczony rozmiar znaków Unicode.

fast_chr() {
    local __octal
    local __char
    printf -v __octal '%03o' $1
    printf -v __char \\$__octal
    REPLY=$__char
}

function unichr {
    local c=$1    # Ordinal of char
    local l=0    # Byte ctr
    local o=63    # Ceiling
    local p=128    # Accum. bits
    local s=''    # Output string

    (( c < 0x80 )) && { fast_chr "$c"; echo -n "$REPLY"; return; }

    while (( c > o )); do
        fast_chr $(( t = 0x80 | c & 0x3f ))
        s="$REPLY$s"
        (( c >>= 6, l++, p += o+1, o>>=1 ))
    done

    fast_chr $(( t = p | c ))
    echo -n "$REPLY$s"
}

## test harness
for (( i=0x2500; i<0x2600; i++ )); do
    unichr $i
done

Wynik był:

─━│┃┄┅┆┇┈┉┊┋┌┍┎┏
┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟
┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯
┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿
╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏
═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟
╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯
╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿
▀▁▂▃▄▅▆▇█▉▊▋▌▍▎▏
▐░▒▓▔▕▖▗▘▙▚▛▜▝▞▟
■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯
▰▱▲△▴▵▶▷▸▹►▻▼▽▾▿
◀◁◂◃◄◅◆◇◈◉◊○◌◍◎●
◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟
◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯
◰◱◲◳◴◵◶◷◸◹◺◻◼◽◾◿
Orwellophile
źródło
Jestem bardzo ciekawy uzasadnienia metody round-about i konkretnego zastosowania zmiennej REPLY. Zakładam, że sprawdziłeś źródło bash lub przejrzałeś lub coś do optymalizacji, co widzę, jak twoje wybory mogą być optymalizujące, choć wysoce zależne od tłumacza).
nabin-info
14

Wystarczy wpisać „☠” w skrypcie powłoki. W prawidłowych ustawieniach narodowych i na konsoli obsługującej Unicode wydrukuje się dobrze:

$ echo 

$

Brzydkim „obejściem” byłoby wyjście z sekwencji UTF-8, ale zależy to również od zastosowanego kodowania:

$ echo -e '\xE2\x98\xA0'

$
Joachim Sauer
źródło
13

Szybka jednowierszowa konwersja znaków UTF-8 na ich 3-bajtowy format:

var="$(echo -n '☠' | od -An -tx1)"; printf '\\x%s' ${var^^}; echo
David King
źródło
5
Nie nazwałbym powyższego przykładu szybkim (z 11 poleceniami i ich parametrami) ... Również obsługuje tylko 3 bajty znaków UTF-8` (znaki UTF-8 mogą mieć 1, 2 lub 3 bajty) ... To jest nieco krótszy i działa dla 1-3 ++++ bajtów: printf "\\\x%s" $(printf '☠'|xxd -p -c1 -u).... xxd jest wysyłany jako część pakietu „vim-common”
Peter.O
PS: Właśnie zauważyłem, że powyższy przykład hexdump / awk zamienia sekwencję bajtów w parze bajtów. To nie stosuje się do UTF-8 wysypisko. Byłoby to wznowione, gdyby był zrzutem UTF-16LE i chciałby wypisywać punkty kodowe Unicode , ale nie ma to sensu, ponieważ dane wejściowe to UTF-8, a dane wyjściowe są dokładnie takie same jak dane wejściowe (plus \ x przed każdym hexdigit
-para
7
Znaki UTF-8 mogą być sekwencjami 1–4 bajtowymi
cms
1
na podstawie komentarza @ Peter.O, uważam, że następujące, choć większe, bardzo przydatne:hexFromGlyph(){ if [ "$1" == "-n" ]; then outputSeparator=' '; shift; else outputSeparator='\n'; fi for glyph in "$@"; do printf "\\\x%s" $(printf "$glyph"|xxd -p -c1 -u); echo -n -e "$outputSeparator"; done } # usage: $ hexFromGlyph ☠ ✿ \xE2\x98\xA0 \xE2\x9C\xBF $ hexFromGlyph -n ☠ ✿ \xE2\x98\xA0 \xE2\x9C\xBF
StephaneAG
2
Dobry Boże! Zastanów się: codepoints () { printf 'U+%04x\n' ${@/#/\'} ; } ; codepoints A R ☯ 🕉 z ... ciesz się 👍
nabin-info
8

Używam tego:

$ echo -e '\u2620'

Jest to o wiele łatwiejsze niż wyszukiwanie reprezentacji szesnastkowej ... Używam tego w moich skryptach powłoki. To działa na gnome-term i urxvt AFAIK.

Metal3d
źródło
2
@masukomi, jeśli wiesz, jak używać naparu, możesz zainstalować najnowszą wersję bash i korzystać z niej. Powyższe działa dobrze na moim terminalu Mac podczas korzystania z uaktualnionego basha.
mcheema
Tak, w porządku z nowszymi wersjami bash. Łańcuchy zachęty Howera, np. $ PS1, nie używają formatów ucieczki echa
cms
6

Może być konieczne zakodowanie punktu kodowego w postaci ósemkowej, aby możliwe było szybkie rozwinięcie w celu prawidłowego odkodowania.

U + 2620 kodowany jako UTF-8 to E2 98 A0.

Więc w Bash

export PS1="\342\230\240"

sprawi, że twoja skorupa zmieni się w czaszkę i kości.

cms
źródło
cześć, jaki kod powinienem wpisać dla „e0 b6 85”? jak mogę to znaleźć?
Udayantha Udy Warnasuriya
wystarczy przekonwertować liczby szesnastkowe (podstawa 16) e0 b6 85 na liczbę ósemkową (podstawa 8) - użycie kalkulatora jest prawdopodobnie najłatwiejszym sposobem na zrobienie tego
cms
e0 b6 85 hex jest 340 266 205 ósemkowy
cms
To działało, wielkie dzięki! A przy okazji, możesz znaleźć wersję ósemkową na tych stronach: graphemica.com/%E2%9B%B5
Perlnika
6

W bash, aby wydrukować znak Unicode do wydrukowania, użyj \ x, \ u lub \ U (pierwszy dla 2-cyfrowego szesnastkowego, drugi dla 4-cyfrowego szesnastkowego, trzeci dla dowolnej długości)

echo -e '\U1f602'

Chcę przypisać go do zmiennej użyj składni $ '...'

x=$'\U1f602'
echo $x
użytkownik2622016
źródło
5

Jeśli nie przeszkadza Ci liniowiec Perl:

$ perl -CS -E 'say "\x{2620}"'

-CSumożliwia dekodowanie UTF-8 na wejściu i kodowanie UTF-8 na wyjściu. -Eocenia następny argument jako Perl, z nowoczesnymi funkcjami, takimi jak saywłączone. Jeśli nie chcesz nowego wiersza na końcu, użyj printzamiast say.

Flimm
źródło
5

Każde z tych trzech poleceń wyświetli żądany znak w konsoli, pod warunkiem, że konsola akceptuje znaki UTF-8 (większość z nich to robi):

echo -e "SKULL AND CROSSBONES (U+2620) \U02620"
echo $'SKULL AND CROSSBONES (U+2620) \U02620'
printf "%b" "SKULL AND CROSSBONES (U+2620) \U02620\n"

SKULL AND CROSSBONES (U+2620) 

Następnie możesz skopiować i wkleić rzeczywisty glif (obraz, znak) do dowolnego edytora tekstowego (z obsługą UTF-8).

Jeśli chcesz zobaczyć, jak taki punkt kodowania Unicode jest kodowany w UTF-8, użyj xxd (znacznie lepsza przeglądarka szesnastkowa niż od):

echo $'(U+2620) \U02620' | xxd
0000000: 2855 2b32 3632 3029 20e2 98a0 0a         (U+2620) ....

That means that the UTF8 encoding is: e2 98 a0

Lub, w HEX, aby uniknąć błędów: 0xE2 0x98 0xA0. Oznacza to, że wartości między spacją (HEX 20) a liniowym przesunięciem (Hex 0A).

Jeśli chcesz zagłębić się w konwersję liczb na znaki: spójrz tutaj, aby zobaczyć artykuł z wiki Grega (BashFAQ) na temat kodowania ASCII w Bash!

2350426
źródło
Odp: „Lub w HEX, aby uniknąć błędów ...” Nie sądzę, że konwersja znaku Unicode na kodowanie binarne wyrażane w znakach szesnastkowych pomaga uniknąć błędów. Użycie notacji Unicode w „bash” lepiej uniknęłoby błędów, tj .: „\ uHHHH --- znak Unicode (ISO / IEC 10646), którego wartością jest ---- wartość szesnastkowa HHHH (jedna do czterech cyfr szesnastkowych); \ UHHHHHHHH ---- znak Unicode (ISO / IEC 10646), którego wartość to ---- wartość szesnastkowa HHHHHHHH (od jednej do ośmiu cyfr szesnastkowych)
Astara
4

printfWbudowane (podobnie jak coreutils' printf) zna \usekwencję escape która akceptuje 4-cyfrowe znaki Unicode:

   \uHHHH Unicode (ISO/IEC 10646) character with hex value HHHH (4 digits)

Test z Bash 4.2.37 (1):

$ printf '\u2620\n'
Michael Jaros
źródło
printf jest także wbudowaną powłoką. Prawdopodobnie używasz domyślnego systemu macOS bash (v3). Spróbuj \printfużyć autonomicznego pliku wykonywalnego lub wypróbuj zaktualizowaną wersję bash
mcint
4

Przepraszamy za wznowienie tego starego pytania. Ale przy użyciu bashistnieje bardzo łatwe podejście do tworzenia punktów kodowych Unicode na podstawie zwykłego wejścia ASCII, które nawet się nie rozwidlają :

unicode() { local -n a="$1"; local c; printf -vc '\\U%08x' "$2"; printf -va "$c"; }
unicodes() { local a c; for a; do printf -vc '\\U%08x' "$a"; printf "$c"; done; };

Użyj go w następujący sposób, aby zdefiniować pewne punkty kodowe

unicode crossbones 0x2620
echo "$crossbones"

lub zrzucenie pierwszych 65536 znaków kodowych Unicode na standardowe wyjście (zajmuje to mniej niż 2 s na mojej maszynie. Dodatkową przestrzenią jest zapobieganie spływaniu niektórych znaków ze względu na czcionkę o stałej szerokości powłoki):

for a in {0..65535}; do unicodes "$a"; printf ' '; done

lub aby opowiedzieć trochę bardzo typową historię rodzica (wymaga Unicode 2010):

unicodes 0x1F6BC 32 43 32 0x1F62D 32 32 43 32 0x1F37C 32 61 32 0x263A 32 32 43 32 0x1F4A9 10

Wyjaśnienie:

  • printf '\UXXXXXXXX' wypisuje dowolny znak Unicode
  • printf '\\U%08x' numberdrukuje \UXXXXXXXXz liczbą przekonwertowaną na heksadecymalną, która jest następnie podawana do innej w printfcelu wydrukowania znaku Unicode
  • printf rozpoznaje liczby ósemkowe (0oct), szesnastkowe (0xHEX) i dziesiętne (0 lub liczby rozpoczynające się od 1 do 9) jako liczby, dzięki czemu możesz wybrać dowolną reprezentację, która najlepiej pasuje
  • printf -v var ..zbiera dane wyjściowe printfdo zmiennej, bez widelca (co ogromnie przyspiesza rzeczy)
  • local variable ma tam nie zanieczyszczać globalnej przestrzeni nazw
  • local -n var=otheraliasy vardo other, takie jak przypisanie do varzmian other. Jedną z interesujących części jest to, że varjest częścią lokalnej przestrzeni nazw, podczas gdy otherjest częścią globalnej przestrzeni nazw.
    • Pamiętaj, że nie ma czegoś takiego jak localani globalprzestrzeń nazw bash. Zmienne są przechowywane w środowisku i takie są zawsze globalne. Lokalny po prostu odkłada bieżącą wartość i przywraca ją, gdy funkcja zostanie ponownie opuszczona. Inne funkcje wywoływane z funkcji za pomocą localbędą nadal widzieć wartość „lokalną”. Jest to całkowicie inna koncepcja niż wszystkie normalne reguły określania zakresu występujące w innych językach (a to, co bashdziała, jest bardzo potężne, ale może prowadzić do błędów, jeśli jesteś programistą, który nie jest tego świadomy).
Tino
źródło
cóż - w ogóle dla mnie nie działa. każda próba użycia dowolnej z twoich funkcji, emituje: linia 6: lokalna: -n: niepoprawna opcja lokalna: użycie: lokalna nazwa [= wartość] ... Używam najnowszego (10.14.2) MacOS i bash (GNU bash , wersja 3.2.57 (1) -release (x86_64-apple-darwin18))
Motti Shneor
4

Oto lista wszystkich dostępnych emoji Unicode:

https://en.wikipedia.org/wiki/Emoji#Unicode_blocks

Przykład:

echo -e "\U1F304"
🌄

Aby uzyskać wartość ASCII tego znaku, użyj hexdump

echo -e "🌄" | hexdump -C

00000000  f0 9f 8c 84 0a                                    |.....|
00000005

A następnie użyj wartości podanych w formacie szesnastkowym

echo -e "\xF0\x9F\x8C\x84\x0A"
🌄
Matheus
źródło
echo ciągu \ U <hex> nie działa w OSX, po prostu wyświetla dokładnie to, co jest w cudzysłowie.
masukomi,
2

Łatwe dzięki jedno-liniowej wersji Python2 / 3:

$ python -c 'print u"\u2620"'    # python2
$ python3 -c 'print(u"\u2620")'  # python3

Prowadzi do:

Chris Johnson
źródło
2

W Bash:

UnicodePointToUtf8()
{
    local x="$1"               # ok if '0x2620'
    x=${x/\\u/0x}              # '\u2620' -> '0x2620'
    x=${x/U+/0x}; x=${x/u+/0x} # 'U-2620' -> '0x2620'
    x=$((x)) # from hex to decimal
    local y=$x n=0
    [ $x -ge 0 ] || return 1
    while [ $y -gt 0 ]; do y=$((y>>1)); n=$((n+1)); done
    if [ $n -le 7 ]; then       # 7
        y=$x
    elif [ $n -le 11 ]; then    # 5+6
        y=" $(( ((x>> 6)&0x1F)+0xC0 )) \
            $(( (x&0x3F)+0x80 ))" 
    elif [ $n -le 16 ]; then    # 4+6+6
        y=" $(( ((x>>12)&0x0F)+0xE0 )) \
            $(( ((x>> 6)&0x3F)+0x80 )) \
            $(( (x&0x3F)+0x80 ))"
    else                        # 3+6+6+6
        y=" $(( ((x>>18)&0x07)+0xF0 )) \
            $(( ((x>>12)&0x3F)+0x80 )) \
            $(( ((x>> 6)&0x3F)+0x80 )) \
            $(( (x&0x3F)+0x80 ))"
    fi
    printf -v y '\\x%x' $y
    echo -n -e $y
}

# test
for (( i=0x2500; i<0x2600; i++ )); do
    UnicodePointToUtf8 $i
    [ "$(( i+1 & 0x1f ))" != 0 ] || echo ""
done
x='U+2620'
echo "$x -> $(UnicodePointToUtf8 $x)"

Wynik:

─━│┃┄┅┆┇┈┉┊┋┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟
┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿
╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟
╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿
▀▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▐░▒▓▔▕▖▗▘▙▚▛▜▝▞▟
■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▲△▴▵▶▷▸▹►▻▼▽▾▿
◀◁◂◃◄◅◆◇◈◉◊○◌◍◎●◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟
◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴◵◶◷◸◹◺◻◼◽◾◿
U+2620 -> 
Дмитрий Юдин
źródło
0

Jeśli znana jest wartość szesnastkowa znaku Unicode

H="2620"
printf "%b" "\u$H"

Jeśli znana jest wartość dziesiętna znaku Unicode

declare -i U=2*4096+6*256+2*16
printf -vH "%x" $U              # convert to hex
printf "%b" "\u$H"
philcolbourn
źródło