Làm cách nào tôi có thể chuyển đổi các chữ số Ba Tư trong UTF-8 sang các chữ số châu Âu trong ASCII?


16

Trong các chữ số Ba Tư, ۰۱۲۳۴۵۶۷۸۹tương đương với 0123456789các chữ số châu Âu.

Làm cách nào tôi có thể chuyển đổi số Ba Tư (in UTF-8) sang ASCII?

Ví dụ, tôi muốn ۲۱trở thành 21.


1
Thật thú vị, có vẻ như echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLITkhông xử lý được ...
Kusalananda

@Kusalananda KHÔNG làm việc
بارپابابا

3
@Kusalananda: Có thật là bất ngờ không? Theo tôi hiểu, iconvđây chỉ là ở đây để ánh xạ các ký tự theo các bảng mã khác nhau, nhưng đây là các ký tự (chữ số Ả Rập Đông phương) không tương đương với ASCII, bạn chỉ có thể chuyển đổi chúng thành một thứ tương tự đủ nhưng chỉ là một chiều.
phk

3
Chà, tôi không chắc những gì iconvcó khả năng và không có khả năng làm. Tôi đã hy vọng rằng việc sử dụng //TRANSLITnó sẽ giúp ích, nhưng nó đã không làm được.
Kusalananda

1
Bạn cũng cần phải đảo ngược thứ tự? Tôi biết rằng các chữ số Ả Rập được viết từ cuối từ phải sang trái và chữ số Latinh là chữ cuối lớn từ trái sang phải (trông tương tự như in hoặc trên màn hình, nhưng ngược lại trong bộ nhớ). Ba Tư có giống nhau không?
Toby Speight

Câu trả lời:


6

Chúng ta có thể tận dụng thực tế là điểm mã UNICODE của các chữ số Ba Tư liên tiếp và được sắp xếp từ 0 đến 9 :

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

Điều đó có nghĩa là chữ số hex cuối cùng là giá trị thập phân:

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

Điều đó làm cho vòng lặp đơn giản này trở thành một công cụ chuyển đổi:

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

Sử dụng nó như:

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

$ sefr.sh ۲۰۱
201

$ sefr.sh ۲۱
21

Lưu ý rằng mã này cũng có thể chuyển đổi các chữ số Ả Rập và Latin (ngay cả khi được trộn lẫn):

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

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

rất rất cảm ơn, đây là giải pháp rất hay ,, và tôi có câu hỏi ,, trong lệnh này printf '% d' '"' tại sao lại sử dụng trích dẫn kép?
بارپابابا

@Babyy Đây không phải là một trích dẫn kép, đó là một cách để cung cấp cho printf một đối số bắt đầu bằng một trích dẫn duy nhất : . Nó có thể đã được viết cũng như '"۰'. Lý do là printf sẽ cho điểm mã UNICODE nếu đối số bắt đầu bằng một trích dẫn 'hoặc trích dẫn kép ". Tìm kiếm một chút trước khi liên kết này cho văn bản "Nếu ký tự đầu là một trích dẫn đơn hoặc trích dẫn kép"

@Babyy Mã đã được mở rộng để chuyển đổi tiếng Ba Tư, tiếng Ả Rập và tiếng Latin (ngay cả khi được trộn lẫn).

27

Vì đó là một bộ số cố định, bạn có thể thực hiện bằng tay:

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

(hoặc sử dụng tr, nhưng chưa có GNU tr )

Đặt ngôn ngữ của bạn thành en_US.utf8(hoặc tốt hơn cho miền địa phương mà bộ ký tự thuộc về) là bắt buộc sedđể nhận ra bộ ký tự của bạn.

Với perl:

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

Đặt cài đặt LC_ALLlà cần thiết để mỗi ký tự unicode cũng sẽ được xem xét như vậy sed, phải không?
phk

@phk: Vâng, xem cập nhật.
cuonglm

Tại sao tất cả mọi thứ phải là một kịch bản sed? Không phải chúng ta đã phát minh ra trcho mục đích chính xác này sao?
Kevin

3
@Kevin Xem câu trả lời khác liên quan đến trcách nó không hoạt động ở mọi nơi. Ngoài ra, hãy nhớ rằng một số công cụ được tối ưu hóa để xử lý byte trong khi các công cụ khác để xử lý các ký tự, với Unicode (đặc biệt là UTF-8), điều này tạo ra sự khác biệt rất lớn.
phk

Điều này không hoạt động với tôi trên OS X 10.10.5 / GNU bash 4.3. Thật kỳ lạ, tôi cần phải loại bỏ các thiết lập rõ ràng LC_ALL. LC_ALLcũng không được đặt trong môi trường của tôi (nhưng LANGđược đặt thành en_GB.UTF-8). Với đoạn mã trên, tôi nhận được lỗi Lỗi sed: 1: "y / / ...": chuỗi biến đổi không có cùng độ dài.
Konrad Rudolph

15

Đối với Python có unidecodethư viện xử lý các chuyển đổi như vậy nói chung: https://pypi.python.org/pypi/Unidecode .

Trong Python 2:

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

Trong Python 3:

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

Chủ đề SO tại /programming//q/8087381/2261442 có thể liên quan.

/ chỉnh sửa: Như Wander Nauta đã chỉ ra trong các bình luận và như đã đề cập trên trang Unidecode, cũng có một phiên bản shell của unidecode(dưới /usr/local/bin/nếu được cài đặt qua pip):

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

2
Thư viện unidecode cũng gửi một tiện ích có tên (không ngạc nhiên) unidecode, hoạt động tương tự như đoạn mã Python 3 của bạn. Chỉ echo '۰۱۲۳۴۵۶۷۸۹' | unidecodenên làm việc.
Đi lang thang Nauta

@ Wander - Gói python-unidecode Debian không cung cấp chương trình tiện ích, vì vậy hình thức dài có thể cần thiết trên các nền tảng như vậy (Tôi không tìm thấy một trong tarball nguồn từ thượng nguồn, vì vậy có lẽ chương trình được thêm vào bởi phân phối của bạn?)
Toby Speight

@TobySpeight Nếu bạn cài đặt nó bằng pipnó.
phk

@TobySpeight Tiện ích nằm trong tarball ngược dòng vì unidecode/util.py- lạ là Debian không bao gồm nó. (Chỉnh sửa: Ah, bí ẩn đã được giải quyết. Gói Debian đã lỗi thời và cũ hơn tiện ích.)
Wander Nauta

7

Một phiên bản bash thuần túy:

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

Đã thử nghiệm trong máy Gentoo của tôi và nó hoạt động.

./convert ۱۳۲
Result is 132

Thực hiện dưới dạng một vòng lặp, đưa ra danh sách các ký tự (từ 0 đến 9) để chuyển đổi:

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

Và được sử dụng như:

$ convert ۱۳۲
132

Một cách khác (khá là quá mức) sử dụng 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 thuần túy, ngoại trừ grep. Thực tế, tôi không hiểu dòng đó, cũng như tại sao bạn không đặt result=0. Bạn có quá thận trọng trong trường hợp $1có chứa những thứ khác với chữ số Farsi không?
Kusalananda

@Kusalananda dòng đó đọc các chữ số Farsi thành chữ số. Làm cho nó có thể lặp.
coffeMug

1
Mười sự thay thế đơn giản sẽ nhanh hơn ... number=${number//۱/1}vv, và sẽ tránh echogrep.
Kusalananda

1
@Kusalananda Đẹp. Đã thay đổi nó. Bây giờ nó là Bash thuần túy! ;-)
coffeMug

@coffeMug: ۱۳۲ là 132 không 123: D
بارپابابا

3

iconvdường như không thể hiểu được điều này, cổng gọi tiếp theo sẽ là sử dụng trtiện ích:

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

tr dịch một bộ ký tự sang bộ ký tự khác, vì vậy chúng tôi chỉ cần bảo nó dịch bộ chữ số Farsi sang bộ chữ số Latinh.

EDIT : Như người dùng @cuonglm chỉ ra. Điều này đòi hỏi không phải GNU tr, ví dụ như trtrên máy Mac và nó cũng yêu cầu $LC_CTYPEđược đặt thành en_US.UTF-8.


2
Lưu ý rằng nó sẽ không hoạt động với GNU tr, không hỗ trợ các ký tự nhiều byte.
cuonglm

1
Ôi trời. GNU ngớ ngẩn. ;-)
Kusalananda

Và bạn cũng cần đặt ngôn ngữ của mình thành một nơi hỗ trợ unicode, như thế nào en_US.utf8.
cuonglm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.