اوامر لغة بايثون لنظام التشغيل من خلال مكتبة subprocess

اوامر لغة بايثون لنظام التشغيل من خلال مكتبة subprocess

اوامر لغة بايثون لنظام التشغيل يُمكن تنفيذها بأكثر من طريقة. في بايثون، تعلمنا في درس “أُكتب برامج CLI بسعادة مع مكتبة Click في لغة البايثون” أن بإمكاننا جعل برامجنا أن تنفذ أوامر نظام التشغيل في نافذة سطر الأوامر وذلك من خلال استدعاء وظيفة  ()system من مكتبة os وتمرير الأمر المراد تنفيذه إليها.

import os

# use dir on windows
my_output = os.system("ls -al")

لتكون النتيجة هي عرض الأدلة و الملفات الموجودة في الدليل الحالي:

Total 56
drwxr-xr-x 1 runner runner 4096 Jan 22 22:21.
drwxr-xr-x 1 root   root   4096 Sep 21 01:19 ..
-rw-r--r-- 1 runner runner  220 May 15  2017 .bash_logout
-rw-r--r-- 1 runner runner 3526 May 15  2017 .bashrc
drwxr-xr-x 1 runner runner 4096 Jan 22 22:20 .cache
drwxr-xr-x 1 runner runner 4096 Jan 22 22:11 .config
drwxr-xr-x 3 runner runner 4096 Sep 21 01:19 .local
-rw-r--r-- 1 runner runner  675 May 15  2017 .profile
drwxr-xr-x 2 runner runner 4096 Jan 22 22:21 .upm
-rw-r--r-- 1 runner runner  579 Jan 22 22:11 _test_runner.py
-rw-r--r-- 1 runner runner  275 Jan 22 22:21 main.py
-rw-r--r-- 1 runner runner   13 Jan 22 22:11 out.txt

جميل، و لكن ماذا لو أردنا التحكم أكثر في مدخلات ومخرجات تلك العملية؟

لنفهم شيئا مهما في البداية وهو أن أي عملية يقوم بتنفيذها نظام التشغيل لا بد لها من مدخلات ومخرجات وأخطاء إن وجدت. حيث يُمكننا أن نُوجه المخرجات إلى ملف أو حتى لتكون مدخلات لعملية أخرى، بل و حتى التحكم بعرضها على شاشة سطر الأوامر، ولكن كيف يُمكن ذلك؟

اوامر لغة بايثون للنظام من خلال مكتبة subprocess

الجواب هو مكتبة subprocess  القياسية في بايثون والتي ستتيح لنا كل ذلك. لنستعرض أهم مميزاتها بشكل سريع و مفيد لكتابة برامج تتعامل مع مدخلات ومخرجات العمليات في نظام التشغيل.

import subprocess

process_1 = subprocess.run('ls')

في الشيفرة السابقة، قمنا باستدعاء مكتبة subprocess وأنشأنا متغير باسم process_1 وفيه قمنا بتخزين مخرجات الوظيفة ()run والتي بدورها نفذت الأمر ls. وظيفة أمر ls في لينكس و ماك هي أن يعرض محتويات الدليل الحالي بشكل مختصر على شاشة سطر الأوامر. ملاحظة : يقوم نظام التشغيل باعتبار لوحة المفاتيح والفأرة كوسائل مدخلات قياسية والشاشة كوسيلة مخرجات قياسية.

عند طباعة محتويات المتغير أو الكائن process_1 من خلال أمر print ستكون المخرجات كالتالي:

CompletedProcess(args='ls', returncode=0)

نجد أن الكائن الناتج هو من نوع CompletedProcess  والمعامل المدخل هو النص ls والذي تم تنفيذه كأمر في سطر الأوامر والرمز المرجع للعملية صفر، أي أن العملية تمت من غير أخطاء.

ممتاز، ولكن ماذا لو أردنا ادخال الأمر السابق مع اختيارات إضافية لعرض تفاصيل عن محتويات الدليل الحالي كما في أمر “ls -al” ؟ جربوا الأمر وراقبوا ما سيحدث. سنجد أنه حدث خطأ ما و أوقف البرنامج نهائيا، فلماذا يحدث ذلك وما الحل؟

لدينا حلان الأول كالتالي:

import subprocess

process_1 = subprocess.run('ls -al', shell =True)

print(process_1)

ما حدث هنا هو أننا قد حددنا قيمة للمعامل shell وهي True ومعنى ذلك أنه يتم تحويل النص في المعامل الأول كاملا إلي بايتات حقيقية ليتم تنفيذها على سطر الأوامر.

total 56
drwxr-xr-x 1 runner runner 4096 Jan 23 20:09 .
drwxr-xr-x 1 root   root   4096 Sep 21 01:19 ..
-rw-r--r-- 1 runner runner  220 May 15  2017 .bash_logout
-rw-r--r-- 1 runner runner 3526 May 15  2017 .bashrc
drwxr-xr-x 4 runner runner 4096 Sep 21 01:20 .cache
drwxr-xr-x 1 runner runner 4096 Jan 23 20:00 .config
drwxr-xr-x 3 runner runner 4096 Sep 21 01:19 .local
-rw-r--r-- 1 runner runner  675 May 15  2017 .profile
drwxr-xr-x 2 runner runner 4096 Jan 23 20:09 .upm
-rw-r--r-- 1 runner runner  579 Jan 23 20:00 _test_runner.py
-rw-r--r-- 1 runner runner  298 Jan 23 20:09 main.py
-rw-r--r-- 1 runner runner   13 Jan 23 20:00 out.txt
CompletedProcess(args='ls -al', returncode=0)

