رفتن به محتوا
مبانی کامپیوتر و اینترنتکامپیوتر از دروندرس ۴ از ۱۶

متن در کامپیوتر: ASCII و UTF-8

حرف و فارسی چطور به بایت تبدیل می‌شوند: جدول ASCII، یونیکد و UTF-8، فرق تعداد حرف با تعداد بایت، دیدن بایت‌های سلام با xxd، چرا متن گاهی به «ط³ظ„ط§ظ…» تبدیل می‌شود (encoding اشتباه) و تبدیلش با iconv، فرق «ی» و «ي» و «ک» و «ك» در جستجو، و فرق فایل متنی و باینری با file.

  • حدود ۲۱ دقیقه مطالعه
  • ۷ مثال اجراشده
  • ۳ تمرین با جواب
  • مقدماتی (از صفر مطلق)
  • به‌روزرسانی: ۲۶ مهر ۱۴۰۵

توی این درس یاد می‌گیری متن، حرف فارسی و حتی ایموجی چطور به بایت تبدیل می‌شوند، و چرا گاهی به‌جای «سلام» چیزی مثل ط³ظ„ط§ظ… یا ؟؟؟ می‌بینی. با ASCII، یونیکد (Unicode) و UTF-8 آشنا می‌شوی، بایت‌های واقعی کلمه‌ی «سلام» را با xxd می‌بینی، خرابی متن را عمداً می‌سازی و با iconv درستش می‌کنی، و یک تله‌ی فارسی را می‌شناسی: «ی» و «ي» (و «ک» و «ك») که شبیه‌اند ولی برای کامپیوتر دو حرف جدا هستند.

مسئله: چرا فارسی گاهی خراب می‌شود؟

Section titled “مسئله: چرا فارسی گاهی خراب می‌شود؟”

دو نمونه‌ای که حتماً دیده‌ای: ایمیلی که به‌جای متن فارسی پر از ؟؟؟؟ است، یا فایل CSV که در اکسل باز می‌کنی و می‌شود ط³ظ„ط§ظ…. متن «خراب» نشده؛ کدگشایی اشتباه شده. در درس قبل دیدی کامپیوتر فقط عدد می‌شناسد؛ پس برای متن باید یک قرارداد باشد که «کدام عدد کدام حرف است». اگر نویسنده و خواننده قراردادشان فرق کند، حرف‌ها قاطی می‌شوند.

دو دوست برای هم پیام عددی می‌فرستند. قرار گذاشته‌اند «۱ یعنی الف، ۲ یعنی ب، …». این قرارداد، دفترچه‌ی رمز است. اگر یکی با دفترچه‌ی دیگری بخواند (مثلاً «۱ یعنی ب»)، پیام بی‌معنی می‌شود، با اینکه عددها دقیقاً همانی هستند که فرستاده شد. encoding یعنی همین دفترچه رمز: جدولی که می‌گوید هر حرف چه عددی (و چه بایت‌هایی) دارد. هر متنی که ذخیره می‌شود یک دفترچه‌ی رمز دارد، چه ما بدانیم چه ندانیم.

متن اول با یک encoding به بایت تبدیل می‌شود و روی دیسک می‌نشیند. موقع خواندن، بایت‌ها باید با همان encoding دوباره به حرف برگردند. اگر encoding خواندن فرق کند، حرف‌های اشتباه می‌بینی.
اسم چیست چند حرف دارد مثال
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 “مثال ۱: هر حرف یک شماره دارد”

در پایتون ord("A") شماره‌ی حرف را می‌دهد و chr(65) برعکس:

Terminal window
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+0633

A شماره‌ی ۶۵ دارد، a شماره‌ی ۹۷ (حرف کوچک و بزرگ دو حرف جدا هستند و ۳۲ تا فاصله دارند)، رقم 0 شماره‌ی ۴۸ دارد (رقم صفر با عدد صفر فرق دارد: کاراکتر 0 یک حرف است) و فاصله شماره‌ی ۳۲. س شماره‌ی U+0633 (هگز ۶۳۳، یعنی ۱۶۳۵ در ده‌دهی) دارد؛ U+ یعنی «شماره‌ی یونیکد».

مثال ۲: بایت‌های واقعی چند حرف

Section titled “مثال ۲: بایت‌های واقعی چند حرف”

با printf یک حرف را چاپ می‌کنیم و با xxd بایت‌هایش را (به هگز) می‌بینیم:

Terminal window
printf 'A' | xxd
printf 'é' | xxd
printf 'س' | xxd
printf 'سلام' | xxd
printf '😀' | xxd
خروجی
00000000: 41 A
00000000: 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 باشد، که امروز تقریباً همیشه هست):

Terminal window
printf 'سلام' > fa.txt
wc -c fa.txt
wc -m fa.txt
python3 -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.txt
4 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 شد ۱۰ بایت)؛ برای همین فایل‌های انگلیسی با آن دو برابر می‌شوند.

