Wie kann ich persische Ziffern in UTF-8 in europäische Ziffern in ASCII konvertieren?

16

In persischen Ziffern ۰۱۲۳۴۵۶۷۸۹entspricht 0123456789in europäischen Ziffern.

Wie kann ich persische Zahlen (in UTF-8) in ASCII konvertieren ?

Zum Beispiel möchte ich ۲۱werden 21.

بارپابابا
quelle
1
Interessant, es scheint echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLITnicht damit
umzugehen
@ Kusalananda NICHT funktioniert
بارپابابا
3
@ Kusalananda: Ist es wirklich so unerwartet? So wie ich es verstanden habe, iconvist es nur hier, um Zeichen in verschiedenen Codierungen abzubilden, aber dies sind Zeichen (ostarabische Ziffern), die kein Äquivalent in ASCII haben. Sie können sie einfach in etwas Ähnliches konvertieren, aber es ist nur in eine Richtung.
Phk
3
Nun, ich war mir nicht ganz sicher, wozu ich iconvfähig und nicht fähig war. Ich hatte gehofft //TRANSLIT, dass das nicht helfen würde, aber das tat es nicht.
Kusalananda
1
Müssen Sie die Bestellung auch stornieren? Ich weiß, dass arabische Ziffern von rechts nach links nach dem Little-Endian-Prinzip geschrieben sind und lateinische Ziffern von links nach rechts nach dem Big-Endian-Prinzip (im Druck oder auf dem Bildschirm ähnlich, im Gedächtnis jedoch umgekehrt). Ist Persisch dasselbe?
Toby Speight

Antworten:

6

Wir können die Tatsache ausnutzen, dass die UNICODE-Codepunkte der persischen Ziffern fortlaufend sind und von 0 bis 9 geordnet sind :

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

Das bedeutet, dass die letzte Hex-Ziffer der Dezimalwert ist:

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

Das macht diese einfache Schleife zu einem Konvertierungswerkzeug:

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

Verwenden Sie es als:

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

$ sefr.sh ۲۰۱
201

$ sefr.sh ۲۱
21

Beachten Sie, dass dieser Code auch arabische und lateinische Ziffern konvertieren kann (auch wenn gemischt):

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

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

quelle
Sehr, sehr, danke, das ist eine sehr schöne Lösung, und ich habe die Frage, in diesem Befehl printf '% d' '"۰' warum doppelte Anführungszeichen verwenden?
بارپابابا
@Babyy Es ist keine doppelten Anführungszeichen, es ist eine Art und Weise zu geben printf ein Argument , das mit einem Apostroph beginnen: . Es hätte auch so geschrieben werden können '"۰'. Der Grund dafür ist, dass printf den UNICODE-Codepunkt angibt, wenn das Argument mit einem einfachen Anführungszeichen 'oder einem doppelten Anführungszeichen beginnt ". Suchen Sie kurz vor diesem Link nach dem Text "Wenn das führende Zeichen ein einfaches oder doppeltes Anführungszeichen ist"
@Babyy Der Code wurde erweitert, um Persisch, Arabisch und Latein zu konvertieren (auch wenn gemischt).
27

Da es sich um eine feste Anzahl von Zahlen handelt, können Sie dies von Hand tun:

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

(oder mit tr, aber noch nicht GNU tr )

Um Ihren en_US.utf8Zeichensatz sedzu erkennen, müssen Sie Ihr Gebietsschema auf (oder besser auf das Gebietsschema, zu dem der Zeichensatz gehört) einstellen.

Mit perl:

$ echo "۲۱" |
  perl -CS -MUnicode::UCD=num -MUnicode::Normalize -lne 'print num(NFKD($_))'
21
cuonglm
quelle
Das muss LC_ALLso eingestellt werden, dass jedes einzelne Unicode-Zeichen auch von als solches betrachtet wird sed, oder?
Phk
@phk: Ja, siehe die Aktualisierung.
Donnerstag,
Warum muss alles ein sed-Skript sein? Haben wir nicht trgenau zu diesem Zweck erfunden ?
Kevin
3
@ Kevin Siehe die andere Antwort, in der tres darum geht, wie es nicht überall funktioniert. Denken Sie auch daran, dass einige Tools für den Umgang mit Bytes optimiert sind, während andere für den Umgang mit Zeichen optimiert sind. Mit Unicode (insbesondere UTF-8) macht dies einen großen Unterschied.
Phk
Dies funktioniert unter OS X 10.10.5 / GNU bash 4.3 nicht. Komischerweise muss ich die explizite Einstellung von entfernenLC_ALL . LC_ALList auch nicht in meiner Umgebung festgelegt (aber LANGfestgelegt auf en_GB.UTF-8). Mit dem obigen Code erhalte ich die Fehlermeldung "sed: 1:" y / ۰۱۲۳۴۵۶۷۸۹ / ... ": Transformationszeichenfolgen haben nicht die gleiche Länge".
Konrad Rudolph
15

