بايثون subprocess: شرح شامل لتنفيذ أوامر النظام وإدارة العمليات الخارجية

1. ما هو وحدة subprocess في بايثون

نظرة عامة

وحدة subprocess في بايثون هي أداة قوية لتنفيذ أوامر النظام أو البرامج الخارجية من خلال بايثون. باستخدام هذه الوحدة، يمكنك إدارة عمليات الإدخال والإخراج القياسية والتحكم في العمليات، مما يسهل التكامل بين برامج بايثون والبرامج الخارجية. توفر طريقة أكثر أمانًا ومرونة للتحكم في العمليات مقارنةً باستخدام os.system() أو وحدة commands القديمة.

الاستخدامات الرئيسية

  • تنفيذ أوامر الشيل: استدعاء أوامر النظام البسيطة.
  • إدارة العمليات: تشغيل البرامج الخارجية وإعادة توجيه الإدخال والإخراج القياسي.
  • المعالجة غير المتزامنة: إدارة المهام الطويلة أو المهام المتوازية.

2. الاستخدام الأساسي: subprocess.run()

كيفية الاستخدام الأساسي

دالة subprocess.run() تتيح تنفيذ أوامر النظام بسهولة من داخل بايثون. على سبيل المثال، لعرض قائمة الملفات في مجلد، يمكنك استخدام الكود التالي:

import subprocess

result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(result.stdout)

يقوم هذا الكود بتنفيذ الأمر ls -l وتخزين الناتج في stdout ليتم معالجته بواسطة بايثون. بفضل capture_output=True، يتم التقاط الناتج القياسي، ومع text=True يتم التعامل مع النتائج كنص.

معالجة الأخطاء

باستخدام subprocess.run()، إذا فشل الأمر يمكنك الحصول على رسالة الخطأ عبر stderr والتحقق من حالة التنفيذ عبر returncode.

result = subprocess.run(['ls', 'nonexistentfile'], capture_output=True, text=True)
if result.returncode != 0:
    print(f"خطأ: {result.stderr}")

في هذا المثال، إذا تم تحديد ملف غير موجود، فسيتم عرض رسالة الخطأ من المخرجات القياسية للأخطاء.

侍エンジニア塾

3. التنفيذ غير المتزامن: subprocess.Popen()

المعالجة غير المتزامنة باستخدام Popen

بما أن subprocess.run() يعالج الأوامر بشكل متزامن، فإن برنامج بايثون ينتظر انتهاء الأمر قبل الانتقال للخطوة التالية. لكن باستخدام subprocess.Popen()، يمكنك تنفيذ العمليات بشكل غير متزامن والسماح بمتابعة العمليات الأخرى في نفس الوقت.

import subprocess

proc = subprocess.Popen(['sleep', '5'], stdout=subprocess.PIPE)
print("تم بدء العملية")
proc.wait()
print("تم الانتهاء من العملية")

يقوم هذا الكود بتنفيذ sleep 5 بشكل غير متزامن، ويمكنك متابعة إجراءات أخرى أثناء تنفيذ العملية.

التحكم في الإدخال والإخراج القياسي

باستخدام Popen، يمكنك إعادة توجيه الإدخال والإخراج القياسي بشكل دقيق. على سبيل المثال، يقوم الكود التالي بقراءة البيانات من ملف ومعالجتها باستخدام أمر cat ثم كتابتها في ملف آخر:

with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
    proc = subprocess.Popen(['cat'], stdin=infile, stdout=outfile)
    proc.wait()

بهذه الطريقة، يمكنك إعادة توجيه الإدخال والإخراج للبرامج الخارجية مباشرة إلى ومن الملفات.

4. أمثلة الاستخدام: سكريبتات الأتمتة

نسخ احتياطي للملفات

وحدة subprocess مفيدة جدًا في أتمتة مهام إدارة النظام أو تنفيذ مهام دورية. مثل المثال التالي الذي ينسخ الملفات تلقائيًا إلى مجلد نسخ احتياطي:

import subprocess

files_to_backup = ['file1.txt', 'file2.txt', 'file3.txt']
backup_dir = '/backup/directory/'

for file in files_to_backup:
    subprocess.run(['cp', file, backup_dir])

يقوم هذا الكود بنسخ الملفات المحددة إلى مجلد النسخ الاحتياطي، ما يسمح بأتمتة المهام الدورية بسهولة.

الاستخدام في خطوط CI/CD

تُستخدم subprocess أيضًا في بيئات التكامل المستمر (CI) والتسليم المستمر (CD)، لتشغيل سكريبتات الاختبار أو عمليات النشر تلقائيًا. يمكنك تنفيذ سكريبتات الاختبار والانتقال للخطوات التالية بناءً على النتائج.

RUNTEQ(ランテック)|超実戦型エンジニア育成スクール

5. الأمان وأفضل الممارسات

مخاطر shell=True

خيار shell=True يُستخدم عند تنفيذ الأوامر عبر الشيل، لكنه يحمل مخاطر أمنية خصوصًا عند تمرير مدخلات خارجية مباشرة، حيث قد يتعرض البرنامج لهجمات “حقن الشيل”. يفضل استخدام shell=False للحد من هذه المخاطر.

import subprocess

# الطريقة الموصى بها (آمنة)
subprocess.run(['ls', '-l'])

# shell=True (بحذر)
subprocess.run('ls -l', shell=True)

التوافق مع الأنظمة المختلفة

تختلف أوامر النظام بين أنظمة التشغيل. يمكنك استخدام وحدة platform في بايثون لتحديد النظام وتغيير الأمر المنفذ بناءً على ذلك.

import platform
import subprocess

if platform.system() == "Windows":
    subprocess.run(['dir'], shell=True)
else:
    subprocess.run(['ls', '-l'])

6. استكشاف الأخطاء وتصحيحها

أخطاء شائعة وحلولها

عند استخدام subprocess، قد تواجه أخطاء مثل “عدم العثور على الملف” أو “عدم وجود صلاحية”. يمكنك التقاط هذه الأخطاء عبر stderr والتحقق من returncode لمعرفة التفاصيل.

نصائح لتصحيح الأخطاء

استخدام خيار check=True يجعل بايثون يرفع استثناء عند فشل الأمر، مما يساعد في اكتشاف المشكلات مبكرًا. يمكنك أيضًا التقاط الإخراج القياسي ورسائل الخطأ وتسجيلها لتسهيل عملية التصحيح.

import subprocess

try:
    result = subprocess.run(['ls', '-l'], check=True, capture_output=True, text=True)
    print(result.stdout)
except subprocess.CalledProcessError as e:
    print(f"حدث خطأ: {e}")

7. العمليات غير المتزامنة مع asyncio

استخدام asyncio للمعالجة غير المتزامنة

باستخدام asyncio مع subprocess، يمكنك تنفيذ عدة عمليات في نفس الوقت بشكل غير متزامن. المثال التالي ينفذ أمر ls بشكل غير متزامن ويعرض النتائج:

import asyncio
import subprocess

async def run_command():
    proc = await asyncio.create_subprocess_exec('ls', '-l',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    stdout, stderr = await proc.communicate()

    if stdout:
        print(f'[stdout]n{stdout.decode()}')
    if stderr:
        print(f'[stderr]n{stderr.decode()}')

asyncio.run(run_command())

يقوم هذا الكود بتنفيذ الأمر بشكل غير متزامن ومعالجة نتائج الإخراج والأخطاء بكفاءة باستخدام asyncio.

侍エンジニア塾