Come posso convertire i numeri persiani in UTF-8 in numeri europei in ASCII?


16

In cifre persiane, ۰۱۲۳۴۵۶۷۸۹è equivalente a 0123456789cifre europee.

Come posso convertire il numero persiano (in UTF-8) in ASCII?

Ad esempio, voglio ۲۱diventare 21.


1
Interessante, sembra che echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLITnon lo gestisca ...
Kusalananda

@Kusalananda NON ha funzionato
بارپابابا,

3
@Kusalananda: è davvero così inaspettato? Come ho capito, iconvè qui solo per mappare i caratteri in diverse codifiche, ma questi sono caratteri (numeri arabi orientali) che non hanno equivalenti in ASCII, puoi semplicemente convertirli in qualcosa di abbastanza simile ma è solo a senso unico.
phk,

3
Beh, non ero abbastanza sicuro di cosa iconvfosse capace e cosa non capace di fare. Speravo che l'uso di questo //TRANSLITavrebbe aiutato, ma non è stato così.
Kusalananda

1
Devi anche invertire l'ordine? So che i numeri arabi sono scritti da little endian da destra a sinistra, e i numeri latini sono da big endian da sinistra a destra (simili nella stampa o sullo schermo, ma invertiti nella memoria). Il persiano è lo stesso?
Toby Speight,

Risposte:


6

Possiamo trarre vantaggio dal fatto che il punto di codice UNICODE dei numeri persiani è consecutivo e ordinato da 0 a 9 :

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

Ciò significa che l'ultima cifra esadecimale È il valore decimale:

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

Ciò rende questo semplice ciclo uno strumento di conversione:

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

Usandolo come:

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

$ sefr.sh ۲۰۱
201

$ sefr.sh ۲۱
21

Nota che questo codice potrebbe anche convertire numeri arabi e latini (anche se mescolati):

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

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

grazie
mille

@Babyy Non è una doppia citazione, è un modo per dare printf un argomento che inizia con una sola offerta: . Avrebbe potuto essere scritto anche come '"۰'. Il motivo è che printf fornirà il punto di codice UNICODE se l'argomento inizia con una virgoletta singola 'o una virgoletta doppia ". Cerca un po ' prima di questo link per il testo "Se il personaggio principale è una virgoletta singola o doppia virgoletta"

@Babyy Il codice è stato esteso per convertire persiano, arabo e latino (anche se misto).

27

Poiché è un insieme di numeri fisso, puoi farlo a mano:

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

(o usando tr, ma non GNU tr ancora)

Impostando la propria locale su en_US.utf8 (o meglio le impostazioni internazionali a cui appartiene il set di caratteri) è necessaria per sedriconoscere il set di caratteri.

Con perl:

$ echo "۲۱" |
  perl -CS -MUnicode::UCD=num -MUnicode::Normalize -lne 'print num(NFKD($_))'
21

L'impostazione LC_ALLè necessaria in modo che ogni singolo carattere unicode venga considerato come tale da sed, giusto?
phk,

@phk: Sì, vedi l'aggiornamento.
cuonglm

Perché tutto deve essere uno script sed? Non abbiamo inventato trper questo preciso scopo?
Kevin,

3
@Kevin Vedi l'altra risposta che riguarda il trmodo in cui non funziona ovunque. Inoltre, tieni presente che alcuni strumenti sono ottimizzati per gestire i byte, mentre altri lo sono per i personaggi, con Unicode (specialmente UTF-8) questo fa una grande differenza.
phk,

Questo non funziona per me su OS X 10.10.5 / GNU bash 4.3. Stranamente, devo rimuovere l'impostazione esplicita di LC_ALL. LC_ALLinoltre non è impostato nel mio ambiente (ma LANGè impostato su en_GB.UTF-8). Con il codice sopra riportato, viene visualizzato l'errore "sed: 1:" y / ۰۱۲۳۴۵۶۷۸۹ / ... ": le stringhe di trasformazione non hanno la stessa lunghezza".
Konrad Rudolph,

15

Per Python esiste la unidecodelibreria che gestisce tali conversioni in generale: https://pypi.python.org/pypi/Unidecode .

In Python 2:

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

In Python 3:

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

Il thread SO su /programming//q/8087381/2261442 potrebbe essere correlato.

/ modifica: come ha sottolineato Wander Nauta nei commenti e come menzionato nella pagina Unidecode, esiste anche una versione shell di unidecode(sotto /usr/local/bin/se installato sopra pip):

$ echo '۰۱۲۳۴۵۶۷۸۹' | unidecode
0123456789

2
La libreria unidecode fornisce anche un'utilità chiamata (non sorprende) unidecodeche fa lo stesso dello snippet di Python 3. echo '۰۱۲۳۴۵۶۷۸۹' | unidecodeDovrebbe solo funzionare.
Passeggia per Nauta il

@Wander - il pacchetto Debian di python-unidecode non contiene il programma di utilità, quindi la forma lunga potrebbe essere necessaria su tali piattaforme (non ne ho trovato uno nel tarball sorgente da upstream, quindi forse il programma è qualcosa di aggiunto da la tua distribuzione?)
Toby Speight,

@TobySpeight Se lo installi usando pipè lì.
phk,

@TobySpeight L'utilità è nel tarball upstream come unidecode/util.py- strano che Debian non lo includa. (Modifica: Ah, mistero risolto. Il pacchetto Debian è obsoleto e più vecchio dell'utilità.)
Wander Nauta,

7

Una versione bash pura:

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

Ho testato sulla mia macchina Gentoo e funziona.

./convert ۱۳۲
Result is 132

Fatto come un ciclo, dato l'elenco di caratteri (da 0 a 9) da convertire:

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

E usato come:

$ convert ۱۳۲
132

Un altro modo (piuttosto eccessivo) 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"

1
Pure Bash, ad eccezione di grep. In realtà, non capisco quella linea, né perché non si imposta result=0. Sei troppo cauto nel caso $1contenga cose diverse dalle cifre del farsi?
Kusalananda

@Kusalananda quella riga legge le cifre del farsi in numeri. Lo rende in grado di loop.
coffeug

1
Dieci semplici sostituzioni sarebbero state più veloci ... number=${number//۱/1}ecc., E avrebbero evitato echoe grep.
Kusalananda

1
@Kusalananda Nice. Modificato Ora è puro Bash! ;-)
coffeMug

@coffeMug: ۱۳۲ è 132 no 123: D
بارپابابا

3

Dato che iconvnon riesco a grok questo, la prossima porta di chiamata sarebbe usare l' trutilità:

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

tr traduce un set di caratteri in un altro, quindi diciamo semplicemente di tradurre il set di cifre Farsi nel set di cifre latine.

EDIT : come sottolinea l'utente @cuonglm. Ciò richiede non GNU tr, ad esempio trsu un Mac, e richiede anche che $LC_CTYPEsia impostato su en_US.UTF-8.


2
Nota che non funzionerà con GNU tr, che non supporta i caratteri multi-byte.
cuonglm,

1
Oh mio. GNU sciocco. ;-)
Kusalananda

Inoltre, è necessario impostare le impostazioni internazionali su quella che supporta Unicode, ad esempio en_US.utf8.
cuonglm
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.