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


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

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.


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


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ł:

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

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'

$

13

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

var="$(echo -n '☠' | od -An -tx1)"; printf '\\x%s' ${var^^}; echo

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.


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.


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

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.


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!


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'

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

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

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:


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

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