FA-TOOLS — Header Component

آموزش subprocess در پایتون: اجرای دستورات سیستم

در این مقاله چه می‌آموزید؟

در این راهنما، نحوه اجرای دستورات سیستم‌عامل (ویندوز، لینوکس و مک) را از داخل کدهای پایتون با استفاده از ماژول قدرتمند subprocess یاد می‌گیرید. ما تفاوت‌های اساسی بین روش‌های ساده و پیشرفته را بررسی کرده و با مثال‌های واقعی، امنیت و مدیریت خطاها را در فرآیندهای سیستمی پیاده‌سازی می‌کنیم.

هنگام توسعه نرم‌افزار با پایتون، بارها نیاز پیدا می‌کنید که با سیستم‌عامل تعامل داشته باشید؛ کارهایی مثل اجرای یک اسکریپت بش (Bash)، پینگ کردن یک سرور، تبدیل فرمت یک ویدیو با FFmpeg، یا حتی باز کردن یک برنامه خاص. در گذشته از ابزارهایی مانند os.system استفاده می‌شد، اما امروز پایتون یک ابزار بسیار امن‌تر، مدرن‌تر و انعطاف‌پذیرتر به نام subprocess را در اختیار ما قرار داده است.

۱. ماژول subprocess چیست و چرا باید از آن استفاده کنیم؟

آموزش subprocess در پایتون: اجرای دستورات سیستم — تصویر 1

پاسخ کوتاه: ماژول subprocess در پایتون به شما اجازه می‌دهد فرآیندهای جدیدی را در سیستم‌عامل ایجاد کنید، به ورودی‌ها (stdin)، خروجی‌ها (stdout) و خطاهای آن‌ها (stderr) متصل شوید و کدهای بازگشتی خروجی را دریافت کنید. این ماژول جایگزین امن‌تر و قدرتمندتری برای توابع قدیمی مانند os.system است.

توابع قدیمی مانند os.system خروجی دستور را مستقیماً به کنسول می‌فرستادند و پایتون نمی‌توانست نتایج متنی آن دستور را ذخیره یا پردازش کند. علاوه بر این، استفاده از آن‌ها خطرات امنیتی شدیدی مانند تزریق کد به شل (Shell Injection) را به همراه داشت. ماژول subprocess به شما کنترل کاملی روی ورودی و خروجی فرآیندها می‌دهد.

اگر به دنبال توسعه اسکریپت‌های اتوماسیون سیستمی هستید، یادگیری این ماژول در کنار کار با ابزارهای طراحی ظاهر وب مانند ساخت استایل‌های مدرن اهمیت بالایی دارد. برای یادگیری ترفندهای بیشتر در حوزه طراحی صفحات وب، می‌توانید به بخش سورس کدهای CSS مراجعه کنید تا رابط کاربری اسکریپت‌های خود را جذاب‌تر طراحی کنید.

۲. تابع اصلی: subprocess.run (پرکاربردترین روش)

آموزش subprocess در پایتون: اجرای دستورات سیستم — تصویر 2

پاسخ کوتاه: تابع subprocess.run() معرفی شده در پایتون ۳.۵، بهترین و ساده‌ترین راه برای اجرای اکثر دستورات سیستمی است. این تابع دستور را اجرا کرده، منتظر اتمام آن می‌ماند و در نهایت یک شیء از نوع CompletedProcess را برمی‌گرداند.

بیایید با یک مثال بسیار ساده شروع کنیم. فرض کنید می‌خواهید لیست فایل‌های پوشه فعلی را در سیستم‌عامل لینوکس یا مک بگیرید (معادل دستور ls -la):

import subprocess

# اجرای دستور ساده لینوکسی
result = subprocess.run(["ls", "-l"])
print("کد وضعیت خروجی:", result.returncode)

توجه داشته باشید که ما دستورات را به صورت یک لیست از رشته‌ها ارسال می‌کنیم. این کار امنیت اسکریپت شما را به شدت افزایش می‌دهد.

دریافت و ذخیره خروجی دستور در پایتون

در مثال بالا، خروجی دستور مستقیماً در ترمینال چاپ می‌شود. اگر بخواهیم این خروجی متنی را در یک متغیر پایتون ذخیره کنیم تا بتوانیم روی آن پردازش انجام دهیم، باید از پارامترهای capture_output=True و text=True استفاده کنیم:

import subprocess

# اجرای دستور و ذخیره خروجی به صورت متن ساده
result = subprocess.run(["echo", "Hello from Python!"], capture_output=True, text=True)

# چاپ خروجی دریافت شده
print("خروجی دستور:", result.stdout.strip())

مدیریت هوشمندانه خطاها با check=True

