Como tornar tr ciente de caracteres não-ascii (unicode)?

36

Estou tentando remover alguns caracteres do arquivo (UTF-8). Estou usando trpara esse fim:

tr -cs '[[:alpha:][:space:]]' ' ' <testdata.dat 

O arquivo contém alguns caracteres estrangeiros (como "Латвийская" ou "àé"). trparece não entendê-los: trata-os como não-alfa e remove-os também.

Tentei alterar algumas das minhas configurações de localidade:

LC_CTYPE=C LC_COLLATE=C tr -cs '[[:alpha:][:space:]]' ' ' <testdata.dat
LC_CTYPE=ru_RU.UTF-8 LC_COLLATE=C tr -cs '[[:alpha:][:space:]]' ' ' <testdata.dat
LC_CTYPE=ru_RU.UTF-8 LC_COLLATE=ru_RU.UTF-8 tr -cs '[[:alpha:][:space:]]' ' ' <testdata.dat

Infelizmente, nada disso funcionou.

Como posso trentender o Unicode?

MatthewRock
fonte

Respostas:

29

Essa é uma limitação conhecida ( 1 , 2 , 3 , 4 , 5 , 6 ) da implementação GNU de tr.

Não é tanto o fato de não suportar caracteres estrangeiros , não ingleses ou não ASCII, mas não suporta caracteres de vários bytes.

Esses caracteres cirílicos seriam tratados como OK, se escritos no conjunto de caracteres iso8859-5 (byte único por caractere) (e seu código do idioma usasse esse conjunto de caracteres), mas o seu problema é que você está usando UTF-8 onde não ASCII caracteres são codificados em 2 ou mais bytes.

O GNU tem um plano (veja também ) para consertar isso e o trabalho está em andamento, mas ainda não existe.

FreeBSD ou Solaris trnão têm o problema.


Enquanto isso, na maioria dos casos de uso tr, você pode usar o GNU sed ou o GNU awk que suportam caracteres de vários bytes.

Por exemplo, seu:

tr -cs '[[:alpha:][:space:]]' ' '

poderia ser escrito:

gsed -E 's/( |[^[:space:][:alpha:]])+/ /'

ou:

gawk -v RS='( |[^[:space:][:alpha:]])+' '{printf "%s", sep $0; sep=" "}'

Para converter entre maiúsculas e minúsculas ( tr '[:upper:]' '[:lower:]'):

gsed 's/[[:upper:]]/\l&/g'

(ou lseja L, minúsculas , não o 1dígito).

ou:

gawk '{print tolower($0)}'

Para portabilidade, perlé outra alternativa:

perl -Mopen=locale -pe 's/([^[:space:][:alpha:]]| )+/ /g'
perl -Mopen=locale -pe '$_=lc$_'

Se você souber que os dados podem ser representados em um conjunto de caracteres de byte único, poderá processá-los nesse conjunto de caracteres:

(export LC_ALL=ru_RU.iso88595
 iconv -f utf-8 |
   tr -cs '[:alpha:][:space:]' ' ' |
   iconv -t utf-8) < Russian-file.utf8
Stéphane Chazelas
fonte
1
Aceitei sua pergunta por causa de informações sobre tr. Eu resolvi o problema e removi a pergunta sobre como resolvê-lo (para que as pessoas que procuram por tr encontrem apenas informações sobre tr, não algum problema arbitrário). Se você também puder remover a solução, já que ela não é mais necessária, ficaria agradecido.
MatthewRock
3
@MatthewRock Mantive-o, mas reformulei-o e tornei-o mais genérico, pois dar uma palavra seria útil para pessoas com o mesmo problema.
Stéphane Chazelas
De onde você tem uma idéia de que o cirílico é (normalmente) codificado na ISO 8859-5? Você já viu um texto em russo além de Unicode?
Incnis MRSI
9
@IncnisMrsi, tudo o que importa aqui é que a ISO 8859-5 é um desses conjuntos de caracteres de um byte que possui esses caracteres cirílicos. Se é amplamente utilizado ou não, é irrelevante aqui. Se você possui um código de idioma com o conjunto de caracteres KOI-R ou window-1251, use-o em todos os aspectos.
Stéphane Chazelas
O @IncnisMrsi Russian na Web quase sempre é codificado em UTF-8 (ou ocasionalmente no Windows-1251), mas apenas porque sentimos a dor de muitas codificações de byte único no início. Aqui está uma página da Web antiga (por volta de 1998) com um comutador de codificação (não funcional): sch57.ru/collect .
Alex Shpilkin