با کمک یک سناریوی واقعی، یاد میگیریم چطور باگهای یک اسکریپت را پیدا کنیم، آن را تعمیر کنیم و تغییرات را با diff و patch به همکار خود منتقل کنیم.
تصور کن یک همکار از تو کمک خواسته تا یک اسکریپت به نام
disk_usage.py را تعمیر کنی. هدف این اسکریپت این است که
بررسی کند چقدر فضای دیسک در حال استفاده است و اگر فضای خالی برای
عملکرد عادی خیلی کم بود، یک پیام خطا چاپ کند.
اما اسکریپت در حال حاضر شکست خورده (broken) است و چند باگ دارد. در این درسنامه، با هم این باگها را پیدا و رفع میکنیم و از ابزارهای diff و patch استفاده میکنیم تا تغییرات را به همکار خود منتقل کنیم.
💡 نکته: این یک مثال واقعی از همکاری تیمی در توسعه نرمافزار است. با استفاده از diff و patch، میتوانیم تغییرات را دقیقاً به دیگران منتقل کنیم و از اشتباهات جلوگیری کنیم.
قبل از هر تغییری، بهتر است از فایل اصلی کپی بگیریم تا
یک نسخه دستنخورده برای مقایسه داشته باشیم. این کار خیلی مفید است چون
بعداً میتوانیم با استفاده از diff، تغییرات را با نسخه
اصلی مقایسه کنیم.
برای این کار، از دستور cp (کپی) استفاده میکنیم و دو کپی میسازیم:
$ cp disk_usage.py disk_usage_original.py
$ cp disk_usage.py disk_usage_fixed.py
disk_usage_original.py – نسخه اصلی که دست نمیزنیم و برای مقایسه استفاده میشود.disk_usage_fixed.py – نسخهای که روی آن تغییرات تعمیری را اعمال میکنیم.⚡ ترفند: همیشه قبل از شروع تغییرات، از فایل اصلی کپی بگیر. این کار به تو امکان میدهد هر زمان خواستی، تغییرات را با نسخه اصلی مقایسه کنی و اگر کار به خطا رفت، برگردی به نقطه اول.
بیایید ابتدا اسکریپت را اجرا کنیم تا ببینیم چه خطایی میدهد:
$ ./disk_usage_fixed.py
File "./disk_usage_fixed.py", line 19
return 1
^
SyntaxError: 'return' outside function
مترجم پایتون (Python interpreter) از این خطا ناراضی است! خطا میگوید:
«return خارج از تابع». یعنی در جایای از کد، از دستور
return استفاده شده که داخل هیچ تابعی قرار ندارد.
همانطور که ممکن است به خاطر داشته باشی، در پایتون ما فقط میتوانیم از
دستور return در داخل توابع استفاده کنیم.
وقتی کد را بررسی میکنیم، میبینیم که یک return 1 و
return 0 در بخش اصلی اسکریپت (خارج از هر تابعی) قرار گرفتهاند که این اشتباه است.
⚠️ هشدار: استفاده از return خارج از تابع، یک خطای نحوی (syntax error) محسوب میشود و مانع از اجرای اسکریپت میشود.
برای رفع این مشکل، دو راه داریم:
sys.exit استفاده کنیم تا کد خروج (exit code) اسکریپت را تنظیم کنیم.در اینجا راه دوم را انتخاب میکنیم. برای این کار:
sys را در ابتدای فایل وارد (import) میکنیم.return 1 و return 0 را با sys.exit(1) و sys.exit(0) جایگزین میکنیم.
توضیح: sys.exit() یک تابع است که باعث
پایان یافتن برنامه با یک کد خروج (exit code) مشخص میشود. عدد 0 معمولاً به معنای «موفقیت» و عدد 1 به معنای «خطا» است.
📌 نکته: استفاده از sys.exit یک روش استاندارد در پایتون برای پایان دادن به برنامه با کد خروج مناسب است.
کد اصلاحشده به این شکل خواهد بود:
#!/usr/bin/env python3
import shutil
import sys # ← ماژول sys اضافه شد
def check_disk_usage(disk, min_absolute, min_percent):
"""Returns True if there is enough free disk space, false otherwise."""
du = shutil.disk_usage(disk)
percent_free = 100 * du.free / du.total
# ... (بقیه کد)
if not check_disk_usage("/", 2*2**30, 10):
print("ERROR: Not enough disk space")
sys.exit(1) # ← به جای return 1
print("Everything ok")
sys.exit(0) # ← به جای return 0
حالا بیایید نسخه جدید اسکریپت را اجرا کنیم تا ببینیم چه میشود:
$ ./disk_usage_fixed.py
ERROR: Not enough disk space
خطای نحوی (syntax error) را رفع کردیم، اما الان اسکریپت میگوید «فضای کافی روی دیسک وجود ندارد». در حالی که ما میدانیم فضای خالی داریم! مشکل از کجاست؟
اگر با دقت به کد نگاه کنیم، متوجه میشویم که اسکریپت دو بار به گیگابایت تبدیل انجام میدهد:
check_disk_usage با پارامتر 2*2**30 صدا زده شده است. (در پایتون، ** عملگر توان است و 2**30 یعنی ۲ به توان ۳۰ که برابر با تعداد بایتهای یک گیگابایت است. بنابراین 2*2**30 یعنی ۲ گیگابایت.)check_disk_usage نیز مقدار فضای آزاد را دوباره به گیگابایت تبدیل میکند.یعنی تبدیل به گیگابایت دو بار انجام شده است. یک بار در هنگام فراخوانی تابع و یک بار در داخل تابع. این باعث میشود که عدد مورد انتظار ۲ گیگابایت، در عمل خیلی بزرگتر شود و اسکریپت فکر کند فضای کافی وجود ندارد.
💡 نکته: در پایتون، 2**30 برابر با ۱,۰۷۳,۷۴۱,۸۲۴ بایت (یک گیگابایت) است. پس 2*2**30 یعنی ۲,۱۴۷,۴۸۳,۶۴۸ بایت یا همان ۲ گیگابایت.
برای رفع این مشکل، باید یکی از تبدیلها را حذف کنیم. بهترین کار این است که نحوه صدا زدن تابع را تغییر دهیم و پارامتر را به شکل درست اصلاح کنیم.
با توجه به اینکه تابع check_disk_usage انتظار مقدار
گیگابایت را دارد (چون داخل تابع، دوباره به گیگابایت تبدیل میکند)، باید عدد 2 را به عنوان پارامتر بدهیم و تبدیل به بایت را حذف کنیم.
پس خط فراخوانی تابع را از:
if not check_disk_usage("/", 2*2**30, 10):
به این شکل تغییر میدهیم:
if not check_disk_usage("/", 2, 10):
حالا تابع با پارامتر ۲ گیگابایت صدا زده میشود و داخل تابع، این عدد را به درستی با فضای آزاد مقایسه میکند.
🧠 نکته: دقت کن که تابع check_disk_usage سه پارامتر دارد: disk (مسیر دیسک)، min_absolute (حداقل فضای آزاد بر حسب گیگابایت)، و min_percent (حداقل درصد فضای آزاد).
حالا بیایید دوباره اسکریپت را اجرا کنیم و ببینیم نتیجه چیست:
$ ./disk_usage_fixed.py
Everything ok
عالی! اسکریپت به درستی کار میکند و پیام «همه چیز خوب است» را چاپ میکند. یعنی فضای دیسک کافی است و باگها را بهدرستی رفع کردهایم.
✅ نتیجه: با رفع دو باگ – یکی مربوط به استفاده نادرست از return و دیگری مربوط به تبدیل دوبرابر به گیگابایت – اسکریپت بهدرستی اجرا شد.
حالا که اسکریپت را تعمیر کردهایم، باید تغییرات را به همکار خود منتقل
کنیم تا او هم بتواند اسکریپت خود را تعمیر کند. برای این کار از تکنیکی
که یاد گرفتیم استفاده میکنیم: تولید یک فایل diff با
دستور diff -u.
نسخه اصلی (که دست نخورده باقی مانده) را با نسخه تعمیرشده مقایسه میکنیم:
$ diff -u disk_usage_original.py disk_usage_fixed.py > disk_usage.diff
حالا بیایید محتوای فایل diff را با cat ببینیم:
$ cat disk_usage.diff
--- disk_usage_original.py 2019-06-22 15:13:38.591579963 -0700
+++ disk_usage_fixed.py 2019-06-22 15:41:35.013023839 -0700
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import shutil
+import sys
def check_disk_usage(disk, min_absolute, min_percent):
"""Returns True if there is enough free disk space, false otherwise."""
@@ -14,9 +15,9 @@
return True
# Check for at least 2 GB and 10% free
-if not check_disk_usage("/", 2*2**30, 10):
+if not check_disk_usage("/", 2, 10):
print("ERROR: Not enough disk space")
- return 1
+ sys.exit(1)
print("Everything ok")
-return 0
+sys.exit(0)
این فایل diff شامل همه تغییرات لازم برای تعمیر اسکریپت است. همکار ما میتواند با دریافت این فایل و استفاده از دستور patch، اسکریپت خود را تعمیر کند.
📌 نکته: فایل diff دقیقاً نشان میدهد که چه چیزی تغییر کرده است: اضافه شدن import sys، اصلاح پارامتر فراخوانی تابع، و جایگزینی return با sys.exit.
حالا فرض کن همکار ما فایل diff را دریافت کرده است. چگونه باید آن را
روی اسکریپت خودش اعمال کند؟ بسیار ساده! با دستور patch:
$ patch disk_usage.py < disk_usage.diff
patching file disk_usage.py
با اجرای این دستور، patch تغییرات را روی فایل disk_usage.py اعمال میکند. حالا بیایید بررسی کنیم که اسکریپت بهدرستی اجرا میشود:
$ ./disk_usage.py
Everything ok
موفقیت! اسکریپت همکار ما نیز با موفقیت تعمیر شد و پیام «همه چیز خوب است» را چاپ کرد.
✅ نتیجه: با استفاده از patch، تغییرات را بهسرعت و بدون خطا روی فایل اصلی اعمال کردیم.
در این درسنامه، با یک سناریوی واقعی آشنا شدیم و دیدیم که چگونه میتوان با استفاده از ابزارهای diff و patch:
این فرایند هنوز تا حد زیادی دستی است، اما درک این مفاهیم پایه، ما را برای ورود به دنیای سیستمهای کنترل نسخه (Version Control Systems) مانند Git آماده میکند.
در ادامه، یک خلاصه از دستوراتی که یاد گرفتیم در اختیار تو قرار میگیرد. حتماً آن را مطالعه کن و سپس به سراغ آزمون تمرینی (practice quiz) برو تا مطمئن شوی همه مفاهیم را بهخوبی درک کردهای.
🚀 قدم بعدی: در درسنامههای آینده، با Git آشنا خواهیم شد که این فرایند را بسیار سادهتر و خودکارتر میکند.
🎉 با اتمام این جلسه، شما با کاربرد عملی diff و patch در یک سناریوی واقعی آشنا شدید و گامی مهم به سوی درک سیستمهای کنترل نسخه برداشتید.