ملاحظة: في أثناء تنفيذ الأوامر في سطر الأوامر وحتى انتقالها من جهاز لآخر عن طريق الشبكات، يتم تحويل البيانات إلى بايتات.

والحل الآخر هو أن ندخل الأمر و اختياراته داخل قائمة لنحصل على نفس النتيجة:

import subprocess
process_1 = subprocess.run(['ls','-al'])
print(process_1)

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

اظهار نتائج اوامر لغة بايثون لنظام التشغيل

لنستخدم الآن وظيفة جديدة تتيح لنا إظهار النتائج والأخطاء إن وجدت والمدخلات بشكل تفصيلي أكثر وكيف يمكننا الإستفادة من كل ذلك:

import subprocess

process_1 = subprocess.Popen(['ls','-al'],stdin = subprocess.PIPE,

                           stdout = subprocess.PIPE,
                           stderr = subprocess.PIPE, shell = True)


output , error = process_1.communicate()

print(output)
print(error)

و المخرجات ستكون كالتالي:

b'_test_runner.py\nmain.py\nout.txt\n'
b''

كما وضحنا سابقا، إن كل عملية لابد من مدخلات ومخرجات وأخطاء إن وجدت. لنقوم بحصر المخرجات والأخطاء قمنا بإضافة:

stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE

و ذلك لتفعيل عملية الحصر من داخل الوظيفة. تقوم وظيفة  ()communicate  بفرز المخرجات والأخطاء في متغيرات للاستفادة منها و عرضها:

output , error = process_1.communicate()

print(output)
print(error)

والآن لنأخذ ما تعلمناه قدما لنوجه مخرجاتنا لملف بدلا من الشاشة:

import subprocess

fout = open('out.txt','w+')

process_1 = subprocess.Popen(['ls','-al'],stdin = subprocess.PIPE,

                           stdout = fout,
                           stderr = subprocess.PIPE, shell = True)


output , error = process_1.communicate()


print(output)
print(error)

لاحظ أننا بعد أن قمنا بإنشاء ملف بوضعية القراءة والكتابة قمنا بتوجيه المخرجات من الأمر الى ذلك الملف:

stdout = fout

لاحظ الشاشة:

None
b''

لا يوجد شيء ولكن لنقم بفتح الملف out.txt وعرض محتوياته، سنجد أن محتويات المخرجات تم توجيهها و كتابتها فيه:

_test_runner.py
main.py
out.txt

تمرين

لنقم بتغيير المعامل shell=True إلى False ولنقم بتشغيل البرنامج، لنلاحظ المخرجات في ملف المخرجات. والآن لنقم بكتابة البرنامج  التالي و تشغيله:

import subprocess

fout = open('out.txt','a+')
ferr = open('error.txt','a+')

p1 = subprocess.Popen(['ls' ,'ppp',],stderr = ferr,stdout= fout, )

p2 = subprocess.Popen(['pwd'],stdout=fout)

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

p1 = subprocess.Popen(['ls' ,'ppp',],stderr = ferr,stdout= fout, )
p2 = subprocess.Popen(['pwd'],stdout=fout)

قمنا بإنشاء متغير للعملية بتمرير معاملات في قائمة وهي ls , ppp و نعلم أن ppp هي اختيارات خطأ للأمر ls في أنظمة لينكس و ماك، وقمنا أيضا بعمل متغير لعملية ثانية وقمنا بتمرير أمر عزض اسم الدليل الحالي كنص وتوجيه مخرجاته في ملف المخرجات.

وعند عرض ملف المخرجات:

/home/runner

وهي نتيجة العملية الثانية p2، ولكن ماذا عن مخرجات العملية الأولى p1 ؟ لنرى ملف الأخطاء:

ls: cannot access 'ppp': No such file or directory

نرى أن الأمر ls نتج عنده خطأ أثناء التنفيذ وذلك بأن ppp اختيارات ليست متاحة أصلًا في سطر أوامر لينكس و ماك.

المخرجات كمدخلات لأوامر أخرى

ممتاز جدا،  والآن لنقم بعمل برنامج بسيط يقوم بإنشاء عمليتين، الأولى تقوم بعرض الدليل الحالي من خلال تمرير أمر لينكس pwd كمعامل نصي لها، والعملية الثانية تأخذ مخرجات العملية الأولى وهي مسار الدليل الحالي و تعرض محتوياته بالتفصيل وذلك بتطبيق وتنفيذ معاملها النصي ls -lha عليه:

import subprocess

p1 = subprocess.Popen(["pwd"], stdout = subprocess.PIPE)

p2 = subprocess.Popen(['ls','-lha'],stdin = p1.stdout)

نلاحظ في السطر الأخير بأن تم تحديد مدخلات العملية الثانية p2 بتحديد المعامل stdin = p1.stdout من مخرجات العملية الأولى p1. و تم تنفيذ الأمر في القائمة بالعملية الثانية p2 على ما نتج من العملية الأولى وهي موقع الدليل الحالي. و النتيجة هي عرض محتوياته بالتفصيل.

في الختام نتمنى أنكم قد استمتعتم من الشرح السابق فيما يخص مبادئ مكتبة subprocess والاستفادة منها فيما يخص التحكم في توجيه المخرجات والأخطاء سواء بين العمليات أو للملفات.

 

 

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *