[python] استدعاء أمر خارجي في بايثون



Answers

فيما يلي ملخص لطرق استدعاء البرامج الخارجية ومزايا وعيوب كل منها:

  1. os.system("some_command with args") بتمرير الأوامر os.system("some_command with args") إلى shell الخاص بالنظام. هذا أمر جيد لأنه يمكنك بالفعل تشغيل أوامر متعددة في وقت واحد بهذه الطريقة وإعداد الأنابيب وإعادة توجيه الإدخال / الإخراج. فمثلا:

    os.system("some_command < input_file | another_command > output_file")  
    

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

  2. stream = os.popen("some_command with args") سوف يفعل نفس الشيء مثل os.system إلا أنه يعطيك كائن يشبه الملف الذي يمكنك استخدامه للوصول إلى الإدخال / الإخراج القياسي لهذه العملية. هناك 3 أنواع أخرى من popen التي تعالج جميع i / o بشكل مختلف قليلاً. إذا قمت بتمرير كل شيء كسلسلة ، فسيتم تمرير الأمر إلى shell ؛ إذا قمت بتمريرها كقائمة ، فلا داعي للقلق بشأن الهروب من أي شيء. انظر الوثائق .

  3. فئة Popen من الوحدة النمطية Popen . هذا هو المقصود كبديل لـ os.popen ولكن له جانب سلبي من كونه أكثر تعقيدًا بقليل بسبب كونه شاملاً للغاية. على سبيل المثال ، يمكنك قول:

    print subprocess.Popen("echo Hello World", shell=True, stdout=subprocess.PIPE).stdout.read()
    

    بدلا من:

    print os.popen("echo Hello World").read()
    

    ولكن من الجيد أن يكون لديك جميع الخيارات في صف واحد موحّد بدلاً من 4 وظائف بوبينية مختلفة. انظر الوثائق .

  4. وظيفة call من الوحدة subprocess . هذا في الأساس تمامًا مثل فئة Popen ويأخذ نفس الوسيطات نفسها ، ولكنه ببساطة ينتظر حتى يكمل الأمر ويمنحك رمز الإرجاع. فمثلا:

    return_code = subprocess.call("echo Hello World", shell=True)  
    

    انظر الوثائق .

  5. إذا كنت تستخدم Python 3.5 أو ما بعده ، فيمكنك استخدام الدالة subprocess.run الجديدة ، والتي تشبه إلى حد كبير ما ورد أعلاه ولكنها أكثر مرونة CompletedProcess كائن CompletedProcess عند انتهاء الأمر.

  6. تشتمل وحدة نظام التشغيل أيضًا على جميع وظائف fork / exec / spawn التي لديك في برنامج C ، ولكن لا أنصح باستخدامها بشكل مباشر.

ربما يجب أن تكون وحدة العمليات subprocess هي ما تستخدمه.

وأخيرًا ، يُرجى الانتباه إلى أنه بالنسبة إلى جميع الطرق التي يتم فيها تمرير الأمر النهائي ليتم تنفيذه من قِبل shell كسلسلة ، فأنت مسؤول عن الفرار منه. هناك تداعيات أمنية خطيرة إذا كان أي جزء من السلسلة التي تمررها لا يمكن الوثوق به تمامًا. على سبيل المثال ، إذا كان المستخدم يدخل بعض / أي جزء من السلسلة. إذا كنت غير متأكد ، استخدم هذه الطرق فقط مع الثوابت. لإعطائك تلميحًا للآثار المترتبة على هذا الرمز:

print subprocess.Popen("echo %s " % user_input, stdout=PIPE).stdout.read()

وتخيل أن المستخدم يدخل "ماما لم تحبني && rm -rf /".

Question

كيف يمكنني الاتصال بأمر خارجي (كما لو كنت اكتبه في shell Unix أو موجه أوامر Windows) من خلال نص برمجي Python؟




In Windows you can just import the subprocess module and run external commands by calling subprocess.Popen() , subprocess.Popen().communicate() and subprocess.Popen().wait() as below:

# Python script to run a command line
import subprocess

def execute(cmd):
    """
        Purpose  : To execute a command and return exit status
        Argument : cmd - command to execute
        Return   : exit_code
    """
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (result, error) = process.communicate()

    rc = process.wait()

    if rc != 0:
        print "Error: failed to execute command:", cmd
        print error
    return result
# def

command = "tasklist | grep python"
print "This process detail: \n", execute(command)

انتاج:

This process detail:
python.exe                     604 RDP-Tcp#0                  4      5,660 K



تحقق من مكتبة بيثون "pexpect" أيضًا.

يسمح بالتحكم التفاعلي في البرامج / الأوامر الخارجية ، حتى ssh ، ftp ، telnet ، إلخ. يمكنك فقط كتابة شيء مثل:

child = pexpect.spawn('ftp 192.168.0.24')

child.expect('(?i)name .*: ')

child.sendline('anonymous')

child.expect('(?i)password')



هذه هي الطريقة التي أدير بها الأوامر. يحتوي هذا الرمز على كل ما تحتاج إليه كثيرًا

from subprocess import Popen, PIPE
cmd = "ls -l ~/"
p = Popen(cmd , shell=True, stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
print "Return code: ", p.returncode
print out.rstrip(), err.rstrip()



import os
cmd = 'ls -al'
os.system(cmd)

إذا كنت ترغب في إرجاع نتائج الأمر ، يمكنك استخدام os.popen . ومع ذلك ، يتم إيقاف هذا منذ الإصدار 2.6 لصالح الوحدة النمطية subprocess ، التي تمت تغطية الإجابات الأخرى بشكل جيد.




يمكنك استخدام Popen ، ومن ثم يمكنك التحقق من حالة الإجراء:

from subprocess import Popen

proc = Popen(['ls', '-l'])
if proc.poll() is None:
    proc.kill()

تحقق من subprocess.Popen .




os.system ما يرام ، ولكن نوع من مؤرخة. كما أنها ليست آمنة جدا. بدلا من ذلك ، حاول subprocess . لا يستدعي subprocess sh مباشرة وبالتالي فهو أكثر أمانًا من os.system .

احصل على المزيد من المعلومات here .




لإحضار معرف الشبكة من النيوترون المفتوح:

#!/usr/bin/python
import os
netid= "nova net-list | awk '/ External / { print $2 }'"
temp=os.popen(netid).read()  /* here temp also contains new line (\n) */
networkId=temp.rstrip()
print(networkId)

إخراج قائمة nova net

+--------------------------------------+------------+------+
| ID                                   | Label      | CIDR |
+--------------------------------------+------------+------+
| 431c9014-5b5d-4b51-a357-66020ffbb123 | test1      | None |
| 27a74fcd-37c0-4789-9414-9531b7e3f126 | External   | None |
| 5a2712e9-70dc-4b0e-9281-17e02f4684c9 | management | None |
| 7aa697f5-0e60-4c15-b4cc-9cb659698512 | Internal   | None |
+--------------------------------------+------------+------+

إخراج الطباعة (networkId)

27a74fcd-37c0-4789-9414-9531b7e3f126



إذا كان ما تحتاجه هو الإخراج من الأمر الذي تتصل به ،
ثم يمكنك استخدام subprocess.check_output (Python 2.7+).

>>> subprocess.check_output(["ls", "-l", "/dev/null"])
'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'

لاحظ أيضًا معلمة shell .

إذا كانت shell هي True ، فسيتم تنفيذ الأمر المحدد من خلال shell. قد يكون هذا مفيدًا إذا كنت تستخدم Python بشكل أساسي لتدفق التحكم المحسّن الذي توفره على معظم غلاف النظام ولا تزال تريد الوصول إلى ميزات shell الأخرى مثل أنابيب shell وأسماء أحرف اسم الملف وتوسيع متغير البيئة والتوسع في ~ إلى منزل المستخدم دليل. ومع ذلك ، لاحظ أن بايثون نفسها تقدم تطبيقات للعديد من الميزات الشبيهة بالقشرة (على وجه الخصوص ، fnmatch ، os.walk() ، os.walk() ، os.path.expandvars() ، os.path.expanduser() ، و shutil ).




بعض التلميحات حول فصل عملية الطفل عن الاتصال (بدء عملية الطفل في الخلفية).

لنفترض أنك تريد بدء مهمة طويلة من نص CGI ، أي أن عملية الطفل يجب أن تعيش لفترة أطول من عملية تنفيذ CGI - script.

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

import subprocess
import sys

# some code here

pid = subprocess.Popen([sys.executable, "longtask.py"]) # call subprocess

# some more code here

الفكرة هنا هي أنك لا تريد الانتظار في السطر "استدعاء subprocess" حتى تنتهي longtask.py. ولكن ليس من الواضح ما الذي يحدث بعد السطر "المزيد من الشفرات هنا" من المثال.

كان النظام الأساسي المستهدف هو freebsd ، ولكن كان التطوير على النوافذ ، لذلك واجهت المشكلة على النوافذ أولاً.

على النوافذ (win xp) ، لن تنتهي العملية الأصل حتى تنتهي longtask.py من عملها. ليس ما تريد في السيناريو CGI. المشكلة ليست خاصة بيثون ، في مجتمع PHP المشاكل هي نفسها.

الحل هو تمرير DETACHED_PROCESS إشارة إنشاء العملية إلى الوظيفة CreateProcess الأساسية في win API. إذا كنت قد قمت بتثبيت pywin32 يمكنك استيراد العلم من وحدة win32process ، وإلا يجب عليك تعريفه بنفسك:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid

/ * UPD 2015.10.27eryksun في تعليق أدناه ملاحظات ، أن العلامة الصحيحة لغويًا هي CREATE_NEW_CONSOLE (0x00000010) * /

على freebsd لدينا مشكلة أخرى: عند الانتهاء من العملية الأم ، فإنه ينتهي من عمليات الطفل كذلك. وهذا ليس ما تريد في السيناريو CGI أما. أظهرت بعض التجارب أن المشكلة كانت في مشاركة sys.stdout. وكان حل العمل هو التالي:

pid = subprocess.Popen([sys.executable, "longtask.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)

لم تتحقق من الشفرة على منصات أخرى ولا أعرف أسباب السلوك على فري بي. إذا كان أي شخص يعرف ، يرجى مشاركة أفكارك. غوغلينغ على بدء عمليات الخلفية في بايثون لا يلقي أي ضوء حتى الآن.




هناك أيضا Plumbum

>>> from plumbum import local
>>> ls = local["ls"]
>>> ls
LocalCommand(<LocalPath /bin/ls>)
>>> ls()
u'build.py\ndist\ndocs\nLICENSE\nplumbum\nREADME.rst\nsetup.py\ntests\ntodo.txt\n'
>>> notepad = local["c:\\windows\\notepad.exe"]
>>> notepad()                                   # Notepad window pops up
u''                                             # Notepad window is closed by user, command returns



تعتبر subprocess.check_call ملائمة إذا كنت لا تريد اختبار قيم الإرجاع. يلقي استثناء على أي خطأ.




لا يسمح لك os.system بتخزين النتائج ، لذلك إذا كنت تريد تخزين النتائج في بعض القوائم أو شيء ما يعمل subprocess.call .




تحديث:

subprocess.run هي الطريقة الموصى بها كما في Python 3.5 إذا لم تكن شفرتك بحاجة إلى التوافق مع إصدارات Python السابقة. إنها أكثر اتساقًا وتوفر سهولة استخدام مماثلة للمبعوث. (لا يعد وضع الأنابيب أمرًا بسيطًا على الرغم من ذلك. انظر هذا السؤال لمعرفة كيفية القيام بذلك ).

إليك بعض الأمثلة من المستندات .

قم بتشغيل عملية:

>>> subprocess.run(["ls", "-l"])  # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

الزيادة في التشغيل الفاشل:

>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

التقاط الإخراج:

>>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')

الجواب الأصلي:

أوصي المحاولة https://github.com/kennethreitz/envoy . وهو عبارة عن غلاف للمعالجة الثانوية ، والذي يهدف بدوره إلى استبدال الوحدات والوظائف القديمة. المبعوث هو عملية فرعية للبشر.

استخدام مثال من الملف التمهيدي :

>>> r = envoy.run('git config', data='data to pipe in', timeout=2)

>>> r.status_code
129
>>> r.std_out
'usage: git config [options]'
>>> r.std_err
''

الاشياء الأنابيب حولها أيضا:

>>> r = envoy.run('uptime | pbcopy')

>>> r.command
'pbcopy'
>>> r.status_code
0

>>> r.history
[<Response 'uptime'>]



استدعاء أمر خارجي في بايثون

بسيطة ، استخدم subprocess.run ، والتي ترجع كائن CompletedProcess :

>>> import subprocess
>>> completed_process = subprocess.run('python --version')
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
>>> completed_process
CompletedProcess(args='python --version', returncode=0)

لماذا ا؟

اعتبارًا من Python 3.5 ، توصي الوثائق بـ subprocess.run :

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

إليك مثال على أبسط استخدام ممكن - وهو يفعل بالضبط كما هو مطلوب:

>>> import subprocess
>>> completed_process = subprocess.run('python --version')
Python 3.6.1 :: Anaconda 4.4.0 (64-bit)
>>> completed_process
CompletedProcess(args='python --version', returncode=0)

run ينتظر الأمر لإنهاء بنجاح ، ثم إرجاع كائن CompletedProcess . قد بدلاً من ذلك رفع TimeoutExpired (إذا قمت TimeoutExpired timeout= argument) أو CalledProcessError (إذا فشلت وتمرير check=True ).

كما قد تستنتج من المثال أعلاه ، ستحصل على كل من stdout و stderr على الأنبوبين إلى stdout و stderr بشكل افتراضي.

يمكننا فحص الكائن المرتجع ورؤية الأمر الذي تم إعطاؤه ورمز العودة:

>>> completed_process.args
'python --version'
>>> completed_process.returncode
0

التقاط الإخراج

إذا كنت ترغب في التقاط الإخراج ، يمكنك تمرير subprocess.PIPE إلى stderr المناسبة أو stdout :

>>> cp = subprocess.run('python --version', 
                        stderr=subprocess.PIPE, 
                        stdout=subprocess.PIPE)
>>> cp.stderr
b'Python 3.6.1 :: Anaconda 4.4.0 (64-bit)\r\n'
>>> cp.stdout
b''

(أجد أنه من المثير للاهتمام ومن البديهي بعض الشيء أن يتم الحصول على معلومات الإصدار إلى stderr بدلاً من stdout.)

مرر قائمة الأوامر

قد ينتقل المرء بسهولة من تقديم سلسلة أوامر يدويًا (كما يقترح السؤال) لتوفير سلسلة مبنية برمجيا. لا تبني السلاسل برمجيًا. هذه مشكلة أمنية محتملة. من الأفضل أن تفترض أنك لا تثق في المدخلات.

>>> import textwrap
>>> args = ['python', textwrap.__file__]
>>> cp = subprocess.run(args, stdout=subprocess.PIPE)
>>> cp.stdout
b'Hello there.\r\n  This is indented.\r\n'

لاحظ ، يجب تمرير args فقط الموقف.

التوقيع الكامل

إليك التوقيع الفعلي في المصدر وكما هو موضح في help(run) :

def run(*popenargs, input=None, timeout=None, check=False, **kwargs):

يتم إعطاء popenargs و kwargs Popen . يمكن أن يكون input عبارة عن سلسلة من وحدات البايت (أو unicode ، في حالة تحديد الترميز أو universal_newlines=True ) التي سيتم توجيهها إلى stprocess stdin.

تصف الوثائق timeout= check=True أفضل من استطيع:

يتم تمرير الوسيطة timeout إلى Popen.communicate (). إذا انتهت المهلة ، سيتم قتل عملية الطفل وانتظر. سيتم re-raised الاستثناء TimeoutExpired بعد إنهاء عملية تابعة.

إذا كان التحقق صحيحًا ، وتخرج العملية باستخدام رمز إنهاء غير صفري ، فسيتم رفع استثناء CalledProcessError. تحتفظ سمات هذا الاستثناء بالوسائط وشفرة الإنهاء و stdout و stderr إذا تم التقاطها.

وهذا المثال check=True أفضل من واحد يمكنني أن أتطرق إليه:

>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

توقيع موسع

إليك توقيع موسع ، كما هو موضح في الوثائق:

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, 
shell=False, cwd=None, timeout=None, check=False, encoding=None, 
errors=None)

لاحظ أن هذا يشير إلى أنه يجب تمرير قائمة ال args فقط. لذا قم بتمرير الوسيطات المتبقية كوسائط كلمات رئيسية.

Popen

عند استخدام Popen بدلا من ذلك؟ أجد صعوبة في إيجاد حالة استخدام استنادًا إلى الحجج وحدها. ومع ذلك ، يمنحك الاستخدام المباشر لـ Popen إمكانية الوصول إلى أساليبه ، بما في ذلك poll ، و "send_signal" ، و "الإنهاء" ، و "الانتظار".

إليك توقيع Popen كما هو Popen في المصدر . أعتقد أن هذا هو التغليف الأكثر دقة للمعلومات (في مقابل help(Popen) ):

def __init__(self, args, bufsize=-1, executable=None,
             stdin=None, stdout=None, stderr=None,
             preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS,
             shell=False, cwd=None, env=None, universal_newlines=False,
             startupinfo=None, creationflags=0,
             restore_signals=True, start_new_session=False,
             pass_fds=(), *, encoding=None, errors=None):

لكن الأكثر Popen هي وثائق Popen :

subprocess.Popen(args, bufsize=-1, executable=None, stdin=None,
                 stdout=None, stderr=None, preexec_fn=None, close_fds=True,
                 shell=False, cwd=None, env=None, universal_newlines=False,
                 startupinfo=None, creationflags=0, restore_signals=True,
                 start_new_session=False, pass_fds=(), *, encoding=None, errors=None)

تنفيذ برنامج طفل في عملية جديدة. في POSIX ، تستخدم الفئة os.execvp () - سلوكًا شبيهًا لتنفيذ البرنامج الفرعي. في Windows ، يستخدم الفئة الدالة Windows CreateProcess (). الحجج ل Popen هي على النحو التالي.

سيتم ترك فهم الوثائق المتبقية في Popen للقارئ.






Related