اگر دستوری که اجرا می‌کنید با خطا مواجه شود (مثلاً فایلی که می‌خواهید بخوانید وجود نداشته باشد)، فرآیند با یک کد وضعیت غیر صفر به پایان می‌رسد. برای اینکه پایتون در صورت بروز خطا بلافاصله یک استثنا (Exception) صادر کند، پارامتر check=True را اضافه می‌کنیم:

import subprocess

try:
    # تلاش برای اجرای یک دستور اشتباه یا ناموجود
    subprocess.run(["ls", "non_existent_folder"], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
    print("خطایی رخ داد! کد خطا:", e.returncode)
    print("متن خطا:", e.stderr)

۳. کار با کلاس پیشرفته Popen (مدیریت جریان‌های پیچیده)

آموزش subprocess در پایتون: اجرای دستورات سیستم — تصویر 3

پاسخ کوتاه: کلاس subprocess.Popen برای مدیریت فرآیندهای ناهمگام (Asynchronous) و بلندمدت استفاده می‌شود. برخلاف run، این کلاس بلافاصله بعد از شروع فرآیند کنترل را به برنامه پایتون بازمی‌گرداند تا برنامه بتواند هم‌زمان با اجرای فرآیند، کارهای دیگری انجام دهد یا خروجی را خط به خط بخواند.

اگر دستور شما زمان‌بر است (مثلاً دانلود یک فایل بزرگ یا اجرای یک فرآیند پشتیبان‌گیری) و می‌خواهید خروجی آن را در حین اجرا به صورت زنده نمایش دهید، Popen بهترین ابزار است:

import subprocess

# اجرای دستور پینگ به صورت پس‌زمینه و خواندن خروجی به صورت زنده
process = subprocess.Popen(
    ["ping", "-c", "4", "8.8.8.8"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)

# خواندن خط به خط خروجی به محض تولید شدن توسط سیستم‌عامل
for line in process.stdout:
    print("دریافت زنده:", line.strip())

# انتظار برای پایان نهایی فرآیند
process.wait()
print("فرآیند با موفقیت پایان یافت.")

اگر به دنبال پیاده‌سازی چنین قابلیت‌های پیشرفته‌ای در پروژه‌های مختلف خود هستید، مطالعه سورس کدهای پایتون می‌تواند الگوهای آماده و کاربردی متعددی را در اختیار شما قرار دهد تا سرعت کدنویسی خود را افزایش دهید.

۴. جدول مقایسه کارکرد run و Popen

آموزش subprocess در پایتون: اجرای دستورات سیستم — تصویر 4

برای اینکه بهتر درک کنید چه زمانی باید از کدام ابزار استفاده کنید، جدول زیر تفاوت‌های کلیدی این دو روش اصلی را نشان می‌دهد:

ویژگی / معیار کارکرد تابع subprocess.run() کلاس subprocess.Popen()
نوع رفتار (Blocking) بلاک‌کننده (منتظر می‌ماند تا دستور تمام شود) غیربلاک‌کننده (بلافاصله به خط بعدی پایتون می‌رود)
سهولت در استفاده بسیار ساده و سرراست پیچیده‌تر (نیاز به مدیریت دستی فرآیند دارد)
خواندن زنده خروجی امکان‌پذیر نیست (فقط در پایان کار خروجی را می‌دهد) کاملاً امکان‌پذیر (خط به خط در زمان واقعی)
بهترین کاربرد دستورات سریع، اجرای تست‌ها، کارهای ساده سیستمی برنامه‌های طولانی‌مدت، مانیتورینگ زنده، تعامل دوطرفه با دستور

۵. سناریوهای واقعی و کاربردی در دنیای واقعی

آموزش subprocess در پایتون: اجرای دستورات سیستم — تصویر 5

سناریوی اول: بکاپ‌گیری اتوماتیک و فشرده‌سازی پوشه‌ها

فرض کنید می‌خواهید از یک پوشه مهم در سرور بکاپ گرفته و آن را به صورت فایل .tar.gz فشرده کنید:

import subprocess

def create_backup(source_dir, output_filename):
    try:
        # اجرای دستور فشرده‌سازی در لینوکس
        subprocess.run(["tar", "-czf", output_filename, source_dir], check=True)
        print(f"پشتیبان‌گیری با موفقیت در فایل {output_filename} انجام شد.")
    except subprocess.CalledProcessError:
        print("خطا در حین فرآیند پشتیبان‌گیری رخ داد!")

create_backup("/var/www/html", "backup.tar.gz")

سناریوی دوم: تعریف محدودیت زمان (Timeout) برای دستورات طولانی

گاهی اوقات ممکن است یک دستور سیستمی به دلایلی (مانند قطع شدن شبکه یا قفل شدن منابع) بی‌پایان ادامه پیدا کند. شما می‌توانید با استفاده از پارامتر timeout یک سقف زمانی بر حسب ثانیه تعریف کنید تا اسکریپت شما متوقف نشود:

import subprocess

try:
    # اگر دستور بیش از ۵ ثانیه طول بکشد، متوقف می‌شود
    subprocess.run(["ping", "8.8.8.8"], timeout=5)
except subprocess.TimeoutExpired:
    print("خطای زمان مجاز: اجرای دستور بیش از حد طول کشید و متوقف شد!")

۶. نکات امنیتی حیاتی: جلوگیری از Shell Injection

بزرگ‌ترین اشتباه امنیتی هنگام استفاده از این ماژول، فعال کردن گزینه shell=True و ترکیب مستقیم متغیرهای ورودی کاربر با دستورات است. به کد خطرناک زیر دقت کنید:

# کدی بسیار خطرناک و ناامن!
user_input = "image.png; rm -rf /"  # تزریق دستور مخرب
subprocess.run(f"ls {user_input}", shell=True)

در مثال ناامن بالا، هکر می‌تواند با اضافه کردن یک کاراکتر سمیکولن (;)، دستور مخرب خود را بلافاصله بعد از دستور اول در سرور شما اجرا کند.

راهکار امن چیست؟
همواره shell=False را به عنوان حالت پیش‌فرض حفظ کنید و دستورات خود را به صورت آرایه‌ای (لیست) از رشته‌ها به تابع ارسال کنید. در این حالت سیستم‌عامل ورودی کاربر را صرفاً به عنوان یک آرگومان متنی ساده تفسیر می‌کند، نه بخشی از دستور اجرایی شل.

۷. پرسش‌های متداول (FAQ)

۱. تفاوت os.system و subprocess در چیست؟

تابع os.system بسیار قدیمی و ناامن است، خروجی دستور را به پایتون باز نمی‌گرداند و کنترل دقیقی روی فرآیندها ندارد. در مقابل، subprocess امن است، امکان مدیریت مستقیم ورودی/خروجی را فراهم می‌کند و استانداردهای مدرن برنامه‌نویسی را رعایت کرده است.

۲. چطور می‌توانیم خروجی فارسی یا UTF-8 دستورات را بدون مشکل بخوانیم؟

برای جلوگیری از به‌هم‌ریختگی متون فارسی یا غیر انگلیسی، در زمان فراخوانی تابع run یا کلاس Popen، پارامتر encoding="utf-8" را به صورت صریح تعریف کنید تا خروجی‌ها به درستی دیکود شوند.

۳. آیا با subprocess می‌توان دستورات خط فرمان ویندوز مانند dir را اجرا کرد؟

بله، اما برای برخی دستورات بومی خط فرمان ویندوز (مانند dir یا echo) که فایل اجرایی مستقل نیستند و بخشی از خود cmd هستند، باید پارامتر shell=True را اضافه کنید یا از فرمت ["cmd", "/c", "dir"] بهره ببرید.

۴. چگونه بفهمیم که اجرای دستور موفقیت‌آمیز بوده است؟

بعد از اتمام دستور، ویژگی returncode را بررسی کنید. مقدار عددی صفر نشانه اجرای موفقیت‌آمیز و بی‌نقص است، و مقادیر غیر صفر نشان‌دهنده بروز خطاهای مختلف سیستمی هستند.

۸. عیب‌یابی سریع و رفع خطاهای رایج

خطای FileNotFoundError: [Errno 2] No such file or directory

علت وقوع: این خطا زمانی اتفاق می‌افتد که پایتون نتواند فایل اجرایی دستور مورد نظر شما را در کلیدهای PATH سیستم پیدا کند، یا اینکه دستور را به شکل یک رشته کامل بدون تفکیک به آرایه ارسال کرده‌اید.

✅ راه‌حل عملی:

  • مطمئن شوید دستور به صورت لیست نوشته شده است: ["ping", "google.com"] به جای "ping google.com".
  • آدرس کامل و مطلق ابزار را بدهید (مثلاً به جای ffmpeg، بنویسید /usr/bin/ffmpeg).

بن‌بست یا فریز شدن برنامه هنگام استفاده از stdout=PIPE

علت وقوع: اگر حجم داده‌های ارسالی به لوله خروجی (Pipe) بسیار زیاد باشد و برنامه پایتون شما به موقع اقدام به خواندن اطلاعات نکند، بافر لوله پر شده و فرآیند تا ابد متوقف (Deadlock) می‌ماند.

✅ راه‌حل عملی:

  • به جای استفاده مستقیم از stdout=PIPE در کارهای پرحجم، از تابع کاربردی process.communicate() برای تعامل امن و بدون بن‌بست با فرآیند استفاده کنید.

Table of Contents

آخرین نوشته‌ها

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *