Как вы повторяете 4-значный символ Unicode в Bash?


224

Я хотел бы добавить череп и скрещенные кости Юникода в мою подсказку оболочки (в частности, «ЧЕРЕП И КРЕСТЫ» (U + 2620)), но я не могу понять магическое заклинание, которое заставило бы эхо выплюнуть его или любое другое, 4-значный Unicode символ. Двузначные легко. Например, echo -e "\ x55",.

В дополнение к ответам ниже следует отметить, что, очевидно, ваш терминал должен поддерживать Unicode, чтобы вывод был тем, что вы ожидаете. gnome-терминал хорошо справляется с этой задачей, но он не обязательно включен по умолчанию.

В приложении терминала MacOS перейдите в «Настройки» -> «Кодировки» и выберите «Юникод» (UTF-8).


7
Обратите внимание, что ваш комментарий «2 цифры одно легко (эхом)» действителен только для значений "\x7F"в локали UTF-8 (что bashговорит ваш тег) ... шаблоны, представленные одним байтом , никогда не находятся в диапазоне \x80-\xFF, Этот диапазон недопустим в однобайтовых символах UTF-8. например, значение кодовой точки Unicode U+0080(т.е. \x80) фактически составляет 2 байта в UTF-8 .. \xC2\x80..
Peter.O

4
Например printf "\\u007C\\u001C".
Кенорб

NB: для меня gnome-terminal, echo -e '\ufc'не производит ü, даже если кодировка символов установлена ​​в UTF-8. Однако, например urxvt, печатает, например, printf "\\ub07C\\ub01C"как ожидалось (не с или коробкой).
isomorphismes

@ Peter.O Почему bashтег такой полезный совет ? Являются ли разные терминалы общими в CJK или ...?
изоморфизм

1
@ Peter.O zsh, fish, scsh, elvish и т. Д. ... существует множество различных оболочек, каждый может обрабатывать символы Юникода так, как они хотят (или нет). «bash» проясняет, что этот вопрос не о какой-то странной оболочке, которая делает вещи по-другому.
Масукоми

Ответы:


237

В UTF-8 на самом деле это 6 цифр (или 3 байта).

$ printf '\xE2\x98\xA0'

Чтобы проверить, как это кодируется консолью, используйте hexdump:

$ printf  | hexdump
0000000 98e2 00a0                              
0000003

5
Мои выводы " " вместо ☠ ... Почему?
trusktr

8
Это правда. Я обнаружил, что я использовал LANG=Cвместо LANG=en_US.UTF-8. Теперь мои терминалы в Gnome правильно отображают символы ... Реальные терминалы (tty1-6) все еще этого не делают.
trusktr

6
Для тех, кто пытается использовать hexdump: 0000000 f0 9f 8d baпереводится как \xf0\x9f\x8d\xba. Пример эхо: echo -e "\xf0\x9f\x8d\xba".
Блейз

8
Вы также можете использовать $'...'синтаксис для ввода закодированного символа в переменную без использования $(...)подоболочки, для использования в контекстах, которые сами по себе не интерпретируют escape-последовательности:skull=$'\xE2\x98\xA0'
Andrew Janke

7
Еще одна вещь, связанная с hexdump: на моей машине выводится вторая команда в ответе 0000000 98e2 00a0. Конечно, 0000000это просто несущественное смещение, но байты после него преобразуются \xe2\x98\xa0, потому что машина использует порядок байтов с прямым порядком байтов.
sigalor

98
% echo -e '\u2620'     # \u takes four hexadecimal digits

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

Это работает в Zsh (я проверял версию 4.3) и в Bash 4.2 или новее.


16
это просто выплевывает \ u2620, когда я делаю это.
Масукоми

Для меня тоже. Какую оболочку вы используете, Джулиано?
Иоахим Зауэр

2
Извините, забыл сказать, что я использую Zsh.
Juliano

32
Поддержка \ u была добавлена ​​в Bash 4.2.
Lri

4
не работает для меня, Mac OS 10.14.2, bash (GNU bash, версия 3.2.57 (1) -релиз (x86_64-apple-darwin18)). Он просто распечатывает ввод - $ echo -e '\ u2620' <enter> просто распечатывает: \ u2620
Мотти Шнеор,

68

До тех пор, пока ваши текстовые редакторы могут справляться с Unicode (предположительно, закодированы в UTF-8), вы можете вводить кодовую точку Unicode напрямую.

Например, в текстовом редакторе Vim вы должны войти в режим вставки и нажать Ctrl+ V+, Uа затем номер кодовой точки в виде 4-значного шестнадцатеричного числа (при необходимости, с нулями). Таким образом , вы должны набрать Ctrl+ V+ U 2 6 2 0. См .: Какой самый простой способ вставить символы Unicode в документ?

В терминале с Bash вы должны набрать CTRL+ SHIFT+ Uи ввести шестнадцатеричный код нужного вам символа. Во время ввода ваш курсор должен показывать подчеркивание u. Первый не введенный вами номер завершает ввод и отображает символ. Таким образом, вы можете печатать U + 2620 в Bash, используя следующее:

echo CTRL+ SHIFT+U2620ENTERENTER

(Первый ввод завершает ввод Unicode, а второй запускает echoкоманду.)

Кредит: Спросите Ubuntu SE


1
Хороший источник для шестнадцатеричных кодовых точек - unicodelookup.com/#0x2620/1
RobM

1
Версия vim, которую я использую (7.2.411 на RHEL 6.3), не отвечает должным образом, когда между ctrl-v и u есть точка, но работает нормально, если эта точка пропущена.
Крис Джонсон

@ChrisJohnson: я удалил точку из инструкции, она не была предназначена для нажатия клавиш (поэтому она не появилась с эффектом клавиатуры). Извините за путаницу.
RobM

5
Осторожно: это работает в терминале с Bash, только если вы используете его в среде GTK + , как Gnome.
Н.Р.

1
Возможность C-S-u 2 6 2 0- это особенность вашего эмулятора терминала, X Input Method (XIM) или аналогичного. AFAIK, вы не сможете отправить как SHIFTи CTRLна уровне терминала. Терминал говорит только в символах, а не в символах и кодах клавиш, таких как ваш X-сервер (также он является 7-разрядным для всех целей и задач). В этом мире CTRLмаскируются 4 наиболее значимых бита (& 0b00001111), что приводит к
nabin-info

31

Вот полностью внутренняя реализация Bash, без разветвления, неограниченный размер символов 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

Выход был:

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

Мне очень любопытно обоснование метода округления и конкретное использование переменной REPLY. Я предполагаю, что вы проверяли исходный код bash или пробежали или что-то для оптимизации, что я вижу, как ваш выбор может быть оптимизирован, хотя и сильно зависит от переводчика).
Набин-инфо

14

Просто вставьте «☠» в свой скрипт оболочки. В правильной локали и на консоли с поддержкой Unicode он будет печататься очень хорошо:

$ echo 

$

Уродливым «обходным путем» будет вывод последовательности UTF-8, но это также зависит от используемой кодировки:

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

$

13

Быстрый однострочный перевод символов UTF-8 в их 3-байтовый формат:

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

5
Я бы не назвал приведенный выше пример быстрым (с 11 командами и их параметрами) ... Также он обрабатывает только 3-байтовые символы UTF-8 (символы UTF-8 могут иметь длину 1, 2 или 3 байта) ... Это немного короче и работает на 1-3 ++++ байтов: printf "\\\x%s" $(printf '☠'|xxd -p -c1 -u).... xxd поставляется как часть пакета 'vim-common'
Peter.O

PS: я только что заметил, что приведенный выше пример hexdump / awk изменяет последовательность байтов в байтовой паре. Это не относится к дампу UTF-8. Это было бы релевантно, если бы это был дамп UTF-16LE и он хотел бы выводить кодовые точки Unicode , но здесь это не имеет смысла, так как ввод является UTF-8, а вывод точно такой же, как ввод (плюс \ x перед каждым hexdigit). -пара)
Peter.O

7
Символы UTF-8 могут иметь последовательность от 1 до 4 байтов
cms

1
основываясь на комментарии @ Peter.O, я нахожу следующее, хотя и более крупное, довольно удобным: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
Хороший бог человек. Подумайте: codepoints () { printf 'U+%04x\n' ${@/#/\'} ; } ; codepoints A R ☯ 🕉 z ... наслаждайтесь 👍
nabin-info

8

Я использую это:

$ echo -e '\u2620'

Это довольно просто, чем поиск в шестнадцатеричном представлении ... Я использую это в своих скриптах оболочки. Это работает на gnome-term и urxvt AFAIK.


2
@masukomi Если вы знаете, как использовать brew, вы можете установить более новую версию bash и использовать ее. Вышеописанное прекрасно работает на моем Mac-терминале при использовании обновленного bash.
mcheema

Да, это нормально с более новыми версиями bash. Hower строки подсказки, например, $ PS1 не использует экранированные экранирующие форматы
cms

6

Вам может потребоваться закодировать кодовую точку как восьмеричное, чтобы быстрое расширение правильно его расшифровывало.

U + 2620 в кодировке UTF-8 - это E2 98 A0.

Так что в Баш,

export PS1="\342\230\240"

превратит вашу раковину в череп и кости.


привет, какой код я должен ввести для "e0 b6 85"? как я могу найти это?
Udayantha Udy Warnasuriya

просто преобразуйте шестнадцатеричные (основание 16) числа e0 b6 85 в восьмеричное (основание 8) - используйте калькулятор, вероятно, самый простой способ сделать это
cms

e0 b6 85 hex - 340 266 205 восьмеричных
cms

Это сработало, спасибо большое! И, кстати, вы можете найти восьмеричные восьмеричные версии на этих страницах: graphemica.com/%E2%9B%B5
Perlnika

6

В bash для вывода символа Unicode для вывода используйте \ x, \ u или \ U (сначала для шестнадцатеричного шестнадцатеричного числа, второго для шестнадцатеричного шестнадцатеричного числа, третьего для любой длины)

echo -e '\U1f602'

Если вы хотите присвоить его переменной, используйте синтаксис $ '...'

x=$'\U1f602'
echo $x

5

Если вы не возражаете против однострочности Perl:

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

-CSвключает декодирование UTF-8 на входе и кодирование UTF-8 на выходе. -Eоценивает следующий аргумент как Perl, с sayвключенными современными функциями . Если вам не нужен перевод строки в конце, используйте printвместо say.


5

Любая из этих трех команд напечатает нужный вам символ в консоли, при условии, что консоль принимает символы UTF-8 (большинство современных):

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) 

После этого вы можете скопировать и вставить фактический глиф (изображение, символ) в любой (с поддержкой UTF-8) текстовый редактор.

Если вам нужно посмотреть, как такая кодовая точка Unicode кодируется в UTF-8, используйте xxd (намного лучше, чем шестнадцатеричный просмотрщик 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

Или, в HEX, чтобы избежать ошибок: 0xE2 0x98 0xA0. То есть значения между пробелом (HEX 20) и переводом строки (Hex 0A).

Если вы хотите глубоко погрузиться в преобразование чисел в символы: посмотрите здесь, чтобы увидеть статью из вики Грега (BashFAQ) о кодировке ASCII в Bash!


re: «Или, в HEX, чтобы избежать ошибок ...» Я не думаю, что преобразование символа unicode в некоторую двоичную кодировку, которую вы выражаете в шестнадцатеричных символах, помогает избежать ошибок. Использование нотации Unicode в «bash» позволит избежать ошибок, а именно: «\ uHHHH --- символ Unicode (ISO / IEC 10646), значение которого равно ---- шестнадцатеричное значение HHHH (от одной до четырех шестнадцатеричных цифр); \ UHHHHHHHH ---- символ Unicode (ISO / IEC 10646), значение которого равно ---- шестнадцатеричное значение HHHHHHHH (от одной до восьми шестнадцатеричных цифр)
Astara

4

printfВстроенный (так же , как Coreutils' printf) знает управляющую \uпоследовательность , которая принимает 4-значные символы Unicode:

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

Тест с Bash 4.2.37 (1):

$ printf '\u2620\n'

printf также является встроенной оболочкой. Вы, вероятно, используете MacOS по умолчанию (v3). Попробуйте \printfиспользовать автономный исполняемый файл или попробуйте обновить bash
mcint

4

Извините за возрождение этого старого вопроса. Но при использовании bashсуществует очень простой подход для создания кодовых точек Unicode из простого ввода ASCII, который даже не разветвляется :

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

Используйте его следующим образом для определения определенных кодовых точек

unicode crossbones 0x2620
echo "$crossbones"

или сбросить первые 65536 кодов Unicode в stdout (на моем компьютере это занимает менее 2 с. Дополнительное пространство - для предотвращения попадания определенных символов друг в друга из-за моноширинного шрифта оболочки):

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

или рассказать немного очень типичную историю родителей (для этого нужен Unicode 2010):

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

Объяснение:

  • printf '\UXXXXXXXX' распечатывает любой символ Unicode
  • printf '\\U%08x' numberпечатает \UXXXXXXXXс числом, преобразованным в шестнадцатеричное, затем передается другому, printfчтобы фактически распечатать символ Unicode
  • printf распознает восьмеричные (0oct), шестнадцатеричные (0xHEX) и десятичные (0 или числа, начинающиеся с 1 до 9) числа, поэтому вы можете выбрать, какое из представлений подходит лучше всего
  • printf -v var ..собирает выходные данные printfв переменную без форка (что значительно ускоряет процесс)
  • local variable есть ли не загрязнять глобальное пространство имен
  • local -n var=otherпсевдонимы varк otherтаким образом, что присвоение varалтарей other. Одна интересная часть здесь - varэто часть локального пространства имен, а также otherчасть глобального пространства имен.
    • Обратите внимание, что нет такой вещи как localили globalпространства имен в bash. Переменные хранятся в среде, и такие всегда глобальные. Local просто убирает текущее значение и восстанавливает его, когда функция снова выходит из режима ожидания. Другие функции, вызываемые из функции with, localбудут по-прежнему видеть «локальное» значение. Это принципиально иное понятие, чем все обычные правила определения области действия, встречающиеся в других языках (и то, что bashделает это, очень мощно, но может привести к ошибкам, если вы программист, который не знает об этом).

хорошо - не работает для меня. любая попытка использовать любую из ваших функций, выдает: строка 6: локальная: -n: недопустимая опция local: использование: локальное имя [= значение] ... Я использую последние (10.14.2) MacOS и bash (GNU bash , версия 3.2.57 (1) -релиз (x86_64-apple-darwin18))
Мотти Шнеор,

4

Вот список всех доступных смайликов Unicode:

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

Пример:

echo -e "\U1F304"
🌄

Для получения значения ASCII этого символа используйте hexdump

echo -e "🌄" | hexdump -C

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

А затем используйте значения, сообщенные в шестнадцатеричном формате

echo -e "\xF0\x9F\x8C\x84\x0A"
🌄

вывод строки \ U <hex> не работает в OSX, он просто выводит именно то, что находится в кавычках.
Масукоми


2

Легко с одним вкладышем Python2 / 3:

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

Результаты в:


2

В Баше:

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

Вывод:

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

0

Если известно шестнадцатеричное значение символа Юникод

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

Если известно десятичное значение символа Юникод

declare -i U=2*4096+6*256+2*16
printf -vH "%x" $U              # convert to hex
printf "%b" "\u$H"
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.