Bagaimana saya bisa mengkonversi angka Persia di UTF-8 ke angka Eropa di ASCII?

16

Dalam angka Persia, ۰۱۲۳۴۵۶۷۸۹sama dengan 0123456789angka di Eropa.

Bagaimana saya bisa mengonversi nomor Persia UTF-8menjadi ASCII?

Misalnya, saya ingin ۲۱menjadi 21.

بارپابابا
sumber
1
Menarik, sepertinya echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLITtidak menanganinya ...
Kusalananda
@Kusalananda TIDAK bekerja
بارپابابا
3
@ Kusalananda: Benarkah itu tidak terduga? Seperti yang saya pahami, iconvini hanya di sini untuk memetakan karakter dalam pengkodean yang berbeda, tetapi ini adalah karakter (angka Arab Timur) yang tidak memiliki padanan dalam ASCII, Anda bisa mengonversinya menjadi sesuatu yang cukup serupa tetapi hanya satu arah.
phk
3
Yah, saya tidak yakin apa iconvyang mampu dan tidak mampu dilakukan. Saya berharap menggunakan itu //TRANSLITakan membantu, tetapi ternyata tidak.
Kusalananda
1
Apakah Anda juga perlu membalik urutan? Saya tahu bahwa angka-angka Arab ditulis sedikit-endian kanan-ke-kiri, dan angka Latin adalah big-endian kiri-ke-kanan (tampak serupa di cetak atau di layar, tetapi terbalik dalam memori). Apakah Persia sama?
Toby Speight

Jawaban:

6

Kita dapat memanfaatkan fakta bahwa titik kode UNICODE angka Persia berturut - turut dan dipesan dari 0 hingga 9 :

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

Itu berarti bahwa digit hex terakhir adalah nilai desimal:

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

Itu membuat loop sederhana ini alat konversi:

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

Menggunakannya sebagai:

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

$ sefr.sh ۲۰۱
201

$ sefr.sh ۲۱
21

Perhatikan bahwa kode ini juga dapat mengonversi angka Arab dan Latin (bahkan jika dicampur):

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

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

sumber
terima kasih banyak, ini solusi yang sangat bagus ,, dan saya punya pertanyaan ,, dalam perintah ini printf '% d' '"۰' mengapa menggunakan kutip ganda?
بارپابابا
@Babyy Ini bukan kutip ganda, itu adalah cara untuk memberi printf argumen yang dimulai dengan satu kutipan: . Itu bisa ditulis juga sebagai '"۰'. Alasannya adalah printf akan memberikan titik kode UNICODE jika argumen dimulai dengan penawaran tunggal 'atau penawaran ganda ". Cari sedikit sebelum tautan ini untuk teks "Jika karakter utama adalah kutipan tunggal atau kutipan ganda"
@Babyy Kode telah diperluas untuk mengonversi bahasa Persia, Arab, dan Latin (bahkan jika dicampur).
27

Karena ini adalah kumpulan angka yang tetap, Anda dapat melakukannya dengan tangan:

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

(atau menggunakan tr, tetapi belum GNU tr )

Menyetel lokal Anda ke en_US.utf8(atau lebih baik ke lokal yang set karakternya milik) diperlukan untuk sedmengenali set karakter Anda.

Dengan perl:

$ echo "۲۱" |
  perl -CS -MUnicode::UCD=num -MUnicode::Normalize -lne 'print num(NFKD($_))'
21
cuonglm
sumber
Mengatur LC_ALLdiperlukan agar setiap karakter unicode tunggal juga akan dianggap seperti itu oleh sed, kan?
phk
@phk: Ya, lihat pembaruan.
cuonglm
Mengapa semuanya harus menjadi skrip sed? Bukankah kita menciptakan truntuk tujuan yang tepat ini?
Kevin
3
@Kevin Lihat jawaban lain yang melibatkan trbagaimana itu tidak berfungsi di mana-mana Juga perlu diingat bahwa beberapa alat dioptimalkan untuk menangani byte, sementara yang lain untuk berurusan dengan karakter, dengan Unicode (terutama UTF-8) ini membuat perbedaan besar.
phk
Ini tidak berfungsi untuk saya di OS X 10.10.5 / GNU bash 4.3. Cukup aneh saya perlu menghapus pengaturan eksplisit LC_ALL. LC_ALLjuga tidak diatur di lingkungan saya (tetapi LANGdiatur ke en_GB.UTF-8). Dengan kode di atas, saya mendapatkan kesalahan "sed: 1:" y / ۰۱۲۳۴۵۶۷۸۹ / ... ": mengubah string tidak sama panjangnya".
Konrad Rudolph
15

