如何将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
您还需要撤销订单吗?我知道阿拉伯数字从右到左是小尾数,拉丁数字是从左到右的大尾数(在打印或屏幕上看起来相似,但在内存上却相反)。波斯人一样吗?
Toby Speight

Answers:


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”““0'为什么要用双引号?
بارپابابا

@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需要设置,以便每个单个Unicode字符也将被视为sed,对吧?
phk

@phk:是的,请参阅更新。
cuonglm '16

为什么所有内容都必须是sed脚本?我们不是tr为此目的而发明的吗?
凯文

3
@Kevin请参阅另一个答案,tr该答案涉及它并非在所有地方都起作用。还请记住,某些工具针对处理字节进行了优化,而其他工具则针对处理字符进行了优化,使用Unicode(尤其是UTF-8)会产生很大的不同。
phk

在OS X 10.10.5 / GNU bash 4.3上,这对我不起作用。足够奇怪的是,我需要删除的显式设置LC_ALLLC_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'

/programming//q/8087381/2261442上的SO线程可能相关。

/ edit:正如Wander Nauta在评论中指出的那样,并且在Unidecode页面上提到的,还有一个shell版本unidecode/usr/local/bin/如果安装在,则在下面pip):

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

2
unidecode库还附带了一个名为(毫不奇怪)的实用程序unidecode,它与您的Python 3代码段相同。只是echo '۰۱۲۳۴۵۶۷۸۹' | unidecode应该工作。
Wander Nauta

@Wander-python-unidecode的Debian软件包没有提供实用程序,因此在这样的平台上可能需要使用长格式(我没有从上游的源tarball中找到一个,因此该程序是由您的分布?)
Toby Speight

@TobySpeight如果您使用pip它安装,请在那里。
phk

@TobySpeight该实用程序位于上游的tarball中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"

1
纯Bash,除了grep。实际上,我不理解该行,也不理解您为什么不设置result=0。如果$1包含波斯数字以外的内容,您是否过于谨慎?
库萨兰达

该行的@Kusalananda将波斯数字读成num。使它可循环。
coffeMug

1
十个简单的替换操作会更快... number=${number//۱/1}等等,并且可以避免使用echoand grep
库桑兰达

1
@Kusalananda尼斯。改了 现在是纯Bash!;-)
coffeMug16年

@coffeMug:132 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。;-)
Kusalananda

另外,您还需要将语言环境设置为支持unicode的语言环境,例如en_US.utf8
cuonglm '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.