برای اجرای پروسه در پایتون میتونیم از ماژول های os.system و subprocess استفاده کنیم
تو این پست با subprocess اشنا میشیم.
این مثال رو ببینید
1 2 3 |
from subprocess import Popen process = Popen(['ls','-l']) process.communicate() |
کد بالا دستور `ls -l` رو در سیستم عامل اجرا میکنه
Popen اجرای دستور رو شروع میکنه ولی منتظر نمیمونه که پروسه تموم بشه این متد communicate هست که اینکارو میکنه
متد communicate یک پارامتر timeout میگیره که اگه اجرای پروسه بیشتر از این مقدار طول بکشه خطای TimeoutExpired ایجاد میشه
برای اینکه خروجی و خطاهای احتمالی دستور رو بگیرید باید `stdout=PIPE` و `stderr=PIPE` قرار بدید
1 2 3 |
from subprocess import PIPE process = Popen(['ls','-l'], stdout=PIPE, stderr=PIPE) stdout, stderr = process.communicate() |
stdout خروجی پروسه هست
stderr خروجی پروسه زمانی که خطا داشته باشه یعنی درواقع خروجی خطای پروسه
برای مثال اگه `ls` رو اجرا کنید stdout میشه لیست فایلها و دایرکتوری های مسیر جاری و stderr خالی خواهد بود
ولی اگه `ls -XXXX` که یک دستور اشتباه هست رو اجرا کنید stdout خالی خواهد بود و stderr مقدار زیر رو خواهد داشت
1 |
ls: invalid option -- 'XXXX'\nTry 'ls --help' for more information. |
برای اینکه stderr به stdout بره و هردو رو یکجا داشته باشید باید `stderr=STDOUT` قرار بدید
1 2 3 |
from subprocess import STDOUT process = Popen(['ls','-l'], stdout=PIPE, stderr=STDOUT) stdout = process.communicate()[0] |
برای اطمینان از اتمام پروسه، از متد wait هم میتونید استفاده کنید، این متد فقط کد خروجی (exit code) رو برمیگردونه
1 |
exitcode = process.wait() |
تو لینوکس برای اینکه exit code دستوری که اجرا کردید رو ببینید
1 |
$ echo $? |
با Popen میتونید مسیر جاری رو عوض کنید تا دستور تو اون مسیر اجرا بشه
1 |
process = Popen(['myapp',], cwd='/home/myapp') |
و یا متغیر محیطی تنظیم کنید
1 |
process = Popen(['myapp',], cwd='/home/myapp', env={'LD_LIBRARY_PATH':'./libs/'}) |
برای اتمام پروسه هم میتونید از متدهای terminate و kill استفاده کنید که اگه اولی رو اجرا کنید سیگنال SIGTERM و دومی سیگنال SIGKILL رو به پروسه میفرسته
1 2 3 4 5 6 |
proc = subprocess.Popen(...) try: outs, errs = proc.communicate(timeout=15) except TimeoutExpired: proc.kill() outs, errs = proc.communicate() |
Popen به صورت پیش فرض اسم برنامه و سوییچ های مورد نظر شما رو در قالب لیست میگیره اینجا `ls` اسم برنامه و `l-` سوییچ مورد نظر ماست
شما میتونید با کمک shlex دستورتون رو به لیست تبدیل کنید اینجوری برنامتون شاید یه مقدار قشنگتر بشه
1 2 |
import shlex process = Popen(shlex.split('ls -l')) |
حالا اگه بخوایم یه دستوری مثل `ps aux | grep x` رو اجرا کنیم داریم
عملگر | خروجی دستور سمت چپش رو به ورودی دستور سمت راستش میفرسته
1 |
process = Popen(['ps','aux','|','grep','x']) |
به خطا میخوریم چون پایتون انتظار داره تمام مقادیر بعد از اسم برنامه سوییچ برنامه باشه ولی اینجا `|` و `grep` سوییچ برنامه `ps` نیست بلکه یک عملگر و یک برنامه هستن.
برای اجرای این دستور دو تا راه حل داریم
اولی
1 2 |
ps = Popen(['ps','aux'], stdout=PIPE) grep = Popen(['grep','x'], stdout=PIPE, stdin=ps.stdout) |
و راه حل دوم این هست که `shell=True` بزاریم که در اینصورت دیگه نباید لیست بدیم و دستور مورد نظر رو در قالب رشته به Popen میدیم
1 |
process = Popen('ps aux | grep x', shell=True) |
حالا اگه تو لینوکس باشید پایتون دستور مورد نظر رو با `bin/sh/` اجرا میکنه به صوت زیر
1 |
$ /bin/sh -c 'ps aux | grep x' |
که در اینصورت 3 تا پروسس `ps`، `sh` و `grep` ایجاد میشه
روش بالا دوتا ایراد داره
اول اینکه از نظر امنیتی خیلی مناسب نیست
دوم هم اینکه اگه برای مثال متد terminate رو اجرا کنید سیگنال SIGTERM فقط به پروسه `sh` ارسال میشه و فقط اون بسته میشه و زیر پروسس ها باز میمونن
برای حل مشکل دوم میتونید از این راه حل استفاده کنید.
دریافت خروجی پروسه به صورت استریم
برای اینکه خروجی پروسه رو به صورت استریم بگیرید
1 2 3 |
with Popen(['strings','/dev/urandom'], stdout=PIPE, stderr=STDOUT) as process: for line in process.stdout: print(line) |
از کانتکس منیجر استفاده کردیم تا زمانی که پروسه تموم شد stdout و stderr اون که بافر هستن بسته شن، اگه از communicate استفاده میکردیم خودش همین کارو میکرد (که چون خروجی رو استریم میخوایم communicate به کارمون نمیاد)