Comment convertir des chiffres persans en UTF-8 en chiffres européens en ASCII?

16

En chiffres persans, ۰۱۲۳۴۵۶۷۸۹équivaut à 0123456789des chiffres européens.

Comment convertir le nombre persan (in UTF-8) en ASCII?

Par exemple, je veux ۲۱devenir 21.

بارپابابا
la source
1
Intéressant, on dirait que echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLITça ne le gère pas ...
Kusalananda
@Kusalananda PAS travaillé
بارپابابا
3
@Kusalananda: Est-ce vraiment si inattendu? Si j'ai bien compris, c'est iconvjuste ici pour mapper des caractères dans différents encodages, mais ce sont des caractères (chiffres arabes orientaux) qui n'ont pas d'équivalent en ASCII, vous pouvez simplement les convertir en quelque chose d'assez similaire mais c'est à sens unique.
phk
3
Eh bien, je n'étais pas tout à fait sûr de ce qui iconvétait capable et pas capable de le faire. J'espérais que cette utilisation //TRANSLITaiderait, mais ce n'est pas le cas.
Kusalananda
1
Avez-vous également besoin d'annuler la commande? Je sais que les chiffres arabes sont écrits de petit à petit de droite à gauche, et les chiffres latins sont de gros bout à bout de gauche à droite (ressemblant à l'impression ou à l'écran, mais inversés en mémoire). Le persan est-il le même?
Toby Speight

Réponses:

6

On peut profiter du fait que le point de code UNICODE des chiffres persans est consécutif et ordonné de 0 à 9 :

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

Cela signifie que le dernier chiffre hexadécimal EST la valeur décimale:

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

Cela fait de cette boucle simple un outil de conversion:

#!/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

L'utiliser comme:

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

$ sefr.sh ۲۰۱
201

$ sefr.sh ۲۱
21

Notez que ce code pourrait également convertir des chiffres arabes et latins (même s'ils sont mélangés):

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

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

la source
très très merci, c'est une très bonne solution ,, et j'ai une question ,, dans cette commande printf '% d' '"۰' pourquoi utiliser les guillemets doubles?
بارپابابا
@Babyy Il n'est pas une double citation, il est un moyen de donner printf un argument qui commencent par une seule citation: . Il aurait pu être écrit aussi comme '"۰'. La raison en est que printf donnera le point de code UNICODE si l'argument commence par un guillemet simple 'ou un guillemet double ". Recherchez un peu avant ce lien le texte "Si le premier caractère est un guillemet simple ou double"
@Babyy Le code a été étendu pour convertir le persan, l'arabe et le latin (même s'il est mélangé).
27

Puisqu'il s'agit d'un ensemble fixe de nombres, vous pouvez le faire à la main:

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

(ou en utilisant tr, mais pas encore GNU tr )

La définition de votre locale en_US.utf8(ou mieux la locale à laquelle appartient le jeu de caractères) est nécessaire pour sedreconnaître votre jeu de caractères.

Avec perl:

$ echo "۲۱" |
  perl -CS -MUnicode::UCD=num -MUnicode::Normalize -lne 'print num(NFKD($_))'
21
cuonglm
la source
La définition du LC_ALLest nécessaire pour que tous les caractères Unicode soient également considérés comme tels par sed, non?
phk
@phk: Oui, voir la mise à jour.
cuonglm
Pourquoi tout doit-il être un script sed? N'avons-nous pas inventé trdans ce but précis?
Kevin
3
@Kevin Voir l'autre réponse impliquant trcomment cela ne fonctionne pas partout. Gardez également à l'esprit que certains outils sont optimisés pour traiter les octets tandis que d'autres sont pour traiter les caractères, avec Unicode (en particulier UTF-8), cela fait une énorme différence.
phk
Cela ne fonctionne pas pour moi sur OS X 10.10.5 / GNU bash 4.3. Bizarrement, je dois supprimer le paramètre explicite de LC_ALL. LC_ALLn'est pas non plus défini dans mon environnement (mais LANGest défini sur en_GB.UTF-8). Avec le code ci-dessus, j'obtiens l'erreur "sed: 1:" y / ۰۱۲۳۴۵۶۷۸۹ / ... ": les chaînes de transformation ne sont pas de la même longueur".
Konrad Rudolph
15

Pour Python, il existe une unidecodebibliothèque qui gère de telles conversions en général: 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'

Le thread SO à /programming//q/8087381/2261442 peut être lié.

/ edit: Comme Wander Nauta l'a souligné dans les commentaires et comme mentionné sur la page Unidecode, il existe également une version shell de unidecode(sous /usr/local/bin/si installée sur pip):

$ echo '۰۱۲۳۴۵۶۷۸۹' | unidecode
0123456789
phk
la source
2
La bibliothèque unidecode fournit également un utilitaire appelé (sans surprise) unidecodequi fait la même chose que votre extrait Python 3. Devrait juste echo '۰۱۲۳۴۵۶۷۸۹' | unidecodefonctionner.
Wander Nauta
@Wander - le paquet Debian de python-unidecode ne livre pas le programme utilitaire, donc la forme longue peut être nécessaire sur de telles plateformes (je n'en ai pas trouvé un dans l'archive source en amont, donc peut-être que le programme est quelque chose ajouté par votre distribution?)
Toby Speight
@TobySpeight Si vous l'installez en utilisant pipc'est là.
phk
@TobySpeight L'utilitaire est dans l'archive amont en tant que unidecode/util.py- étrange que Debian ne l'inclue pas. (Edit: Ah, mystère résolu. Le paquet Debian est obsolète et plus ancien que l'utilitaire.)
Wander Nauta
7

Une version pure 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"

J'ai testé sur ma machine Gentoo et ça marche.

./convert ۱۳۲
Result is 132

Fait en boucle, compte tenu de la liste des caractères (de 0 à 9) à 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"

Et utilisé comme:

$ convert ۱۳۲
132

Une autre façon (plutôt exagérée) d'utiliser 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"
coffeMug
la source
1
Pure Bash, sauf pour le grep. En fait, je ne comprends pas cette ligne, ni pourquoi vous ne définissez pas result=0. Êtes-vous trop prudent au cas où il $1contiendrait autre chose que des chiffres farsi?
Kusalananda
@Kusalananda cette ligne lit les chiffres Farsi en nums. Le rend bouclable.
coffeMug
1
Dix substitutions simples auraient été plus rapides ... number=${number//۱/1}etc., et auraient évité le echoet grep.
Kusalananda
1
@Kusalananda Nice. Je l'ai changé. Maintenant c'est du pur Bash! ;-)
coffeMug
@coffeMug: ۱۳۲ est 132 no 123: D
بارپابابا
3

Puisqu'il iconvne semble pas y avoir de problème, le prochain port d'escale serait d'utiliser l' trutilitaire:

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

tr traduit un ensemble de caractères en un autre, nous lui demandons donc simplement de traduire l'ensemble de chiffres farsi en ensemble de chiffres latins.

EDIT : Comme le souligne l'utilisateur @cuonglm. Cela nécessite non-GNU tr, par exemple trsur un Mac, et cela nécessite également qu'il $LC_CTYPEsoit défini sur en_US.UTF-8.

Kusalananda
la source
2
Notez qu'il ne fonctionnera pas avec GNU tr, qui ne prend pas en charge les caractères multi-octets.
cuonglm
1
Oh mon. Silly GNU. ;-)
Kusalananda
Et vous devez également définir vos paramètres régionaux sur celui qui prend en charge l'unicode, comme en_US.utf8.
cuonglm