متن در کامپیوتر: ASCII و UTF-8
حرف و فارسی چطور به بایت تبدیل میشوند: جدول ASCII، یونیکد و UTF-8، فرق تعداد حرف با تعداد بایت، دیدن بایتهای سلام با xxd، چرا متن گاهی به «ط³ظ„ط§ظ…» تبدیل میشود (encoding اشتباه) و تبدیلش با iconv، فرق «ی» و «ي» و «ک» و «ك» در جستجو، و فرق فایل متنی و باینری با file.
- حدود ۲۱ دقیقه مطالعه
- ۷ مثال اجراشده
- ۳ تمرین با جواب
- مقدماتی (از صفر مطلق)
- بهروزرسانی: ۲۶ مهر ۱۴۰۵
توی این درس یاد میگیری متن، حرف فارسی و حتی ایموجی چطور به بایت تبدیل میشوند، و چرا گاهی بهجای «سلام» چیزی مثل ط³ظ„ط§ظ… یا ؟؟؟ میبینی. با ASCII، یونیکد (Unicode) و UTF-8 آشنا میشوی، بایتهای واقعی کلمهی «سلام» را با xxd میبینی، خرابی متن را عمداً میسازی و با iconv درستش میکنی، و یک تلهی فارسی را میشناسی: «ی» و «ي» (و «ک» و «ك») که شبیهاند ولی برای کامپیوتر دو حرف جدا هستند.
مسئله: چرا فارسی گاهی خراب میشود؟
Section titled “مسئله: چرا فارسی گاهی خراب میشود؟”دو نمونهای که حتماً دیدهای: ایمیلی که بهجای متن فارسی پر از ؟؟؟؟ است، یا فایل CSV که در اکسل باز میکنی و میشود ط³ظ„ط§ظ…. متن «خراب» نشده؛ کدگشایی اشتباه شده. در درس قبل دیدی کامپیوتر فقط عدد میشناسد؛ پس برای متن باید یک قرارداد باشد که «کدام عدد کدام حرف است». اگر نویسنده و خواننده قراردادشان فرق کند، حرفها قاطی میشوند.
تشبیه: دفترچهی رمز
Section titled “تشبیه: دفترچهی رمز”دو دوست برای هم پیام عددی میفرستند. قرار گذاشتهاند «۱ یعنی الف، ۲ یعنی ب، …». این قرارداد، دفترچهی رمز است. اگر یکی با دفترچهی دیگری بخواند (مثلاً «۱ یعنی ب»)، پیام بیمعنی میشود، با اینکه عددها دقیقاً همانی هستند که فرستاده شد. encoding یعنی همین دفترچه رمز: جدولی که میگوید هر حرف چه عددی (و چه بایتهایی) دارد. هر متنی که ذخیره میشود یک دفترچهی رمز دارد، چه ما بدانیم چه ندانیم.
سه اسم که باید بشناسی
Section titled “سه اسم که باید بشناسی”| اسم | چیست | چند حرف دارد | مثال |
|---|---|---|---|
| ASCII | قدیمیترین جدول (۱۹۶۳): حرفهای انگلیسی، رقم و علامت | ۱۲۸ | A = ۶۵، a = ۹۷ |
| Unicode | «فهرست همهی حرفهای دنیا»: به هر حرف یک شماره میدهد (code point)، بدون اینکه بگوید چطور به بایت تبدیل شود | بیش از ۱۴۰ هزار | س = U+0633، 😀 = U+1F600 |
| UTF-8 | روشی برای نوشتن شمارههای یونیکد با ۱ تا ۴ بایت | همهی یونیکد | A یک بایت، س دو بایت، 😀 چهار بایت |
نکتهی مهم: یونیکد جدول است، UTF-8 روش ذخیره. ASCII جدول کوچکی است که داخل UTF-8 همانطور که بود حفظ شده: هر متن ASCII خودبهخود UTF-8 معتبر هم هست. امروز تقریباً همهی وب، لینوکس و برنامهها UTF-8 هستند؛ مشکلات از جایی میآیند که فایل یا سیستمی هنوز قدیمی است.
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: هر حرف یک شماره دارد
Section titled “مثال ۱: هر حرف یک شماره دارد”در پایتون ord("A") شمارهی حرف را میدهد و chr(65) برعکس:
python3 -c "print([(c, ord(c)) for c in 'Aa0 '])print(chr(72), chr(105), ' and a Persian letter:', chr(0x633), 'is U+%04X' % ord('س'))"[('A', 65), ('a', 97), ('0', 48), (' ', 32)]H i and a Persian letter: س is U+0633A شمارهی ۶۵ دارد، a شمارهی ۹۷ (حرف کوچک و بزرگ دو حرف جدا هستند و ۳۲ تا فاصله دارند)، رقم 0 شمارهی ۴۸ دارد (رقم صفر با عدد صفر فرق دارد: کاراکتر 0 یک حرف است) و فاصله شمارهی ۳۲. س شمارهی U+0633 (هگز ۶۳۳، یعنی ۱۶۳۵ در دهدهی) دارد؛ U+ یعنی «شمارهی یونیکد».
مثال ۲: بایتهای واقعی چند حرف
Section titled “مثال ۲: بایتهای واقعی چند حرف”با printf یک حرف را چاپ میکنیم و با xxd بایتهایش را (به هگز) میبینیم:
printf 'A' | xxdprintf 'é' | xxdprintf 'س' | xxdprintf 'سلام' | xxdprintf '😀' | xxd00000000: 41 A00000000: c3a9 ..00000000: d8b3 ..00000000: d8b3 d984 d8a7 d985 ........00000000: f09f 9880 ....ستون وسط بایتها به هگز است:
Aیک بایت:41(همان ۶۵).éدو بایت:c3 a9.سدو بایت:d8 b3. «سلام» چهار حرف است و هشت بایت (هر حرف دو بایت).😀چهار بایت:f0 9f 98 80.
(ستون کناری در xxd نمایش متنی بایتهاست و . یعنی «بایتی که حرف قابلچاپ ندارد».)
مثال ۳: تعداد حرف با تعداد بایت فرق دارد
Section titled “مثال ۳: تعداد حرف با تعداد بایت فرق دارد”برای مثال یک فایل ۴ حرفی فارسی چند بایت است؟ دستور wc -c بایت میشمارد و wc -m حرف (به شرط اینکه تنظیم زبان سیستم UTF-8 باشد، که امروز تقریباً همیشه هست):
printf 'سلام' > fa.txtwc -c fa.txtwc -m fa.txtpython3 -c "for t in ('Hello', 'سلام', '😀'): print(f'{t:>6}: {len(t)} chars, {len(t.encode())} bytes in UTF-8, {len(t.encode(\"utf-16-le\"))} bytes in UTF-16')"8 fa.txt4 fa.txt Hello: 5 chars, 5 bytes in UTF-8, 10 bytes in UTF-16 سلام: 4 chars, 8 bytes in UTF-8, 8 bytes in UTF-16 😀: 1 chars, 4 bytes in UTF-8, 4 bytes in UTF-16فایل «سلام» ۸ بایت است ولی ۴ حرف. در پایتون هم همین: len حرف میشمارد و len(t.encode()) بایت. ایموجی یک حرف است و ۴ بایت. و UTF-16 (که ویندوز و جاوا در بعضی جاها استفاده میکنند) هر حرف انگلیسی را دو بایت مینویسد (Hello شد ۱۰ بایت)؛ برای همین فایلهای انگلیسی با آن دو برابر میشوند.
مثال ۴: یک متن، دو encoding
Section titled “مثال ۴: یک متن، دو encoding”قدیمترها فارسی را با Windows-1256 (به اسم cp1256) ذخیره میکردند: هر حرف فارسی فقط یک بایت بود. با iconv (convert encoding) متن را از UTF-8 به cp1256 میبریم و با file میپرسیم هر فایل چه نوعی است:
printf 'سلام' | iconv -f UTF-8 -t CP1256 > fa-cp1256.txtprintf 'سلام' > fa-utf8.txtfile fa-utf8.txt fa-cp1256.txtxxd fa-utf8.txtxxd fa-cp1256.txtfa-utf8.txt: Unicode text, UTF-8 text, with no line terminatorsfa-cp1256.txt: ISO-8859 text, with no line terminators00000000: d8b3 d984 d8a7 d985 ........00000000: d3e1 c7e3 ....- فایل UTF-8 هشت بایت است:
d8b3 d984 d8a7 d985. - فایل cp1256 فقط چهار بایت است:
d3 e1 c7 e3، یعنی هر حرف یک بایت. fileفایل اول را درست تشخیص میدهد (UTF-8) ولی برای دومی فقط میگوید «ISO-8859 text»: چون فایل cp1256 نشانهی مشخصی ندارد،fileفقط حدس میزند. هیچ ابزاری نمیتواند همیشه encoding یک فایل قدیمی را با قطعیت بگوید؛ باید از منشأ فایل بپرسی.
مثال ۵: ساختن عمدی خرابی (mojibake)
Section titled “مثال ۵: ساختن عمدی خرابی (mojibake)”به متن خرابشدهی اینچنینی ژاپنیها mojibake میگویند (یعنی «حرفهای تبدیلشده»). با خواندن بایتها با encoding اشتباه، همهی خرابیهای مسئلهی اول را خودمان میسازیم:
python3 - <<'EOF'utf8 = 'سلام'.encode('utf-8')cp1256 = 'سلام'.encode('cp1256')
print('UTF-8 bytes read as UTF-8 :', utf8.decode('utf-8'))print('UTF-8 bytes read as cp1256 :', utf8.decode('cp1256'))print('UTF-8 bytes read as latin-1 :', utf8.decode('latin-1'))print('cp1256 bytes read as latin-1 :', cp1256.decode('latin-1'))try: cp1256.decode('utf-8')except UnicodeDecodeError as e: print('cp1256 bytes read as UTF-8 : ERROR ->', e)EOFUTF-8 bytes read as UTF-8 : سلامUTF-8 bytes read as cp1256 : ط³ظ„ط§ظ…UTF-8 bytes read as latin-1 : Ø³ÙØ§Ù
cp1256 bytes read as latin-1 : ÓáÇãcp1256 bytes read as UTF-8 : ERROR -> 'utf-8' codec can't decode byte 0xd3 in position 0: invalid continuation byteهمان چهار حرف «سلام»، چهار نتیجه:
- UTF-8 با UTF-8: درست.
- UTF-8 با cp1256:
ط³ظ„ط§ظ…. این دقیقاً همان چیزی است که در اکسل و بعضی سایتها میبینی: هر حرف فارسی (دو بایت UTF-8) با دو حرف عربیمانند نشان داده میشود. اگر این الگو را دیدی (حرفهایطوظپشتسرهم)، یعنی متن UTF-8 است و با cp1256 خوانده شده. - UTF-8 با latin-1:
Ø³ÙØ§Ù. همان قصه با جدول اروپایی. - cp1256 با latin-1:
ÓáÇã. - cp1256 با UTF-8: خطا:
invalid continuation byte. بایتd3در UTF-8 یعنی «این اول یک حرف دوبایتی است» ولی بایت بعدی (e1) الگوی درست را ندارد. این خطا نشانهی خوبی است: بایتها UTF-8 نیستند.
مثال ۶: درست کردن با iconv
Section titled “مثال ۶: درست کردن با iconv”برای درست کردن فایل قدیمی، باید بدانی با چه encoding نوشته شده، و آن را به UTF-8 تبدیل کنی:
echo "== what you see without converting:"cat fa-cp1256.txt; echoecho "== after converting from CP1256 to UTF-8:"iconv -f CP1256 -t UTF-8 fa-cp1256.txt; echoecho "== converting Persian to ASCII is impossible:"iconv -f UTF-8 -t ASCII fa-utf8.txt; echo "exit code: $?"== what you see without converting:����== after converting from CP1256 to UTF-8:سلام== converting Persian to ASCII is impossible:iconv: illegal input sequence at position 0exit code: 1بدون تبدیل، cat چهار علامت � چاپ کرد (ترمینال UTF-8 این بایتها را معتبر نمیداند، پس بهجایشان «علامت حرف نامعتبر» میگذارد). با iconv -f CP1256 -t UTF-8 متن برگشت. -f یعنی from و -t یعنی to. آخرین دستور هم یک واقعیت را نشان میدهد: ASCII فارسی ندارد، پس تبدیل شکست میخورد (illegal input sequence، کد خروج ۱). (با گزینهی //TRANSLIT ممکن است بهجای حرف نامعتبر ? بگذارد، ولی این یعنی از دست دادن اطلاعات؛ از آن برای متنهای فارسی استفاده نکن.)
مثال ۷: فایل متنی یا باینری؟
Section titled “مثال ۷: فایل متنی یا باینری؟”هر فایلی بایت است؛ تفاوت در این است که بایتها متن هستند (طبق یک encoding قابلخواندن) یا باینری (دستورهای برنامه، عکس، فشرده…). دستور file از روی شمارهی جادویی (magic number) ابتدای فایل نوعش را تشخیص میدهد:
file /etc/hostname fa-utf8.txt /bin/ls | cut -c1-110echo "--- first 4 bytes of /bin/ls:"head -c 4 /bin/ls | xxdecho "--- what happens if you print a binary file as text:"head -c 16 /bin/ls | cat -v/etc/hostname: ASCII textfa-utf8.txt: Unicode text, UTF-8 text, with no line terminators/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64--- first 4 bytes of /bin/ls:00000000: 7f45 4c46 .ELF--- what happens if you print a binary file as text:^?ELF^B^A^A^@^@^@^@^@^@^@^@^@fileبرای/etc/hostnameگفت «ASCII text» و برای فایل UTF-8 ما «UTF-8 text». برای/bin/lsگفت «ELF 64-bit … executable»: یک فایل اجرایی، نه متن.- بایتهای اول
/bin/ls:7f 45 4c 46. مقدار45 4c 46در ASCII همانELFاست. همهی برنامههای لینوکس با این شمارهی جادویی شروع میشوند. - چاپ فایل باینری بهصورت متن، چیزی جز علامتهای بیمعنی نیست (
^@یعنی بایت صفر). به همین دلیل هرگز برنامهای را باcatچاپ نکن؛ ترمینال ممکن است قاطی شود.
پشت پرده: UTF-8 چطور کار میکند؟
Section titled “پشت پرده: UTF-8 چطور کار میکند؟”UTF-8 هوشمندانه طراحی شده: چند بیت اول هر بایت میگوید با چه بایتی روبهرو هستی.
| تعداد بایت | الگوی بیتها | محدوده | مثال |
|---|---|---|---|
| ۱ | 0xxxxxxx |
ASCII (۱۲۸ حرف) | A |
| ۲ | 110xxxxx 10xxxxxx |
لاتینهای اروپایی، عربی، فارسی، عبری… | س، é |
| ۳ | 1110xxxx 10xxxxxx 10xxxxxx |
بیشتر زبانهای دنیا (چینی، ژاپنی…) | € |
| ۴ | 11110xxx 10xxxxxx ... |
ایموجی و حرفهای نادر | 😀 |
بیایید حرف س را با دست حل کنیم. شمارهی یونیکدش U+0633 است، یعنی عدد ۱۶۳۵ که در باینری 110 0011 0011 (۱۱ بیت) میشود. جا برای ۱۱ بیت فقط در الگوی دوبایتی هست (۵ + ۶ = ۱۱). پس بیتها را در الگو میریزیم: 110 + 11000 برای بایت اول، و 10 + 110011 برای بایت دوم. با کد ببینیم همین شد:
python3 -c "bits = format(ord('س'), 'b')print('U+0633 in binary:', bits, f'({len(bits)} bits)')print('UTF-8 bytes :', ' '.join(format(b, '08b') for b in 'س'.encode()))print('as hex :', 'س'.encode().hex(' '))"U+0633 in binary: 11000110011 (11 bits)UTF-8 bytes : 11011000 10110011as hex : d8 b3شمارهی یونیکد س در باینری ۱۱ بیت است، و همین ۱۱ بیت در الگوی دوبایتی (110xxxxx 10xxxxxx) جا شدند و دو بایت 11011000 10110011 یعنی d8 b3 ساختند؛ دقیقاً همان بایتهایی که xxd در مثال ۲ نشان داد.
دو مزیت این طراحی: ۱) متن ASCII بدون هیچ تغییر معتبر است (سازگاری با گذشته)؛ ۲) اگر وسط فایل گم شوی، با دیدن 10xxxxxx میفهمی این بایت وسط یک حرف است و با چند بایت عقب رفتن شروع حرف را پیدا میکنی. و دلیل اینکه UTF-16 در مثال ۳ برای انگلیسی دو برابر جا میگیرد، این است که هر حرف را حداقل دو بایت مینویسد.
اشتباهات رایج
Section titled “اشتباهات رایج”۱) خواندن فایل با encoding اشتباه
Section titled “۱) خواندن فایل با encoding اشتباه”printf 'caf\xe9\n' > old.txtfile old.txtpython3 -c "print(open('old.txt', encoding='utf-8').read())" 2>&1 | tail -n 1iconv -f LATIN1 -t UTF-8 old.txtold.txt: ISO-8859 textUnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 3: invalid continuation bytecaféفایل را با printf 'caf\xe9' ساختیم: حرف é در encoding قدیمی Latin-1 فقط یک بایت e9 است. file فقط «ISO-8859 text» گفت. پایتون که انتظار UTF-8 داشت با UnicodeDecodeError ایستاد: بایت e9 در UTF-8 یعنی «شروع حرف سهبایتی» و ادامهاش نیست. با گفتن encoding درست (LATIN1) متن café برگشت. راهحل: پیام خطا را بخوان، بپرس «این فایل از کجا آمده؟»، و با iconv تبدیل کن.
۲) فرض کردن «۱ حرف = ۱ بایت»
Section titled “۲) فرض کردن «۱ حرف = ۱ بایت»”برای انگلیسی درست است و برای فارسی و ایموجی نه (مثال ۳). این در دنیای واقعی دردسر میسازد: فیلدی که «حداکثر ۱۰ بایت» جا دارد، فقط ۵ حرف فارسی جا میدهد.
printf 'ایران' | wc -cprintf 'ایران' | wc -m105«ایران» ۵ حرف است ولی ۱۰ بایت. بسیاری از دیتابیسها و فرمها محدودیت را بهصورت بایت میگذارند؛ پس اگر فیلدی «۱۰ حرف» جا دارد، ممکن است فقط ۵ حرف فارسی را قبول کند. هنگام تعریف محدودیت بپرس «حرف یا بایت؟».
۳) «ی» و «ي»، «ک» و «ك»
Section titled “۳) «ی» و «ي»، «ک» و «ك»”صفحهکلیدهای عربی و فارسی دو شکل یکسان با کد متفاوت دارند. اگر در متنی «علي» با «ي» عربی باشد و تو «علی» فارسی جستجو کنی، پیدا نمیشود:
python3 - <<'EOF'import unicodedataa, b = 'علی', 'علی'.replace('ی', 'ي') # Persian yeh vs Arabic yehprint('equal?', a == b)for ch in ('ی', 'ي', 'ک', 'ك'): print(ch, f'U+{ord(ch):04X}', unicodedata.name(ch))EOFecho "علي" | grep -c "علی"echo "علی" | grep -c "علی"equal? Falseی U+06CC ARABIC LETTER FARSI YEHي U+064A ARABIC LETTER YEHک U+06A9 ARABIC LETTER KEHEHك U+0643 ARABIC LETTER KAF01equal? False: «علی» (با ی فارسی) و «علي» (با ي عربی) برای کامپیوتر دو متن متفاوتاند.- شمارهها فرق دارند:
U+06CC(یای فارسی) وU+064A(یای عربی)، وU+06A9(کاف فارسی) وU+0643(کاف عربی). grepروی «علي» جستجوی «علی» را پیدا نکرد (۰ تطابق) و روی «علی» پیدا کرد (۱ تطابق).
این مشکل واقعی در جستجو، مرتبسازی و مقایسهی اسمها پیش میآید، چون کاربران از صفحهکلیدها و نرمافزارهای مختلف مینویسند. راهحل: نرمالسازی: پیش از ذخیره یا جستجو، هر ي ← ی و هر ك ← ک (تمرین سخت همین را مینویسد).
کلمهی «ایران» را به UTF-8 ببر و بایتهایش را به هگز بنویس. چند حرف و چند بایت است؟
دیدن جواب
printf 'ایران' | xxd -c 20printf 'ایران' | wc -cprintf 'ایران' | wc -m00000000: d8a7 db8c d8b1 d8a7 d986 ..........105پنج حرف (wc -m)، ده بایت (wc -c): هر حرف دو بایت. بایتها به ترتیب: ا = d8 a7، ی = db 8c، ر = d8 b1، ا = d8 a7 و ن = d9 86. دقت کن حرف «ی» (db 8c) با «ا» (d8 a7) بایت اول متفاوت دارد، چون در بخش دیگری از جدول یونیکد است (U+06CC).
متن «تهران» را به cp1256 ببر و در یک فایل بگذار. نشان بده بدون تبدیل خوانا نیست و با iconv برمیگردد. بعد با file بگو encoding هر دو نسخه چیست.
دیدن جواب
printf 'تهران' | iconv -f UTF-8 -t CP1256 > tehran-old.txtecho "raw bytes: $(xxd -p tehran-old.txt)"echo "as-is: $(cat tehran-old.txt)"echo "fixed: $(iconv -f CP1256 -t UTF-8 tehran-old.txt)"iconv -f CP1256 -t UTF-8 tehran-old.txt > tehran-new.txtfile tehran-old.txt tehran-new.txt | cut -d, -f1raw bytes: cae5d1c7e4as-is: �����fixed: تهرانtehran-old.txt: ISO-8859 texttehran-new.txt: Unicode textبایتهای cp1256 پنج بایتاند (ca e5 d1 c7 e4). بدون تبدیل، ترمینال UTF-8 آنها را نمیخواند (علامتهای �)، با iconv -f CP1256 -t UTF-8 متن برگشت. file فایل قدیمی را «ISO-8859 text» حدس میزند و نسخهی جدید را «Unicode text».
تمرین اصلی درس: یک تابع پایتون بنویس که متن را نرمال کند: «ي» عربی را به «ی» فارسی و «ك» عربی را به «ک» فارسی تبدیل کند. با یک متن نمونه ثابت کن بعد از نرمالسازی، جستجوی «علی» و «کتاب» هر دو کار میکنند.
دیدن جواب
# maps Arabic yeh/kaf to the Persian letters so searches matchTABLE = str.maketrans({'ي': 'ی', 'ك': 'ک'})
def normalize(text: str) -> str: return text.translate(TABLE)
samples = ['علي', 'علی', 'كتاب', 'کتاب']print('before:', [s == 'علی' or s == 'کتاب' for s in samples])print('after :', [normalize(s) == 'علی' or normalize(s) == 'کتاب' for s in samples])assert normalize('علي') == 'علی' and normalize('كتاب') == 'کتاب'print('both words match after normalizing')python3 normalize.pyrm -f normalize.pybefore: [False, True, False, True]after : [True, True, True, True]both words match after normalizingstr.maketrans جدولی از «جایگزینی هر حرف» میسازد و translate آن را روی کل متن اجرا میکند؛ این سریعترین راه در پایتون است. قبل از نرمالسازی فقط دو نمونهی «درست» با کلمهی هدف برابر بودند؛ بعد از آن هر چهار تا. در پروژهی واقعی این تابع را روی هر ورودی کاربر پیش از ذخیره اجرا میکنی.
آزمونک
Section titled “آزمونک”فایلی با encoding قدیمی cp1256 ذخیره شده و در برنامهای که انتظار UTF-8 دارد باز میشود. چه اتفاقی میافتد؟
جواب درست: حرفها خراب نشان داده میشوند یا خطای decode میگیری
بایتها همان بایتهای cp1256اند ولی برنامه آنها را با جدول UTF-8 میخواند. چون فارسی در cp1256 بایت تکی است و در UTF-8 جفتی، یا حرفهای بیمعنی میبینی یا (اگر ترتیب بایتها در UTF-8 معتبر نباشد) خطای decode. حرفهای انگلیسی در هر دو یکیاند و درست میمانند.
کلمهی «سلام» در UTF-8 چند بایت است؟
جواب درست: ۸
هر حرف فارسی در UTF-8 دو بایت است (الگوی ۱۱۰xxxxx ۱۰xxxxxx). چهار حرف × دو بایت = ۸ بایت. در مثال ۲ هم هشت بایت (
d8b3 d984 d8a7 d985) دیدی.- ۴ ← ۴ تعداد حرفهاست، نه بایت؛ هر حرف فارسی بیش از یک بایت است.
- ۱۶ ← ۱۶ بایت برای ۴ حرف در UTF-8 زیاد است (هر حرف فارسی دو بایت است نه چهار).
- ۲ ← ۲ بایت فقط یک حرف است، نه کل کلمه.
کدام عبارت درست است؟
جواب درست: یونیکد جدولی است که به هر حرف شماره میدهد و UTF-8 یکی از روشهای ذخیرهی آن شمارهها با بایت است
یونیکد میگوید «س» شمارهی U+0633 را دارد؛ UTF-8، UTF-16 و UTF-32 هر کدام روشهای متفاوتی برای نوشتن آن شماره با بایت هستند. UTF-8 رایجترین است.
- یونیکد و UTF-8 دقیقاً یک چیزند ← یکی جدول است و دیگری روش ذخیره؛ برای همین یونیکد چند encoding متفاوت دارد.
- UTF-8 فقط برای حرفهای انگلیسی است ← UTF-8 همهی حرفهای یونیکد را پوشش میدهد، از جمله فارسی و ایموجی.
- ASCII از UTF-8 جدیدتر است ← ASCII قدیمیتر است (۱۹۶۳) و داخل UTF-8 هم حفظ شده.
در فایل CSV، اسمهای فارسی در اکسل به شکل
ط¹ظ„غŒدیده میشوند. مشکل چیست؟جواب درست: بایتهای UTF-8 با encoding دیگری (مثلاً cp1256) خوانده شدهاند
متن سالم است و بایتها درستاند؛ برنامه آنها را با دفترچهی رمز اشتباه میخواند. راهحل: در اکسل هنگام وارد کردن داده، encoding را UTF-8 انتخاب کن، یا فایل را با BOM ذخیره کن.
- فایل خراب است ← اگر فایل خراب بود در برنامههای دیگر هم خراب دیده میشد.
- اکسل فارسی بلد نیست ← اکسل فارسی را بدون مشکل نشان میدهد، به شرط اینکه encoding درست را بداند.
- فونت نصب نیست ← مشکل فونت جای حرفها را با مربع نشان میدهد، نه با حرفهای دیگر.
چرا جستجوی «علی» (با «ی» فارسی) در متنی که «علي» با «ي» عربی دارد جواب نمیدهد؟
جواب درست: این دو حرف در یونیکد دو شمارهی متفاوت دارند (U+06CC و U+064A) و برای کامپیوتر دو حرف جدا هستند
ظاهرشان تقریباً یکی است ولی شمارهشان فرق دارد، و کامپیوتر فقط شمارهها را مقایسه میکند. راهحل رایج: پیش از ذخیره یا جستجو، متن را نرمال کن (ي ← ی، ك ← ک).
- grep خراب است ← grep درست کار میکند و دقیقاً شمارهها را مقایسه میکند.
- جستجوی فارسی ممکن نیست ← جستجوی فارسی بهراحتی ممکن است؛ فقط باید حرفها یکسان باشند.
- حرفها فقط رنگشان فرق دارد ← رنگ در متن وجود ندارد؛ تفاوت در شمارهی حرف است.
دستور
fileچطور میفهمد یک فایل برنامهی اجرایی است نه متن؟جواب درست: از چند بایت اول فایل (شمارهی جادویی)، مثلاً
7f 45 4c 46برای ELFخیلی از فرمتها در ابتدای فایل یک امضای ثابت دارند. برنامههای لینوکس با
7f 45 4c 46(کهELFخوانده میشود) شروع میشوند.fileبه پسوند و اسم کاری ندارد؛ به همین دلیل فایل «عکس.txt» را هم درست تشخیص میدهد.- از پسوند فایل ← پسوند را بهراحتی میشود عوض کرد و
fileبه آن اعتماد نمیکند. - از اسم فایل ← اسم فایل چیزی را ثابت نمیکند.
- از اندازهی فایل ← اندازه چیزی دربارهی نوع فایل نمیگوید.
- از پسوند فایل ← پسوند را بهراحتی میشود عوض کرد و
جمعبندی
Section titled “جمعبندی”- متن در کامپیوتر عدد است و encoding میگوید هر حرف چه عددی (و چه بایتهایی) دارد؛ مثل یک دفترچهی رمز که نویسنده و خواننده باید هر دو یکی داشته باشند.
- ASCII ۱۲۸ حرف دارد. یونیکد به همهی حرفهای دنیا شماره میدهد. UTF-8 آن شمارهها را با ۱ تا ۴ بایت مینویسد: انگلیسی ۱ بایت، فارسی ۲ بایت، ایموجی ۴ بایت.
- تعداد حرف با تعداد بایت فرق دارد:
wc -mحرف میشمارد وwc -cبایت. - متن خراب (
ط³ظ„ط§ظ…) یعنی بایتها با encoding اشتباه خوانده شدهاند. اول بپرس «با چه encoding نوشته شده؟»، بعد باiconv -f قدیمی -t UTF-8تبدیل کن. - «ی» با «ي» و «ک» با «ك» در یونیکد جدا هستند؛ برای جستجو متن را نرمال کن.
fileاز روی چند بایت اول نوع فایل را میگوید وxxdبایتهای واقعی را نشان میدهد.
| دستور | کاری که میکند |
|---|---|
xxd file | بایتهای واقعی فایل به هگز |
printf 'سلام' | xxd | بایتهای یک متن |
wc -c file | تعداد بایت |
wc -m file | تعداد حرف (به شرط locale ِ UTF-8) |
file file | نوع و encoding تقریبی فایل |
iconv -f CP1256 -t UTF-8 in > out | تبدیل encoding فایل |
python3 -c "print(ord('A'), chr(65))" | شمارهی حرف و برعکس |
"str".encode('utf-8').hex() | بایتهای یک متن در پایتون |