Jak przekonwertować cyfry perskie w UTF-8 na cyfry europejskie w ASCII?

16

Cyfry perskie ۰۱۲۳۴۵۶۷۸۹odpowiadają 0123456789cyfrom europejskim.

Jak przekonwertować liczbę perską (in UTF-8) na ASCII?

Na przykład chcę ۲۱zostać 21.

بارپابابا
źródło
1
Ciekawe, wygląda na to, że echo "۰۱۲۳۴۵۶۷۸۹" | iconv -f UTF-8 -t ascii//TRANSLITsobie z tym nie poradzi ...
Kusalananda
@Kusalananda NIE zadziałało
بارپابابا
3
@Kusalananda: Czy to naprawdę takie nieoczekiwane? Jak zrozumiałem, iconvwłaśnie tutaj jest mapowanie znaków w różnych kodowaniach, ale są to znaki (cyfry wschodnio arabskie), które nie mają odpowiednika w ASCII, możesz po prostu przekonwertować je na coś podobnego, ale jest to tylko jednokierunkowe.
phk
3
Nie byłem do końca pewien, co iconvjest w stanie, a co nie. Miałem nadzieję, że //TRANSLITto pomoże, ale tak nie było.
Kusalananda
1
Czy musisz także odwrócić zamówienie? Wiem, że cyfry arabskie są pisane małymi literami od prawej do lewej, a cyfry łacińskie są dużymi literami od lewej do prawej (wyglądają podobnie na wydruku lub na ekranie, ale odwrócone w pamięci). Czy perski jest taki sam?
Toby Speight,

Odpowiedzi:

6

Możemy skorzystać z faktu, że punkt kodowy UNICODE liczb perskich jest następujący i uporządkowany od 0 do 9 :

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

Oznacza to, że ostatnia cyfra szesnastkowa JEST wartością dziesiętną:

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

To sprawia, że ​​ta prosta pętla jest narzędziem do konwersji:

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

Używając go jako:

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

$ sefr.sh ۲۰۱
201

$ sefr.sh ۲۱
21

Pamiętaj, że ten kod może również konwertować cyfry arabskie i łacińskie (nawet jeśli są mieszane):

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

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

źródło
bardzo, bardzo dziękuję, to jest bardzo fajne rozwiązanie ,, i mam pytanie ,, w tym poleceniu printf '% d' '„۰” po co używać podwójnego cudzysłowu?
بارپابابا
@Babyy To nie jest podwójne notowanie, jest to sposób, aby dać printf argument, że zaczynają się jednym cytatem: . Mógłby być również napisany jako '"۰'. Powodem jest to, że printf poda kod UNICODE, jeśli argument zaczyna się od pojedynczego cudzysłowu' lub podwójnego cudzysłowu ". Wyszukaj trochę przed tym linkiem tekst „Jeśli wiodącym znakiem jest pojedynczy lub podwójny cudzysłów”
@Babyy Kod został rozszerzony o konwersję języka perskiego, arabskiego i łacińskiego (nawet jeśli jest mieszany).
27

Ponieważ jest to stały zestaw liczb, możesz to zrobić ręcznie:

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

(lub używając tr, ale jeszcze nie GNU tr )

Ustawienie twojego locale na en_US.utf8(lub lepiej na locale, do którego należy zestaw znaków) jest wymagane seddo rozpoznania twojego zestawu znaków.

Z perl:

$ echo "۲۱" |
  perl -CS -MUnicode::UCD=num -MUnicode::Normalize -lne 'print num(NFKD($_))'
21
Cuonglm
źródło
Ustawienie LC_ALLjest konieczne, aby każdy pojedynczy znak Unicode był również traktowany jako taki sed, prawda?
phk
@phk: Tak, zobacz aktualizację.
cuonglm,
Dlaczego wszystko musi być scenariuszem sed? Czy nie wymyśliliśmy trtego właśnie w tym celu?
Kevin
3
@Kevin Zobacz inną odpowiedź dotyczącą trtego, jak to nie działa wszędzie. Pamiętaj również, że niektóre narzędzia są zoptymalizowane do obsługi bajtów, podczas gdy inne do obsługi znaków, z Unicode (szczególnie UTF-8) robi to ogromną różnicę.
phk,
To nie działa dla mnie w OS X 10.10.5 / GNU bash 4.3. Co dziwne, muszę usunąć jawne ustawienie LC_ALL. LC_ALLnie jest również ustawiony w moim środowisku (ale LANGjest ustawiony na en_GB.UTF-8). Przy powyższym kodzie pojawia się błąd „sed: 1:„ y / ۰۱۲۳۴۵۶۷۸۹ / ... ”: łańcuchy transformacji nie są tej samej długości”.
Konrad Rudolph
15

W przypadku Python istnieje unidecodebiblioteka, która ogólnie obsługuje takie konwersje: https://pypi.python.org/pypi/Unidecode .

W Python 2:

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

W Pythonie 3:

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

Wątek SO na /programming//q/8087381/2261442 może być powiązany.

/ edit: Jak zauważył Wander Nauta w komentarzach i jak wspomniano na stronie Unidecode, istnieje również wersja powłoki unidecode(poniżej, /usr/local/bin/jeśli została zainstalowana pip):

$ echo '۰۱۲۳۴۵۶۷۸۹' | unidecode
0123456789
phk
źródło
2
Biblioteka unidecode zawiera także narzędzie o nazwie ( unidecodeco nie jest zaskoczeniem), które działa tak samo jak twój fragment kodu w Pythonie 3. Po prostu echo '۰۱۲۳۴۵۶۷۸۹' | unidecodepowinno działać.
Wander Nauta,
@Wander - pakiet Debiana python-unidecode nie dostarcza programu narzędziowego, więc na takich platformach może być potrzebna długa forma (nie znalazłem go w źródłowym archiwum z góry, więc być może program został dodany przez Twoja dystrybucja?)
Toby Speight
@TobySpeight Jeśli zainstalujesz go za pomocą pipgo tam.
phk
@TobySpeight Narzędzie znajduje się w głównym archiwum, ponieważ unidecode/util.py- dziwne, że Debian go nie zawiera. (Edycja: Ach, zagadka rozwiązana. Pakiet Debiana jest nieaktualny i starszy niż narzędzie.)
Wander Nauta,
7

Wersja czysto bashowa:

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

Przetestowałem na moim komputerze Gentoo i działa.

./convert ۱۳۲
Result is 132

Wykonano jako pętlę, biorąc pod uwagę listę znaków (od 0 do 9) do konwersji:

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

I używany jako:

$ convert ۱۳۲
132

Innym (raczej przesadnym) sposobem jest użycie 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"
kubek kawy
źródło
1
Pure Bash, z wyjątkiem grep. W rzeczywistości nie rozumiem tej linii ani dlaczego jej nie ustawiłeś result=0. Czy jesteś zbyt ostrożny, jeśli $1zawiera inne rzeczy niż cyfry farsi?
Kusalananda
@Kusalananda linia ta odczytuje cyfry Farsi na liczby. Umożliwia pętlę.
coffeMug
1
Dziesięć podstawienia proste byłoby szybciej ... number=${number//۱/1}itd., I uniknąć echoi grep.
Kusalananda
1
@Kusalananda Nice. Zmieniłem to. Teraz jest czysty Bash! ;-)
coffeMug
@coffeMug: ۱۳۲ is 132 no 123: D
بارپابابا
3

Ponieważ iconvwydaje się, że to nie przeszkadza, następnym portem połączenia byłoby użycie trnarzędzia:

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

tr tłumaczy jeden zestaw znaków na inny, więc po prostu mówimy mu, aby przetłumaczył zestaw cyfr farsi na zestaw cyfr łacińskich.

EDYCJA : Jak wskazuje użytkownik @cuonglm. Wymaga to systemu innego niż GNU tr, na przykład trna komputerze Mac, i również wymaga $LC_CTYPEustawienia en_US.UTF-8.

Kusalananda
źródło
2
Zauważ, że nie będzie działać z GNU tr, który nie obsługuje znaków wielobajtowych.
cuonglm,
1
O mój. Głupie GNU. ;-)
Kusalananda
Musisz także ustawić ustawienia regionalne na takie, które obsługują Unicode, np en_US.utf8.
cuonglm