Für Python gibt es die unidecodeBibliothek, die solche Konvertierungen im Allgemeinen verwaltet: 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'

Der SO-Thread unter /programming//q/8087381/2261442 könnte verwandt sein.

/ edit: Wie Wander Nauta zeigte in den Kommentaren und wie auf der Unidecode Seite erwähnt , gibt es auch eine Shell - Version unidecode(unter /usr/local/bin/falls installiert über pip):

$ echo '۰۱۲۳۴۵۶۷۸۹' | unidecode
0123456789
phk
quelle
2
Die Unidecode-Bibliothek enthält auch ein Hilfsprogramm mit dem Namen (nicht überraschend) unidecode, das dasselbe wie Ihr Python 3-Snippet ausführt. Sollte einfach echo '۰۱۲۳۴۵۶۷۸۹' | unidecodefunktionieren.
Wander Nauta
@Wander - Das Debian-Paket von python-unidecode liefert das Hilfsprogramm nicht aus, so dass die lange Form auf solchen Plattformen möglicherweise erforderlich ist (ich habe keine im Quell-Tarball von oben gefunden, also ist das Programm möglicherweise etwas, das von hinzugefügt wurde Ihre Verteilung?)
Toby Speight
@TobySpeight Wenn Sie es mithilfe von installieren, ist pipes dort.
Phk
@TobySpeight Das Dienstprogramm befindet sich im Upstream-Tarball als unidecode/util.py- seltsam, dass Debian es nicht enthält. (Edit: Ah, das Rätsel ist gelöst. Das Debian-Paket ist veraltet und älter als das Hilfsprogramm.)
Wander Nauta,
7

Eine reine Bash-Version:

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

Habe in meinem Gentoo Rechner getestet und es funktioniert.

./convert ۱۳۲
Result is 132

Als Schleife ausgeführt, mit der Liste der zu konvertierenden Zeichen (von 0 bis 9):

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

Und verwendet als:

$ convert ۱۳۲
132

Ein anderer (eher übertriebener) Weg mit 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"
Kaffeebecher
quelle
1
Pure Bash, bis auf die grep. Tatsächlich verstehe ich diese Zeile nicht und auch nicht, warum Sie sie nicht setzen result=0. Sind Sie übervorsichtig, wenn es sich $1nicht nur um Farsi-Ziffern handelt?
Kusalananda
@ Kusalananda diese Zeile liest die Farsi-Ziffern in Zahlen. Macht es Endlos wiederholbar.
coffeMug
1
Zehn einfache Ersetzungen wären schneller gewesen ... number=${number//۱/1}usw. und würden das echound vermeiden grep.
Kusalananda
1
@ Kusalananda Schön. Änderte es. Jetzt ist es pure Bash! ;-)
coffeMug
@coffeMug: ۱۳۲ ist 132 no 123: D
بارپابابا
3

Da iconvdies nicht zu befürchten scheint, wäre die nächste Anlaufstelle die Verwendung des trDienstprogramms:

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

tr Übersetzt einen Zeichensatz in einen anderen, so dass wir ihn einfach anweisen, den Satz der persischen Ziffern in den Satz der lateinischen Ziffern zu übersetzen.

EDIT : Als Benutzer @cuonglm weist darauf hin. Dies erfordert Nicht-GNU tr, z. B. trauf einem Mac, und es erfordert auch, dass auf festgelegt $LC_CTYPEist en_US.UTF-8.

Kusalananda
quelle
2
Beachten Sie, dass es nicht mit GNU tr funktioniert, das keine Multi-Byte-Zeichen unterstützt.
Cuonglm
1
Oh mein. Dumme GNU. ;-)
Kusalananda
Außerdem müssen Sie Ihr Gebietsschema auf das Gebietsschema einstellen, das Unicode unterstützt, z en_US.utf8.
Donnerstag,