Não pode usar 'cut -c' ('- caracteres') com UTF-8?

10

O comando cut tem uma opção -c para trabalhar em caracteres, em vez de bytes com a opção -b . Mas isso parece não funcionar, em en_US.UTF-8 locale:

O segundo byte fornece o segundo caractere ASCII (que é codificado da mesma forma em UTF-8):

$ printf 'ABC' | cut -b 2          
B

mas não fornece o segundo de três caracteres não-ASCII em grego no idioma UTF-8:

$ printf 'αβγ' | cut -b 2         
�

Tudo bem - é o segundo byte .
Então, olhamos para o segundo caractere :

$ printf 'αβγ' | cut -c 2 
�

Isso parece quebrado.
Com alguns experimentos, o intervalo 3-4 mostra o segundo caractere:

$ printf 'αβγ' | cut -c 3-4
β

Mas isso é o mesmo que os bytes 3 a 4:

$ printf 'αβγ' | cut -b 3-4
β

Portanto, o -c não é maior que o -b de UTF-8.

Eu esperaria que a configuração do código do idioma não seja adequada para UTF-8, mas, em comparação, wc funciona como esperado; Geralmente é usado para contar bytes, com a opção -c ( --bytes ).   (Observe os nomes das opções confusas).

$ printf 'αβγ' | wc -c
6

Mas também pode contar caracteres com a opção -m ( --chars ), que funciona:

$ printf 'αβγ' | wc -m
3

Portanto, minha configuração parece estar ok - mas algo é especial sobre cut .

Talvez não suporte UTF-8 em tudo? Mas parece suportar caracteres multi-byte, caso contrário não seria necessário suportar -b e -c .

Então, o que há de errado? E por quê?


A configuração local parece correta para o utf8, até onde eu sei:

$ locale
LANG=en_US.UTF-8
LANGUAGE=en_US
LC_CTYPE=en_US.UTF-8
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

A entrada, byte por byte:

$ printf 'αβγ' | hd 
00000000  ce b1 ce b2 ce b3                                 |......|
00000006
    
por Volker Siegel 23.10.2014 / 07:56

2 respostas

10

Você não disse qual cut você está usando, mas desde que você mencionou a opção longa do GNU --characters , eu assumirei que é essa. Nesse caso, observe esta passagem de info coreutils 'cut invocation' :

‘-c character-list’
‘--characters=character-list’

Select for printing only the characters in positions listed in character-list. The same as -b for now, but internationalization will change that.

(ênfase adicionada)

No momento, o GNU cut sempre funciona em termos de "caracteres" de byte único, então o comportamento que você vê é esperado.

Suportar ambas as opções -b e -c é exigido pelo POSIX - elas não foram adicionados ao GNU cut porque tinham suporte multi-byte e funcionavam corretamente, mas para evitar erros na entrada compatível com POSIX. O mesmo -c foi feito em algumas outras implementações cut , embora não seja FreeBSD e OS X pelo menos.

Este é o comportamento histórico de -c . -b foi adicionado recentemente para assumir a função de byte, de modo que -c possa funcionar com caracteres de múltiplos bytes. Talvez daqui a alguns anos funcione como desejado de forma consistente, embora o progresso não tenha sido exatamente rápido (já faz mais de uma década). O GNU cut nem sequer implementa a opção -n ainda, embora seja ortogonal e destinado a ajudar na transição. Existem potenciais problemas de compatibilidade com scripts antigos, o que pode ser uma preocupação, embora eu não saiba de forma definitiva qual é o motivo.

    
por 23.10.2014 / 08:16
2

Como muitas implementações grep são compatíveis com vários bytes, você também pode usar grep -o para simular alguns usos de cut -c .

$ echo Τηεοδ29 | grep -o '^..'
Τη
$ echo Τηεοδ29 | egrep -o '^..' | grep -o '.$'
η

Ajuste o número de períodos para simular os intervalos cut .

    
por 20.08.2016 / 16:48