Як можна перетворити персийські цифри в UTF-8 на європейські цифри в ASCII?


16

У персидських цифрах ۰۱۲۳۴۵۶۷۸۹еквівалентний 0123456789європейським цифрам .

Як я можу перетворити перський номер (в UTF-8) в ASCII?

Наприклад, я хочу ۲۱стати 21.


1
Цікаво, схоже, echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLITце не справляється ...
Kusalananda

@Kusalananda НЕ працював
بارپابابا

3
@Kusalananda: Невже це несподівано? Як я зрозумів iconv, саме тут потрібно відображати символи в різних кодуваннях, але це символи (східноарабські цифри), які не мають еквівалента в ASCII, ви можете просто перетворити їх на щось досить схоже, але це лише в одну сторону.
phk

3
Ну, я не був зовсім впевнений, на що iconvздатний і не здатний. Я сподівався, що використання фотографії //TRANSLITдопоможе, але це не вдалося.
Kusalananda

1
Вам також потрібно скасувати замовлення? Я знаю, що арабські цифри пишуться мало-ендіанськими справа наліво, а латинські цифри - великі-ендіанці зліва направо (схожі на друк чи на екрані, але обернені в пам'яті). Чи є перська мова однаковою?
Toby Speight

Відповіді:


6

Ми можемо скористатися тим, що кодова точка UNICODE з персидських цифр є послідовною і впорядкована від 0 до 9 :

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

Це означає, що останній шістнадцятковий раз є десятковим значенням:

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

Це робить цей простий цикл інструментом перетворення:

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

Використовуючи його як:

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

$ sefr.sh ۲۰۱
201

$ sefr.sh ۲۱
21

Зауважте, що цей код також може перетворити арабські та латинські цифри (навіть якщо їх змішати):

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

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

дуже дякую, це дуже приємне рішення ,, і у мене є питання ,, у цій команді printf '% d' '"۰' навіщо використовувати подвійну котирування?
بارپابابا

@Babyy Це не подвійні лапки, це спосіб дати Printf аргумент , що почати з однієї цитати: . Це могло бути написане також як '"۰'. Причина полягає в тому, що printf надасть код коду UNICODE, якщо аргумент починається з одиничної 'або подвійної лапки ". Шукайте перед цим посиланням текст "Якщо головним персонажем є

@Babyy Код було розширено для перетворення перської, арабської та латинської (навіть якщо їх змішано).

27

Оскільки це фіксований набір чисел, ви можете це зробити вручну:

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

(або з використанням tr, але ще не GNU tr )

Щоб визнати ваш набір символів, потрібно встановити локальну en_US.utf8(а краще локальну, до якої належить sedнабір символів).

З perl:

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

LC_ALLПотрібно встановити знаки так, щоб усі символи унікоду також вважалися такими sed, чи не так?
phk

@phk: Так, дивіться оновлення.
cuonglm

Чому все має бути сценарієм sed? Хіба ми не вигадували trсаме цю мету?
Кевін

3
@Kevin Дивіться іншу відповідь про trте, як вона працює не скрізь. Також пам’ятайте, що деякі інструменти оптимізовані для роботи з байтами, а інші - для роботи з символами, а Unicode (особливо UTF-8) це робить величезну різницю.
phk

Це не працює для мене в ОС X 10.10.5 / GNU bash 4.3. Як не дивно, мені потрібно видалити явні налаштування LC_ALL. LC_ALLтакож не встановлено в моєму середовищі (але LANGвстановлено en_GB.UTF-8). З наведеним вище кодом я отримую помилку "sed: 1:" y / ۰۱۲۳۴۵۶۷۸۹ / ... ": рядки перетворення не однакової довжини".
Конрад Рудольф

15

Для Python існує unidecodeбібліотека, яка взагалі обробляє такі перетворення: https://pypi.python.org/pypi/Unidecode .

У Python 2:

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

У Python 3:

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

Нитка SO на /programming//q/8087381/2261442 може бути пов’язана.

/ редагувати: Як в коментарях зауважив Вандер Наута і як зазначено на сторінці Unidecode, існує також версія оболонки unidecode/usr/local/bin/випадку, якщо встановлено понад pip):

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

2
Бібліотека unidecode також постачає утиліту (не дивно), unidecodeяка робить те саме, що і ваш фрагмент Python 3. Просто echo '۰۱۲۳۴۵۶۷۸۹' | unidecodeтреба працювати.
Блукати Наута

@Wander - пакет Debian python-unidecode не постачає утиліту, тому на таких платформах може знадобитися довга форма (я не знайшов її у вихідному tarball з висхідного потоку, тому, можливо, програма щось додала ваш розповсюдження?)
Toby Speight

@TobySpeight Якщо встановити його за допомогою pipйого там.
phk

@TobySpeight Утиліта знаходиться у верховій тарболі як unidecode/util.py- дивно, що Debian не включає її. (Редагувати: Ах, таємниця вирішена. Пакет Debian застарів і застарілий, ніж утиліта.)
Блукайте Наута

7

Чистий баш-версія:

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

Випробували в моїй машині Gentoo і вона працює.

./convert ۱۳۲
Result is 132

Виконано у вигляді циклу з урахуванням списку символів (від 0 до 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"

І використовується як:

$ convert ۱۳۲
132

Інший (швидше завищений) спосіб використання 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
Чистий Баш, крім самого grep. Насправді я не розумію цієї лінії, і чому ви її не встановите result=0. Ви надто обережні, якщо вони $1містять інші речі, крім цифр фарсі?
Kusalananda

@Kusalananda цей рядок зчитує цифри фарсі в цифри. Робить це циклом.
coffeMug

1
Десять простих замін було б швидше ... number=${number//۱/1}тощо, і уникнуло б echoі grep.
Кусалаланда

1
@Kusalananda Nice. Змінив це. Тепер це чистий Баш! ;-)
coffeMug

@coffeMug: ۱۳۲ це 132 ні 123: D
بارپابابا

3

Оскільки, iconvздається, це не можливо, наступним портом дзвінка буде використання trутиліти:

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

tr переводить один набір символів в інший, тому ми просто кажемо йому перекласти набір цифр фарсі на набір латинських цифр.

EDIT : Як вказує користувач @cuonglm. Для цього потрібні не GNU tr, наприклад, trна Mac, а також потрібно, що $LC_CTYPEвстановлено en_US.UTF-8.


2
Зауважте, що він не працюватиме з GNU tr, який не підтримує багатобайтові символи.
cuonglm

1
О Боже. Дурний ГНУ. ;-)
Kusalananda

А також вам потрібно встановити локаль на той, який підтримує unicode, наприклад en_US.utf8.
cuonglm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.