Untuk Python ada unidecode perpustakaan yang menangani konversi seperti itu secara umum: https://pypi.python.org/pypi/Unidecode .

Dengan Python 2:

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

Dengan Python 3:

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

Utas SO di /programming//q/8087381/2261442 mungkin terkait.

/ edit: Seperti yang ditunjukkan oleh Wander Nauta dalam komentar dan seperti yang disebutkan pada halaman Unidecode, ada juga versi shell unidecode(di bawah /usr/local/bin/jika diinstal pip):

$ echo '۰۱۲۳۴۵۶۷۸۹' | unidecode
0123456789
phk
sumber
2
Pustaka unidecode juga mengirimkan utilitas yang disebut (tidak mengejutkan) unidecodeyang melakukan hal yang sama dengan cuplikan Python 3 Anda. Hanya echo '۰۱۲۳۴۵۶۷۸۹' | unidecodeharus bekerja.
Mengembara Nauta
@ Mengembara - paket Debian python-unidecode tidak mengirimkan program utilitas, jadi formulir panjang mungkin diperlukan pada platform tersebut (saya tidak menemukan satu di tarball sumber dari hulu, jadi mungkin program ini adalah sesuatu yang ditambahkan oleh distribusi Anda?)
Toby Speight
@TobySpeight Jika Anda menginstalnya menggunakannya pipdi sana.
phk
@TobySpeight Utilitas ini ada di tarball hulu unidecode/util.py- aneh bahwa Debian tidak memasukkannya. (Sunting: Ah, misteri terpecahkan. Paket Debian kedaluwarsa dan lebih tua dari utilitas.)
Wander Nauta
7

Versi bash murni:

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

Telah diuji di mesin Gentoo saya dan berhasil.

./convert ۱۳۲
Result is 132

Dilakukan sebagai loop, diberi daftar karakter (dari 0 hingga 9) untuk dikonversi:

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

Dan digunakan sebagai:

$ convert ۱۳۲
132

Cara lain (agak berlebihan) menggunakan 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
sumber
1
Bash Murni, kecuali untuk grep. Sebenarnya, saya tidak mengerti kalimat itu, atau mengapa Anda tidak mengaturnya result=0. Apakah Anda terlalu berhati-hati seandainya $1berisi hal-hal lain selain digit Farsi?
Kusalananda
@ Kusalananda bahwa garis membaca angka-angka Farsi menjadi num. Membuatnya bisa diulang.
coffeMug
1
Sepuluh pergantian sederhana akan lebih cepat ... number=${number//۱/1}dll, dan akan menghindari echodan grep.
Kusalananda
1
@ Kusalananda Bagus. Mengubahnya. Sekarang murni Bash! ;-)
coffeMug
@coffeMug: ۱۳۲ adalah 132 no 123: D
بارپابابا
3

Karena iconvsepertinya tidak bisa melakukan ini, porta panggilan selanjutnya adalah menggunakan trutilitas:

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

tr menerjemahkan satu set karakter ke yang lain, jadi kami hanya mengatakannya untuk menerjemahkan set angka Farsi ke set angka Latin.

EDIT : Seperti yang ditunjukkan pengguna @cuonglm. Ini memerlukan non-GNU tr, misalnya pada trpada Mac, dan itu juga mengharuskan $LC_CTYPEdiatur ke en_US.UTF-8.

Kusalananda
sumber
2
Perhatikan bahwa ini tidak akan berfungsi dengan GNU tr, yang tidak mendukung karakter multi-byte.
cuonglm
1
Astaga. GNU yang konyol. ;-)
Kusalananda
Dan juga Anda perlu mengatur lokal Anda ke yang mendukung unicode, seperti en_US.utf8.
cuonglm