¿Cómo hacer tr consciente de los caracteres no ASCII (Unicode)?

36

Estoy tratando de eliminar algunos caracteres del archivo (UTF-8). Estoy usando trpara este propósito:

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

El archivo contiene algunos caracteres extranjeros (como "Латвийская" o "àé"). trno parece entenderlos: los trata como no alfa y también los elimina.

Intenté cambiar algunas de mis configuraciones locales:

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

Lamentablemente, ninguno de estos funcionó.

¿Cómo puedo hacer trentender Unicode?

MatthewRock
fuente

Respuestas:

29

Esa es una limitación conocida ( 1 , 2 , 3 , 4 , 5 , 6 ) de la implementación de GNU de tr.

No es tanto que no sea compatible con caracteres extranjeros , no ingleses o no ASCII, sino que no sea compatible con caracteres de varios bytes.

Esos caracteres cirílicos se tratarían bien, si se escriben en el conjunto de caracteres iso8859-5 (un byte por carácter) (y su localidad estaba usando ese juego de caracteres), pero su problema es que está usando UTF-8 donde no es ASCII Los caracteres están codificados en 2 o más bytes.

GNU tiene un plan (ver también ) para arreglar eso y el trabajo está en marcha pero aún no existe.

FreeBSD o Solaris trno tienen el problema.


Mientras tanto, para la mayoría de los casos de uso tr, puede usar GNU sed o GNU awk que admiten caracteres de varios bytes.

Por ejemplo, su:

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

podría escribirse:

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

o:

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

Para convertir entre minúsculas y mayúsculas ( tr '[:upper:]' '[:lower:]'):

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

(eso les minúscula L, no el 1dígito).

o:

gawk '{print tolower($0)}'

Para la portabilidad, perles otra alternativa:

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

Si sabe que los datos se pueden representar en un conjunto de caracteres de un solo byte, puede procesarlos en ese 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
fuente
1
He aceptado tu pregunta por información sobre tr. He resuelto el problema y eliminé la pregunta sobre cómo resolverlo (por lo que las personas que buscan tr encontrarán solo información sobre tr, no algún problema arbitrario). Si también pudiera eliminar la solución, ya que ya no es necesaria, se lo agradecería.
MatthewRock
3
@MatthewRock Lo guardé, pero lo reformulé y lo hice más genérico, ya que dar una palabra sería útil para las personas con el mismo problema.
Stéphane Chazelas
¿De dónde sacas una idea de que Cirílico está (habitualmente) codificado en ISO 8859-5? ¿Alguna vez viste un texto ruso en otra cosa que no sea Unicode?
Incnis Mrsi
99
@IncnisMrsi, lo único que importa aquí es que ISO 8859-5 es uno de esos conjuntos de caracteres de byte único que tiene esos caracteres cirílicos. Si es de uso generalizado o no es irrelevante aquí. Si tiene una configuración regional con KOI-R o window-1251 charset, por supuesto, úselo.
Stéphane Chazelas
@IncnisMrsi El ruso en la web casi siempre está codificado en UTF-8 (u ocasionalmente en Windows-1251), pero solo porque hemos sentido el dolor de muchas codificaciones de un solo byte desde el principio. Aquí hay una página web antigua (alrededor de 1998) con un conmutador de codificación (no funcional): sch57.ru/collect .
Alex Shpilkin