قدیم‌ترها فارسی را با Windows-1256 (به اسم cp1256) ذخیره می‌کردند: هر حرف فارسی فقط یک بایت بود. با iconv (convert encoding) متن را از UTF-8 به cp1256 می‌بریم و با file می‌پرسیم هر فایل چه نوعی است:

Terminal window
printf 'سلام' | iconv -f UTF-8 -t CP1256 > fa-cp1256.txt
printf 'سلام' > fa-utf8.txt
file fa-utf8.txt fa-cp1256.txt
xxd fa-utf8.txt
xxd fa-cp1256.txt
خروجی
fa-utf8.txt: Unicode text, UTF-8 text, with no line terminators
fa-cp1256.txt: ISO-8859 text, with no line terminators
00000000: 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 اشتباه، همه‌ی خرابی‌های مسئله‌ی اول را خودمان می‌سازیم:

Terminal window
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)
EOF
خروجی
UTF-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 نیستند.

برای درست کردن فایل قدیمی، باید بدانی با چه encoding نوشته شده، و آن را به UTF-8 تبدیل کنی:

Terminal window
echo "== what you see without converting:"
cat fa-cp1256.txt; echo
echo "== after converting from CP1256 to UTF-8:"
iconv -f CP1256 -t UTF-8 fa-cp1256.txt; echo
echo "== 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 0
exit 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) ابتدای فایل نوعش را تشخیص می‌دهد:

Terminal window
file /etc/hostname fa-utf8.txt /bin/ls | cut -c1-110
echo "--- first 4 bytes of /bin/ls:"
head -c 4 /bin/ls | xxd
echo "--- what happens if you print a binary file as text:"
head -c 16 /bin/ls | cat -v
خروجی
/etc/hostname: ASCII text
fa-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 برای بایت دوم. با کد ببینیم همین شد:

Terminal window
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 10110011
as hex : d8 b3

شماره‌ی یونیکد س در باینری ۱۱ بیت است، و همین ۱۱ بیت در الگوی دوبایتی (110xxxxx 10xxxxxx) جا شدند و دو بایت 11011000 10110011 یعنی d8 b3 ساختند؛ دقیقاً همان بایت‌هایی که xxd در مثال ۲ نشان داد.

دو مزیت این طراحی: ۱) متن ASCII بدون هیچ تغییر معتبر است (سازگاری با گذشته)؛ ۲) اگر وسط فایل گم شوی، با دیدن 10xxxxxx می‌فهمی این بایت وسط یک حرف است و با چند بایت عقب رفتن شروع حرف را پیدا می‌کنی. و دلیل اینکه UTF-16 در مثال ۳ برای انگلیسی دو برابر جا می‌گیرد، این است که هر حرف را حداقل دو بایت می‌نویسد.

۱) خواندن فایل با encoding اشتباه

Section titled “۱) خواندن فایل با encoding اشتباه”
Terminal window
printf 'caf\xe9\n' > old.txt
file old.txt
python3 -c "print(open('old.txt', encoding='utf-8').read())" 2>&1 | tail -n 1
iconv -f LATIN1 -t UTF-8 old.txt
خروجی
old.txt: ISO-8859 text
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 3: invalid continuation byte
café

فایل را با printf 'caf\xe9' ساختیم: حرف é در encoding قدیمی Latin-1 فقط یک بایت e9 است. file فقط «ISO-8859 text» گفت. پایتون که انتظار UTF-8 داشت با UnicodeDecodeError ایستاد: بایت e9 در UTF-8 یعنی «شروع حرف سه‌بایتی» و ادامه‌اش نیست. با گفتن encoding درست (LATIN1) متن café برگشت. راه‌حل: پیام خطا را بخوان، بپرس «این فایل از کجا آمده؟»، و با iconv تبدیل کن.

۲) فرض کردن «۱ حرف = ۱ بایت»

Section titled “۲) فرض کردن «۱ حرف = ۱ بایت»”

برای انگلیسی درست است و برای فارسی و ایموجی نه (مثال ۳). این در دنیای واقعی دردسر می‌سازد: فیلدی که «حداکثر ۱۰ بایت» جا دارد، فقط ۵ حرف فارسی جا می‌دهد.

Terminal window
printf 'ایران' | wc -c
printf 'ایران' | wc -m
خروجی
10
5

«ایران» ۵ حرف است ولی ۱۰ بایت. بسیاری از دیتابیس‌ها و فرم‌ها محدودیت را به‌صورت بایت می‌گذارند؛ پس اگر فیلدی «۱۰ حرف» جا دارد، ممکن است فقط ۵ حرف فارسی را قبول کند. هنگام تعریف محدودیت بپرس «حرف یا بایت؟».

صفحه‌کلیدهای عربی و فارسی دو شکل یکسان با کد متفاوت دارند. اگر در متنی «علي» با «ي» عربی باشد و تو «علی» فارسی جستجو کنی، پیدا نمی‌شود:

Terminal window
python3 - <<'EOF'
import unicodedata
a, b = 'علی', 'علی'.replace('ی', 'ي') # Persian yeh vs Arabic yeh
print('equal?', a == b)
for ch in ('ی', 'ي', 'ک', 'ك'):
print(ch, f'U+{ord(ch):04X}', unicodedata.name(ch))
EOF
echo "علي" | 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 KAF
0
1
  • equal? False: «علی» (با ی فارسی) و «علي» (با ي عربی) برای کامپیوتر دو متن متفاوت‌اند.
  • شماره‌ها فرق دارند: U+06CC (یای فارسی) و U+064A (یای عربی)، و U+06A9 (کاف فارسی) و U+0643 (کاف عربی).
  • grep روی «علي» جستجوی «علی» را پیدا نکرد (۰ تطابق) و روی «علی» پیدا کرد (۱ تطابق).

این مشکل واقعی در جستجو، مرتب‌سازی و مقایسه‌ی اسم‌ها پیش می‌آید، چون کاربران از صفحه‌کلیدها و نرم‌افزارهای مختلف می‌نویسند. راه‌حل: نرمال‌سازی: پیش از ذخیره یا جستجو، هر ي ← ی و هر ك ← ک (تمرین سخت همین را می‌نویسد).

تمرینآسان

کلمه‌ی «ایران» را به UTF-8 ببر و بایت‌هایش را به هگز بنویس. چند حرف و چند بایت است؟

دیدن جواب
Terminal window
printf 'ایران' | xxd -c 20
printf 'ایران' | wc -c
printf 'ایران' | wc -m
خروجی
00000000: d8a7 db8c d8b1 d8a7 d986 ..........
10
5

پنج حرف (wc -m)، ده بایت (wc -c): هر حرف دو بایت. بایت‌ها به ترتیب: ا = d8 a7، ی = db 8c، ر = d8 b1، ا = d8 a7 و ن = d9 86. دقت کن حرف «ی» (db 8c) با «ا» (d8 a7) بایت اول متفاوت دارد، چون در بخش دیگری از جدول یونیکد است (U+06CC).

تمرینمتوسط

متن «تهران» را به cp1256 ببر و در یک فایل بگذار. نشان بده بدون تبدیل خوانا نیست و با iconv برمی‌گردد. بعد با file بگو encoding هر دو نسخه چیست.

دیدن جواب
Terminal window
printf 'تهران' | iconv -f UTF-8 -t CP1256 > tehran-old.txt
echo "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.txt
file tehran-old.txt tehran-new.txt | cut -d, -f1
خروجی
raw bytes: cae5d1c7e4
as-is: �����
fixed: تهران
tehran-old.txt: ISO-8859 text
tehran-new.txt: Unicode text

بایت‌های cp1256 پنج بایت‌اند (ca e5 d1 c7 e4). بدون تبدیل، ترمینال UTF-8 آن‌ها را نمی‌خواند (علامت‌های �)، با iconv -f CP1256 -t UTF-8 متن برگشت. file فایل قدیمی را «ISO-8859 text» حدس می‌زند و نسخه‌ی جدید را «Unicode text».

تمرینسخت

تمرین اصلی درس: یک تابع پایتون بنویس که متن را نرمال کند: «ي» عربی را به «ی» فارسی و «ك» عربی را به «ک» فارسی تبدیل کند. با یک متن نمونه ثابت کن بعد از نرمال‌سازی، جستجوی «علی» و «کتاب» هر دو کار می‌کنند.

دیدن جواب
normalize.py
# maps Arabic yeh/kaf to the Persian letters so searches match
TABLE = 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')
Terminal window
python3 normalize.py
rm -f normalize.py
خروجی
before: [False, True, False, True]
after : [True, True, True, True]
both words match after normalizing

str.maketrans جدولی از «جایگزینی هر حرف» می‌سازد و translate آن را روی کل متن اجرا می‌کند؛ این سریع‌ترین راه در پایتون است. قبل از نرمال‌سازی فقط دو نمونه‌ی «درست» با کلمه‌ی هدف برابر بودند؛ بعد از آن هر چهار تا. در پروژه‌ی واقعی این تابع را روی هر ورودی کاربر پیش از ذخیره اجرا می‌کنی.

⚡ بررسی سریع

فایلی با encoding قدیمی cp1256 ذخیره شده و در برنامه‌ای که انتظار UTF-8 دارد باز می‌شود. چه اتفاقی می‌افتد؟

آزمونک
  1. کلمه‌ی «سلام» در UTF-8 چند بایت است؟

  2. کدام عبارت درست است؟

  3. در فایل CSV، اسم‌های فارسی در اکسل به شکل ط¹ظ„غŒ دیده می‌شوند. مشکل چیست؟

  4. چرا جستجوی «علی» (با «ی» فارسی) در متنی که «علي» با «ي» عربی دارد جواب نمی‌دهد؟

  5. دستور file چطور می‌فهمد یک فایل برنامه‌ی اجرایی است نه متن؟

  • متن در کامپیوتر عدد است و 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()بایت‌های یک متن در پایتون