Как я могу преобразовать персидские цифры в UTF-8 в европейские цифры в ASCII?

16

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

Как я могу конвертировать персидское число (в UTF-8) в ASCII?

Например, я хочу ۲۱стать 21.

بارپابابا
источник
1
Интересно, кажется echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLIT, не справляется ...
Кусалананда
@Kusalananda НЕ работал
بارپابابا
3
@Kusalananda: это действительно так неожиданно? Как я понял, это iconvпросто здесь для отображения символов в разных кодировках, но это символы (восточно-арабские цифры), которые не имеют эквивалента в ASCII, вы можете просто преобразовать их во что-то достаточно похожее, но это только односторонний характер.
phk
3
Ну, я не совсем знал, на что iconvспособен и не способен делать. Я надеялся, что //TRANSLITэто поможет, но это не помогло.
Кусалананда
1
Вам также нужно изменить заказ? Я знаю, что арабские цифры пишутся с прямым порядком байтов справа налево, а латинские цифры - с прямым порядком слева направо (выглядят одинаково в печати или на экране, но обращены в памяти). Персидский это то же самое?
Тоби Спейт

Ответы:

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 )

Установка вашей локали en_US.utf8(или, что лучше, локали, которой принадлежит набор символов) необходима для sedраспознавания набора символов.

С perl:

$ echo "۲۱" |
  perl -CS -MUnicode::UCD=num -MUnicode::Normalize -lne 'print num(NFKD($_))'
21
cuonglm
источник
Установка LC_ALLнеобходима для того, чтобы каждый отдельный символ Юникода также рассматривался как таковой sed, верно?
phk
@phk: Да, смотрите обновление.
cuonglm
Почему все должно быть сценарием sed? Разве мы не изобрели trдля этой конкретной цели?
Кевин
3
@Kevin Смотрите другой ответ, касающийся trтого, как он не работает везде. Также имейте в виду, что некоторые инструменты оптимизированы для работы с байтами, в то время как другие предназначены для работы с символами, с Unicode (особенно UTF-8) это имеет огромное значение.
phk
Это не работает для меня на OS 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 может быть связан.

/ edit: Как отметил Вандер Наута в комментариях и как уже упоминалось на странице Unidecode, есть также версия оболочки unidecode/usr/local/bin/случае установки поверх pip):

$ echo '۰۱۲۳۴۵۶۷۸۹' | unidecode
0123456789
PHK
источник
2
Библиотека unidecode также поставляет утилиту под названием (что неудивительно), unidecodeкоторая делает то же самое, что и ваш фрагмент Python 3. Просто echo '۰۱۲۳۴۵۶۷۸۹' | unidecodeдолжно работать.
Бродить Наута
@Wander - пакет Debian python-unidecode не поставляется с утилитой, поэтому на таких платформах может потребоваться длинная форма (я не нашел ее в исходном архиве из апстрима, поэтому, возможно, программа была добавлена ваш дистрибутив?)
Тоби Спейт
@TobySpeight Если вы установите его, используя pipего там.
phk
@TobySpeight Утилита находится в вышестоящем архиве как unidecode/util.py- странно, что Debian не включает его. (Правка: Ах, загадка раскрыта. Пакет Debian устарел и старше, чем утилита.)
Wander Nauta
7

Чистая версия 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"

Протестировал на моей машине 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"
coffeMug
источник
1
Чистый Баш, кроме grep. На самом деле, я не понимаю ни этой строки, ни того, почему вы не задаете result=0. Вы слишком осторожны, если в нем $1есть что-то кроме цифр фарси?
Кусалананда
@Kusalananda эта строка читает цифры фарси в числах. Делает это петлевым.
CoffeMug
1
Десять простых замен были бы быстрее ... number=${number//۱/1}и т. Д., И избегали бы echoи grep.
Кусалананда
1
@Kusalananda Отлично. Поменял это. Теперь это чистый Баш! ;-)
coffeMug
@coffeMug: ۱۳۲ - это 132, а не 123: D
بارپابابا
3

Так iconvкак кажется, что это невозможно, следующим портом захода будет использование trутилиты:

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

tr переводит один набор символов в другой, поэтому мы просто просим его перевести набор цифр фарси в набор латинских цифр.

РЕДАКТИРОВАТЬ : Как пользователь @cuonglm указывает. Для этого требуется не-GNU tr, например, trна Mac, а также для $LC_CTYPEнего установлено значение en_US.UTF-8.

Кусалананда
источник
2
Обратите внимание, что он не будет работать с GNU tr, который не поддерживает многобайтовые символы.
cuonglm
1
Боже мой Глупый GNU. ;-)
Кусалананда
А также вам нужно установить свой язык, который поддерживает Unicode, например en_US.utf8.
cuonglm