¿Cómo puedo convertir números persas en UTF-8 a números europeos en ASCII?

16

En números persas, ۰۱۲۳۴۵۶۷۸۹es equivalente a 0123456789en dígitos europeos.

¿Cómo puedo convertir el número persa (in UTF-8) a ASCII?

Por ejemplo, quiero ۲۱ser 21.

بارپابابا
fuente
1
Interesante, parece que echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLITno lo maneja ...
Kusalananda
@Kusalananda NO funcionó
بارپابابا
3
@Kusalananda: ¿Es realmente tan inesperado? Como entendí, iconves solo aquí para mapear caracteres en diferentes codificaciones, pero estos son caracteres (números arábigos orientales) que no tienen equivalente en ASCII, solo puede convertirlos en algo lo suficientemente similar, pero solo es unidireccional.
phk
3
Bueno, no estaba muy seguro de lo que iconvera capaz y no capaz de hacer. Esperaba que usarlo me //TRANSLITayudara, pero no fue así.
Kusalananda
1
¿También necesitas invertir el orden? Sé que los números arábigos se escriben little-endian de derecha a izquierda, y los números latinos son big-endian de izquierda a derecha (se parecen en la impresión o en la pantalla, pero se invierten en la memoria). ¿Persa es igual?
Toby Speight

Respuestas:

6

Podemos aprovechar el hecho de que el punto de código UNICODE de los números persas es consecutivo y está ordenado de 0 a 9 :

$ printf '%b' '\U06F'{0..9}
۰۱۲۳۴۵۶۷۸۹

Eso significa que el último dígito hexadecimal ES el valor decimal:

$ echo $(( $(printf '%d' "'۲") & 0xF ))
2

Eso hace que este bucle simple sea una herramienta de conversión:

#!/bin/bash
(   ### Use a locale that use UTF-8 to make the script more reliable.
    ### Maybe something like LC_ALL=fa_IR.UTF-8 for you?.
    LC_ALL=en_US.UTF-8
    a="$1"
    while (( ${#a} > 0 )); do
        # extract the last hex digit from the UNICODE code point
        # of the first character in the string "$a":
        printf '%d' $(( $(printf '%d' "'$a") & 15 ))
        a=${a#?}    ## Remove one character from $a
    done
)
echo

Utilizándolo como:

$ sefr.sh ۰۱۲۳۴۵۶۷۸۹
0123456789

$ sefr.sh ۲۰۱
201

$ sefr.sh ۲۱
21

Tenga en cuenta que este código también podría convertir números arábigos y latinos (incluso si se mezclan):

$ sefr.sh ۴4٤۵5٥۶6٦۷7٧۸8٨۹9٩
444555666777888999

$ sefr.sh ٤٧0٠٦7١٣3٥۶٦۷
4700671335667

fuente
muy, muy gracias, esta es una solución muy agradable ,, y tengo pregunta ,, en este comando printf '% d' '"0' por qué utilizar doble cita?
بارپابابا
@Babyy No es una cita doble, que es una forma de dar printf un argumento que comienza con una comilla simple: . Podría haber sido escrito también como '"۰'. La razón es que printf le dará el punto de código UNICODE si el argumento comienza con una comilla simple 'o una comilla doble ". Busque un poco antes de este enlace el texto "Si el personaje principal es una comilla simple o una comilla doble"
@Babyy El código se ha extendido para convertir persa, árabe y latín (incluso si es mixto).
27

Como es un conjunto fijo de números, puedes hacerlo a mano:

$ echo ۲۱ | LC_ALL=en_US.UTF-8 sed -e 'y/۰۱۲۳۴۵۶۷۸۹/0123456789/'
21

(o usando tr, pero aún no GNU tr )

Es necesario establecer su configuración regional en en_US.utf8(o mejor configuración regional a la que pertenece el conjunto de caracteres) para sedreconocer su conjunto de caracteres.

Con perl:

$ echo "۲۱" |
  perl -CS -MUnicode::UCD=num -MUnicode::Normalize -lne 'print num(NFKD($_))'
21
Cuonglm
fuente
LC_ALLEs necesario configurarlo para que todos los caracteres unicode también sean considerados como tales por sed, ¿verdad?
phk
@phk: Sí, vea la actualización.
Cuonglm
¿Por qué todo debe ser un script sed? ¿No inventamos trpara este propósito exacto?
Kevin
3
@Kevin Vea la otra respuesta sobre trcómo no funciona en todas partes. También tenga en cuenta que algunas herramientas están optimizadas para tratar con bytes, mientras que otras son para tratar con caracteres, con Unicode (especialmente UTF-8) esto hace una gran diferencia.
phk
Esto no funciona para mí en OS X 10.10.5 / GNU bash 4.3. Por extraño que parezca, necesito eliminar la configuración explícita de LC_ALL. LC_ALLtampoco está configurado en mi entorno (pero LANGestá configurado en en_GB.UTF-8). Con el código anterior, aparece el error "sed: 1:" y / ۰۱۲۳۴۵۶۷۸۹ / ... ": las cadenas de transformación no tienen la misma longitud".
Konrad Rudolph
15

Para Python existe la unidecodebiblioteca que maneja tales conversiones en general: https://pypi.python.org/pypi/Unidecode .

En Python 2:

>>> from unidecode import unidecode
>>> unidecode(u"۰۱۲۳۴۵۶۷۸۹")
'0123456789'

En Python 3:

>>> from unidecode import unidecode
>>> unidecode("۰۱۲۳۴۵۶۷۸۹")
'0123456789'

El hilo SO en /programming//q/8087381/2261442 podría estar relacionado.

/ edit: Como señaló Wander Nauta en los comentarios y como se menciona en la página de Unidecode, también hay una versión de shell unidecode(debajo de /usr/local/bin/si está instalada pip):

$ echo '۰۱۲۳۴۵۶۷۸۹' | unidecode
0123456789
phk
fuente
2
La biblioteca unidecode también incluye una utilidad llamada (como era de esperar) unidecodeque hace lo mismo que su fragmento de Python 3. Solo echo '۰۱۲۳۴۵۶۷۸۹' | unidecodedebería funcionar.
Wander Nauta
@Wander: el paquete Debian de python-unidecode no incluye el programa de utilidad, por lo que la forma larga puede ser necesaria en dichas plataformas (no encontré uno en el tarball fuente desde arriba, por lo que quizás el programa sea algo agregado por su distribución?)
Toby Speight
@TobySpeight Si lo instala usando pip, está allí.
phk
@TobySpeight La utilidad está en el tarball ascendente como unidecode/util.py- extraño que Debian no lo incluya. (Editar: Ah, misterio resuelto. El paquete Debian está desactualizado y es más antiguo que la utilidad.)
Wander Nauta
7

Una versión pura bash:

#!/bin/bash

number="$1"

number=${number//۱/1}
number=${number//۲/2}
number=${number//۳/3}
number=${number//۴/4}
number=${number//۵/5}
number=${number//۶/6}
number=${number//۷/7}
number=${number//۸/8}
number=${number//۹/9}
number=${number//۰/0}

echo "Result is $number"

Lo he probado en mi máquina Gentoo y funciona.

./convert ۱۳۲
Result is 132

Hecho como un bucle, dada la lista de caracteres (de 0 a 9) para convertir:

#!/bin/bash
conv() ( LC_ALL=en_US.UTF-8
         local n="$2"
         for ((i=0;i<${#1};i++)); do
              n=${n//"${1:i:1}"/"$i"}
         done
         printf '%s\n' "$n"
       )

conv "۰۱۲۳۴۵۶۷۸۹" "$1"

Y usado como:

$ convert ۱۳۲
132

Otra forma (más bien exagerada) usando grep:

#!/bin/bash

nums=$(echo "$1" | grep -o .)
result=()

for i in $nums
do
    case $i in
        ۱)
            result+=1
            ;;
        ۲)
            result+=2
            ;;
        ۳)
            result+=3
            ;;
        ۴)
            result+=4
            ;;
        ۵)
            result+=5
            ;;
        ۶)
            result+=6
            ;;
        ۷)
            result+=7
            ;;
        ۸)
            result+=8
            ;;
        ۹)
            result+=9
            ;;
        ۰)
            result+=0
            ;;
    esac
done
echo "Result is $result"
taza de cafe
fuente
1
Pure Bash, excepto por el grep. De hecho, no entiendo esa línea, ni por qué no establece result=0. ¿Estás siendo demasiado cauteloso en caso de que $1contenga otras cosas que no sean dígitos farsi?
Kusalananda
@Kusalananda esa línea lee los dígitos farsi en números. Lo hace capaz de bucle.
coffeMug
1
Diez sustituciones simples habrían sido más rápidas ... number=${number//۱/1}etc., y evitarían el echoy grep.
Kusalananda
1
@Kusalananda Nice. Lo cambié Ahora es puro Bash! ;-)
coffeMug
@coffeMug: ۱۳۲ es 132 no 123: D
بارپابابا
3

Como iconvparece que no puede asimilar esto, el siguiente puerto de escala sería utilizar la trutilidad:

$ echo "۲۱" | tr '۰۱۲۳۴۵۶۷۸۹' '0123456789'
21

tr traduce un conjunto de caracteres a otro, por lo que simplemente le decimos que traduzca el conjunto de dígitos farsi al conjunto de dígitos latinos.

EDITAR : Como señala el usuario @cuonglm. Esto requiere que no sea GNU tr, por ejemplo, tren una Mac, y también requiere que $LC_CTYPEesté configurado en en_US.UTF-8.

Kusalananda
fuente
2
Tenga en cuenta que no funcionará con GNU tr, que no admite caracteres de varios bytes.
Cuonglm
1
Oh mi. Tonto GNU. ;-)
Kusalananda
Y también debe establecer su configuración regional en la que admite Unicode, como en_US.utf8.
Cuonglm