Como converter um emoticon especificado por um código U + xxxxx em utf-8?


16

Os emoticons parecem ser especificados usando um formato de U + xxxxx,
em que cada x é um dígito hexadecimal.

Por exemplo, U + 1F615 é o código oficial do Unicode Consortium para a "cara confusa" 😕

Como muitas vezes estou confuso, tenho uma forte afinidade por esse símbolo.

A representação U + 1F615 é confusa para mim, porque eu pensava que as únicas codificações possíveis para caracteres unicode exigiam 8, 16, 24 ou 32 bits, enquanto 5 dígitos hexadecimais requerem 5x4 = 20 bits.

Eu descobri que esse símbolo parece ser representado por uma string hexadecimal completamente diferente no bash:

$echo -n 😕 | hexdump
0000000 f0 9f 98 95                                    
0000004

$echo -e "\xf0\x9f\x98\x95"
😕

$PS1=$'\xf0\x9f\x98\x95  >'
😕  >

Eu esperava que o U + 1F615 se convertesse em algo como \ x00 \ x01 \ xF6 \ x15 .

Não vejo a relação entre essas duas codificações?

Quando procuro um símbolo na lista oficial do Unicode Consortium , gostaria de poder usar esse código diretamente sem precisar convertê-lo manualmente dessa maneira tediosa. ie

  • localizando o símbolo em alguma página da web
  • copiando-o para a área de transferência do navegador da web
  • colá-lo no bash para ecoar através de um hexdump para descobrir o código REAL.

Posso usar esse código de 20 bits para determinar qual é o código de 32 bits?

Existe uma relação entre esses 2 números?

Respostas:


20

UTF-8é uma codificação de comprimento variável de Unicode. Ele foi projetado para ser um superconjunto de ASCII. Veja a Wikipedia para detalhes da codificação. \x00 \x01 \xF6 \x15seria UCS-4BEou UTF-32BEcodificação.

Para ir do ponto de código Unicode até a codificação UTF-8, assumindo que o mapa de caracteres do código do idioma seja UTF-8 (consulte a saída de locale charmap), é apenas:

$ printf '\U1F615\n'
😕
$ echo -e '\U1F615'
😕
$ confused_face=$'\U1F615'

Este último estará na próxima versão do padrão POSIX .

AFAIK, que a sintaxe foi introduzido em 2000 pelo stand-alone GNU printfutilidade (em oposição à printfutilidade do shell GNU), trouxe a echo/ printf/ $'...'builtins primeiro por zsh, em 2003 , ksh93 em 2004, o bash em 2010 (embora não está funcionando corretamente lá até 2014 ), mas foi obviamente inspirado em outros idiomas.

ksh93também suporta como printf '\x1f615\n'e printf '\u{1f615}\n'.

$'\uXXXX'e $'\UXXXXXXXX'são apoiados por zsh, bash, ksh93, mkshe FreeBSD sh, GNU printf, GNU echo.

Alguns requerem todos os dígitos (como em \U0001F615oposição a \U1F615), embora isso mude em versões futuras, pois o POSIX permitirá menos dígitos. De qualquer forma, você precisa de todos os dígitos se \UXXXXXXXXfor para ser seguido por dígitos hexadecimais como em \U0001F615FOX, como \U1F615FOXteria sido $'\U001F615F'OX.

Alguns se expandem para os caracteres na codificação do código do idioma atual no momento em que a cadeia é analisada ou no momento em que é expandida, alguns apenas no UTF-8, independentemente do código do idioma. Se o caractere não estiver disponível na codificação da localidade atual, o comportamento varia entre as conchas.

Portanto, para melhor portabilidade, o melhor é usá-lo apenas nos locais UTF-8 e usar todos os dígitos, e usá-lo em $'...':

printf '%s\n' $'\U0001F615'

Observe que:

LC_ALL=C.UTF-8; printf '%s\n' $'\U0001F615'

ou:

{
  LC_ALL=C.UTF-8
  printf '%s\n' $'\U0001F615'
}

Não irá funcionar com todos os shells (incluindo bash), porque o $'\U0001F615'é analisado antes LC_ALLé atribuído. (observe também que não há garantia de que um sistema tenha um código de idioma chamado C.UTF-8)

Você precisaria de:

LC_ALL=C.UTF-8; eval "confused_face=$'\U0001F615'"

Ou:

LC_ALL=C.UTF-8
printf '%s\n' $'\U0001F615'

(não dentro de um comando ou função composta).


Para o inverso, para ir da codificação UTF-8 ao ponto de código Unicode, consulte essa outra pergunta ou essa .

$ unicode 😕 
U+1F615 CONFUSED FACE
UTF-8: f0 9f 98 95  UTF-16BE: d83dde15  Decimal: 😕
😕
Category: So (Symbol, Other)
Bidi: ON (Other Neutrals)

$ perl -CA -le 'printf "%x\n", ord shift' 😕
1f615

2
Observe que se \U1F615for seguido por outro dígito hexadecimal válido, será considerado parte da sequência de escape. Para fazê-lo funcionar, independentemente do que é seguido, ele deve ter zeros à esquerda suficientes para ter exatamente oito dígitos:\U0001F615
kasperd

@kasperd, obrigado. Sim, vale a pena notar. Eu incluí isso na resposta.
Stéphane Chazelas

7

Aqui está uma maneira de converter de UTF-32 (big endian) para UTF-8

$ confused=$(echo -ne "\x0\x01\xF6\x15" | iconv -f UTF-32BE -t UTF-8)     
$ echo $confused 
😕

Você notará seu valor hexadecimal 0x01F615, preenchido com um 0 extra extra para preencher 32 bits.

A página da Wikipedia em UTF-8 explica a transformação de um ponto de código Unicode para sua representação UTF-8 com muita clareza. Mas tentar fazer isso sozinho em scripts de shell pode não ser a melhor idéia.

O UTF-32 é de largura fixa e a correspondência entre o codepoint e a representação do UTF-32 é trivial - o valor é o mesmo.


6

Ótima maneira de fazer isso na sua cabeça ou no papel:

  1. Calcule quantos bytes serão: os valores em U + 0080 são um byte; em U + 0800, em 2 bytes; em U + 10000, em 3 bytes; em outros, em 4 bytes. No seu caso, 4 bytes.

  2. Convert hex para octal: 0373025.

  3. A partir do final, peel off 2 dígitos octais de cada vez para obter uma sequência de valores octais: 037 030 025.

  4. Se você tem menos valores octais do que o número esperado de bytes, adicionar um extra 0 no início: 000 037 030 025.

  5. Para todos, mas o primeiro, add on 0200para obter: 000 0237 0230 0225.

  6. Para o primeiro, adicione 0300se a duração prevista é de 2, 0340se é 3, ou 0360se é 4, para obter: 360 0237 0230 0225.

Agora escrever como uma seqüência de escapes octais: \360\237\230\225. Opcionalmente, converta de volta para hex, se desejar.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.