A partir desta discussão:
Quando eu tenho (zsh 5.8, bash 5.1.0)
var="ASCII"
echo "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"
a resposta é simples: são 5 caracteres, ocupando 5 bytes.
Agora, var=Müller
rende
Müller has the length 6, and is 7 bytes long
O que sugere que o ${#}
operador conta pontos de código, não bytes. Isso é um pouco obscuro no POSIX , onde eles dizem que conta "caracteres". Isso ficaria mais claro se os char
atores em POSIX C não fossem octetos, normalmente.
De qualquer forma: Legal! Que bom, vendo isso LANG==en_US.utf8
.
Agora,
var='??♀️'
echo "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"
??♀️ has the length 5, and is 17 bytes long
Soooo, nós decompomos "Mermaid of dark skin color" no codepoint Unicode
- Merperson
- Pele escura
- Junção de largura zero
- Fêmea
- Imprimir imprimir o caractere anterior como emoji
Tudo bem, então estamos realmente contando pontos de código Unicode!
var="e\xcc\x81"
echo "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"
é has the length 9, and is 9 bytes long
(claro, minha fonte de console decidiu que ´
combina com o espaço a seguir, não o anterior e
. O último estaria correto. Mas vamos deixar minha raiva sobre isso para outro momento.)
Hum, um leve "wat" está em ordem aqui.
> printf "e\xcc\x81"|wc -c
3
> printf "%s" "${var}" |wc -c
9
> echo -n ${var} |wc -c
3
> echo "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"
é has the length 9, and is 9 bytes long
> printf "%s" "${var}" |xxd
00000000: 655c 7863 635c 7838 31 e\xcc\x81
Aqui é onde eu desisto.
echo $var
, echo ${var}
e echo "${var}"
todos "corretamente" emitem três bytes. No entanto, echo ${#var}
me diz que são 9 caracteres.
Onde isso está documentado/padronizado, quais são as regras para tudo isso?
Em shells compatíveis com POSIX (não no shell Bourne, esse recurso vem do shell Korn),
${#var}
comowc -m
conta o número de caracteres ¹$var
e o comportamento não é especificado se a sequência de bytes armazenada$var
não puder ser decodificada para caracteres na localidade atual.Os bytes são decodificados em caracteres de acordo com a localidade atual (sua
LC_CTYPE
categoria). Em uma localidade que usa UTF-8 como a codificação de caracteres, a sequência 0xc3 0xa9 seria decodificada em umé
caractere, enquanto em uma localidade usando ISO8859-1, que seria decodificada emé
e em uma localidade usando BIG5 em矇
.De qualquer forma, tem pouco a ver com codepoints Unicode. Também não é o mesmo que contar o número de clusters de grafema ou a largura da string quando exibida por um terminal ou qualquer outro dispositivo de exibição.
Dentro:
$var
contém 9 bytes e 9 caracteres:e
,\
,x
,c
,c
,\
,x
,8
e1
.Alguns
printf
(no argumento de formato ou em argumentos para%b
diretivas de formato) eecho
implementações se expandirão\xcc
para o byte 0xcc, nem todos o fazem. Por POSIX,\x
em um argumento para aqueles leva a um comportamento não especificado. (\351
expande para o byte 0xe9 noprintf
argumento de formato e\0351
emecho
/%b
embora).Se você quiser
$var
conter os bytes , , em0x65
/0xcc
/ ( e hoje em dia mais e mais shells), você faria:0x81
ksh93
zsh
bash
Ou você sempre pode fazer:
Então, em uma localidade onde
locale charmap
outputsUTF-8
,$var
conteria 3 bytes (como mostrado porwc -c
), 2 caracteres (como mostrado porwc -m
ou${#var}
), 1 agrupamento de grafemas (como mostrado por GNUgrep -Po '\X'
) geralmente exibido com largura 1 (como mostrado por GNUwc -L
).Se a localidade no momento em que o shell foi invocado e no momento em que o código foi analisado e executado tinha UTF-8 como charset, em vários shells, você também pode fazer:
Para
$var
conter a codificação UTF-8 dose
caracteres e U+0301 (combinando acento agudo).Se o conjunto de caracteres da localidade não for UTF-8, o comportamento varia entre os shells. Além disso, se é a localidade que estava em vigor no momento em que o código foi analisado ou no momento em que o código foi executado que é levado em consideração para expandir o ponto de código Unicode em um caractere depende do shell. Você também encontrará variações de comportamento se o personagem não estiver presente no charmap do local.
No shell Bourne, para obter o comprimento em caracteres de uma string, era necessário recorrer a outros utilitários como:
Ou:
No entanto, se você encontrar um sistema antigo o suficiente para ainda ter um shell Bourne, é provável que
wc
ele não seja compatível-m
ou que não haja umprintf
comando.¹ O próprio POSIX não especifica o mapeamento entre sequências de bytes e caracteres, nem mesmo na localidade POSIX, apenas algumas APIs para definir e recuperar esse mapeamento ou converter sequências de bytes para sequência de caracteres (
wchar_t
). Os sistemas geralmente usam conjuntos de caracteres padrão para o charmap como UTF-8, que é um formato de transformação do conjunto de caracteres definido por outro padrão ISO (ISO/IEC 10646 aka Unicode). Alguns sistemas como os GNU realmente usam os pontos de código Unicode para oswchar_t
valores, independentemente da localidade.