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

27

Estou tentando remover alguns caracteres do arquivo (UTF-8). Estou usando tr para essa finalidade:

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

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

Eu 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 fazer tr entender o Unicode?

    
por MatthewRock 09.09.2015 / 14:57

1 resposta

24

Isso é um conhecido ( 1 , 2 , 3 , 4 , 5 , 6 ) limitação da implementação GNU de tr .

Não é tanto que não ofereça suporte a caracteres estrangeiros , que não estejam em inglês ou não sejam ASCII, mas que eles não suportam caracteres de múltiplos bytes.

Esses caracteres cirílicos seriam tratados como OK, se escritos no conjunto de caracteres iso8859-5 (um byte por caractere) (e sua localidade estava usando esse conjunto de caracteres), mas o problema é que você está usando UTF-8 onde caracteres não-ASCII 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 chegou.

FreeBSD ou Solaris tr não tem o problema.

Nesse meio tempo, para a maioria dos casos de uso de tr , você pode usar o GNU sed ou o GNU awk que foram internacionalizados.

Por exemplo, o 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'

(que l é uma minúscula L , não a 1 dí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, você 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
    
por 09.09.2015 / 15:47