การรันคำสั่งเชลล์และการดักจับเอาต์พุต


907

ฉันต้องการเขียนฟังก์ชั่นที่จะรันคำสั่งเชลล์และส่งคืนเอาต์พุตเป็นสตริงไม่ว่าจะเป็นข้อผิดพลาดหรือข้อความแจ้งความสำเร็จ ฉันแค่อยากได้ผลลัพธ์แบบเดียวกับที่ฉันได้รับจากบรรทัดคำสั่ง

อะไรคือตัวอย่างโค้ดที่จะทำสิ่งนั้น?

ตัวอย่างเช่น:

def run_command(cmd):
    # ??????

print run_command('mysqladmin create test -uroot -pmysqladmin12')
# Should output something like:
# mysqladmin: CREATE DATABASE failed; error: 'Can't create database 'test'; database exists'

2
เกี่ยวข้อง: stackoverflow.com/questions/2924310/…
jfs

คำตอบ:


1138

คำตอบสำหรับคำถามนี้ขึ้นอยู่กับเวอร์ชันของ Python ที่คุณใช้ วิธีที่ง่ายที่สุดคือการใช้subprocess.check_outputฟังก์ชั่น:

>>> subprocess.check_output(['ls', '-l'])
b'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

check_outputรันโปรแกรมเดียวที่รับอาร์กิวเมนต์เป็นอินพุตเท่านั้น 1stdoutก็จะส่งกลับผลตรงตามที่พิมพ์ไป หากคุณต้องการเขียนอินพุตให้stdinข้ามไปข้างหน้าrunหรือPopenส่วนต่างๆ หากคุณต้องการรันคำสั่งเชลล์ที่ซับซ้อนดูบันทึกย่อshell=Trueที่ท้ายคำตอบนี้

check_outputฟังก์ชั่นนี้ใช้งานได้กับ Python เกือบทุกรุ่นที่ยังใช้งานอยู่ (2.7+) 2แต่สำหรับเวอร์ชั่นล่าสุดมันไม่ได้เป็นแนวทางที่แนะนำอีกต่อไป

Python เวอร์ชันใหม่ (3.5 หรือสูงกว่า): run

หากคุณกำลังใช้งูหลาม 3.5หรือสูงกว่าและไม่จำเป็นต้องทำงานร่วมกันหลังที่ใหม่runฟังก์ชั่นขอแนะนำ มันให้ API ระดับสูงทั่วไปมากสำหรับsubprocessโมดูล ในการดักจับเอาต์พุตของโปรแกรมให้ส่งsubprocess.PIPEค่าสถานะไปยังstdoutอาร์กิวเมนต์คีย์เวิร์ด จากนั้นเข้าถึงstdoutแอตทริบิวต์ของCompletedProcessวัตถุที่ส่งคืน:

>>> import subprocess
>>> result = subprocess.run(['ls', '-l'], stdout=subprocess.PIPE)
>>> result.stdout
b'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

ค่าส่งคืนเป็นbytesวัตถุดังนั้นหากคุณต้องการสตริงที่เหมาะสมคุณจะต้องใช้decodeมัน สมมติว่ากระบวนการที่เรียกคืนสตริงที่เข้ารหัส UTF-8:

>>> result.stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

ทั้งหมดนี้สามารถบีบอัดให้เป็นหนึ่งซับ:

>>> subprocess.run(['ls', '-l'], stdout=subprocess.PIPE).stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

หากคุณต้องการส่งผ่านข้อมูลไปยังกระบวนการstdinให้ส่งbytesวัตถุไปยังinputอาร์กิวเมนต์คำหลัก:

>>> cmd = ['awk', 'length($0) > 5']
>>> input = 'foo\nfoofoo\n'.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=input)
>>> result.stdout.decode('utf-8')
'foofoo\n'

คุณสามารถดักจับข้อผิดพลาดโดยผ่านstderr=subprocess.PIPE(capture to result.stderr) หรือstderr=subprocess.STDOUT(capture ไปยังresult.stdoutพร้อมกับเอาต์พุตปกติ) เมื่อความปลอดภัยไม่ได้เป็นกังวลคุณยังสามารถเรียกใช้คำสั่งเชลล์ที่ซับซ้อนมากขึ้นโดยส่งผ่านshell=Trueตามที่อธิบายไว้ในหมายเหตุด้านล่าง

สิ่งนี้เพิ่มความซับซ้อนเพียงเล็กน้อยเมื่อเทียบกับวิธีการทำสิ่งเก่า แต่ฉันคิดว่ามันคุ้มค่ากับการจ่ายเงิน: ตอนนี้คุณสามารถทำอะไรก็ได้ที่คุณต้องทำกับrunฟังก์ชั่นเพียงอย่างเดียว

Python เวอร์ชันเก่ากว่า (2.7-3.4): check_output

หากคุณใช้ Python เวอร์ชั่นเก่ากว่าหรือต้องการความเข้ากันได้แบบย้อนหลังคุณอาจใช้check_outputฟังก์ชั่นตามที่อธิบายไว้ข้างต้นโดยย่อ สามารถใช้งานได้ตั้งแต่ Python 2.7

subprocess.check_output(*popenargs, **kwargs)  

มันจะใช้เวลาขัดแย้งกันเป็นPopen(ดูด้านล่าง) และส่งกลับสตริงที่มีผลลัพธ์ของโปรแกรม จุดเริ่มต้นของคำตอบนี้มีตัวอย่างการใช้ที่ละเอียดยิ่งขึ้น ใน Python 3.5 และสูงกว่าcheck_outputเทียบเท่ากับการดำเนินการrunกับcheck=Trueและstdout=PIPEและส่งคืนเฉพาะแอstdoutททริบิวต์

คุณสามารถส่งผ่านstderr=subprocess.STDOUTเพื่อให้มั่นใจว่าข้อความผิดพลาดที่จะถูกรวมในการส่งออกกลับ - แต่ในบางรุ่นของงูใหญ่ผ่านstderr=subprocess.PIPEไปcheck_outputอาจทำให้เกิดการติดตาย เมื่อความปลอดภัยไม่ได้เป็นกังวลคุณยังสามารถเรียกใช้คำสั่งเชลล์ที่ซับซ้อนมากขึ้นโดยส่งผ่านshell=Trueตามที่อธิบายไว้ในหมายเหตุด้านล่าง

หากคุณต้องการไพพ์จากstderrหรือส่งผ่านอินพุตไปยังกระบวนการcheck_outputจะไม่ขึ้นอยู่กับภารกิจ ดูPopenตัวอย่างด้านล่างในกรณีนั้น

แอปพลิเคชั่นที่ซับซ้อน & Python เวอร์ชั่นดั้งเดิม (2.6 และต่ำกว่า): Popen

หากคุณต้องการความเข้ากันได้อย่างลึกล้ำหรือหากคุณต้องการฟังก์ชั่นที่ซับซ้อนมากกว่าที่check_outputมีให้คุณจะต้องทำงานกับPopenวัตถุโดยตรงซึ่งจะห่อหุ้ม API ระดับต่ำสำหรับกระบวนการย่อย

คอนPopenสตรัคยอมรับทั้งคำสั่งเดียวโดยไม่มีข้อโต้แย้งหรือรายการที่มีคำสั่งเป็นรายการแรกตามด้วยจำนวนอาร์กิวเมนต์ใด ๆ แต่ละรายการเป็นรายการแยกต่างหากในรายการ shlex.splitสามารถช่วยวิเคราะห์สตริงในรายการที่จัดรูปแบบอย่างเหมาะสม Popenออบเจ็กต์ยังยอมรับโฮสต์ของอาร์กิวเมนต์ที่แตกต่างกันสำหรับการจัดการกระบวนการ IO และการกำหนดค่าระดับต่ำ

ในการส่งอินพุตและเอาต์พุตจับภาพcommunicateเป็นวิธีที่ต้องการเกือบทุกครั้ง ในขณะที่:

output = subprocess.Popen(["mycmd", "myarg"], 
                          stdout=subprocess.PIPE).communicate()[0]

หรือ

>>> import subprocess
>>> p = subprocess.Popen(['ls', '-a'], stdout=subprocess.PIPE, 
...                                    stderr=subprocess.PIPE)
>>> out, err = p.communicate()
>>> print out
.
..
foo

ถ้าคุณตั้งค่าstdin=PIPE, communicateนอกจากนี้ยังช่วยให้คุณสามารถส่งผ่านข้อมูลในการประมวลผลผ่านstdin:

>>> cmd = ['awk', 'length($0) > 5']
>>> p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
...                           stderr=subprocess.PIPE,
...                           stdin=subprocess.PIPE)
>>> out, err = p.communicate('foo\nfoofoo\n')
>>> print out
foofoo

หมายเหตุคำตอบแอรอนฮอลล์ซึ่งแสดงให้เห็นว่าในบางระบบคุณอาจจำเป็นต้องชุดstdout, stderrและstdinทุกคนที่จะPIPE(หรือDEVNULL) ที่จะได้รับcommunicateในการทำงานที่ทุกคน

ในบางกรณีที่หายากคุณอาจต้องใช้การจับภาพเอาต์พุตแบบเรียลไทม์ที่ซับซ้อน คำตอบของVartecแนะนำวิธีไปข้างหน้า แต่วิธีการอื่นนอกจากcommunicateมีแนวโน้มที่จะหยุดชะงักหากไม่ได้ใช้อย่างระมัดระวัง

shell=Trueเช่นเดียวกับทุกฟังก์ชั่นดังกล่าวข้างต้นเมื่อการรักษาความปลอดภัยไม่กังวลคุณสามารถเรียกใช้คำสั่งเชลล์ที่ซับซ้อนมากขึ้นโดยผ่าน

หมายเหตุ

1. การรันคำสั่งเชลล์: shell=Trueอาร์กิวเมนต์

โดยปกติแต่ละการเรียกไปrun, check_outputหรือPopenคอนสตรัรันโปรแกรมเดียว นั่นหมายความว่าไม่มีท่อทุบตีสไตล์แฟนซี หากคุณต้องการรันคำสั่งเชลล์ที่ซับซ้อนคุณสามารถส่งผ่านshell=Trueซึ่งทั้งสามฟังก์ชั่นสนับสนุน

แต่การทำเช่นนั้นทำให้เกิดความกังวลด้านความปลอดภัย หากคุณกำลังทำอะไรมากกว่าสคริปต์ไฟคุณอาจจะดีกว่าที่จะเรียกแต่ละกระบวนการแยกจากกันและส่งผ่านเอาต์พุตจากแต่ละวิธีเป็นอินพุตไปยังถัดไป

run(cmd, [stdout=etc...], input=other_output)

หรือ

Popen(cmd, [stdout=etc...]).communicate(other_output)

สิ่งล่อใจที่จะเชื่อมต่อท่อโดยตรงนั้นแข็งแกร่ง ต่อต้านมัน มิฉะนั้นคุณอาจจะเห็นการติดตายหรือมีการทำสิ่ง hacky เช่นนี้

2. ข้อควรพิจารณาของ Unicode

check_outputส่งคืนสตริงใน Python 2 แต่เป็นbytesวัตถุใน Python 3 มันควรใช้เวลาสักครู่เพื่อเรียนรู้เกี่ยวกับยูนิโค้ดถ้าคุณยังไม่ได้ทำ


5
ทั้งด้วยcheck_output()และcommunicate()คุณต้องรอจนกว่ากระบวนการจะเสร็จสิ้นโดยที่poll()คุณได้รับผลลัพธ์ตามที่มา ขึ้นอยู่กับสิ่งที่คุณต้องการจริงๆ
vartec

2
ไม่แน่ใจว่าสิ่งนี้ใช้ได้กับ Python รุ่นที่ใหม่กว่าเท่านั้น แต่ตัวแปรoutนั้นเป็นประเภท<class 'bytes'>สำหรับฉัน เพื่อให้ได้ผลลัพธ์เป็นสตริงฉันต้องถอดรหัสก่อนพิมพ์ดังนี้:out.decode("utf-8")
PolyMesh

1
@par มันไม่ได้ผลสำหรับคุณเมื่อคุณผ่านshell=True? มันใช้งานได้สำหรับฉัน คุณไม่จำเป็นต้องเมื่อคุณผ่านshlex.split ใช้สำหรับคำสั่งที่ไม่ใช่เชลล์ ฉันคิดว่าฉันจะเอามันออกไปเพราะสิ่งนี้ทำให้น้ำขุ่น shell=Trueshlex.split
senderle

2
Python 3.5+ อนุญาตให้มีการโต้แย้งคำหลักuniversal_newlines=Trueซึ่งช่วยให้คุณสามารถส่งและรับสตริง Unicode ในการเข้ารหัสเริ่มต้นของระบบ ใน 3.7 text=Trueนี้ถูกเปลี่ยนชื่อเป็นที่เหมาะสมมากขึ้น
tripleee

2
สำหรับ Python 3.6 ขึ้นไปคุณสามารถใช้encodingพารามิเตอร์ของsubprocess.runแทนการใช้คุณสามารถใช้result.stdout.decode('utf-8') subprocess.run(['ls', '-l'], stdout=subprocess.PIPE, encoding='utf-8')
Pierre

191

วิธีนี้ง่ายกว่า แต่ใช้ได้กับ Unix (รวมถึง Cygwin) และ Python2.7 เท่านั้น

import commands
print commands.getstatusoutput('wc -l file')

ส่งคืน tuple ด้วย (return_value, เอาต์พุต)

สำหรับวิธีแก้ปัญหาที่ใช้งานได้ทั้ง Python2 และ Python3 ให้ใช้subprocessโมดูลแทน:

from subprocess import Popen, PIPE
output = Popen(["date"],stdout=PIPE)
response = output.communicate()
print response

31
เลิกใช้แล้ว แต่มีประโยชน์มากสำหรับงูหลามรุ่นเก่าที่ไม่มี subprocess.check_output
static_rtti

22
โปรดทราบว่านี่เป็น Unix เฉพาะ ตัวอย่างเช่นจะล้มเหลวใน Windows
Zitrax

4
+1 ฉันต้องทำงานกับ python 2.4 รุ่นเก่าและนี่เป็นประโยชน์อย่างมาก
javadba

1
PIPE คืออะไรเพื่อนมาแสดงรหัสเต็ม: subprocess.PIPE
Kyle Bridenstine

@KyleBridenstine คุณสามารถแก้ไขคำตอบได้
บอริส

106

อะไรแบบนั้น:

def runProcess(exe):    
    p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    while(True):
        # returns None while subprocess is running
        retcode = p.poll() 
        line = p.stdout.readline()
        yield line
        if retcode is not None:
            break

โปรดทราบว่าฉันกำลังเปลี่ยนเส้นทาง stderr ไปยัง stdout อาจไม่ตรงกับที่คุณต้องการ แต่ฉันต้องการข้อความแสดงข้อผิดพลาดด้วย

ฟังก์ชั่นนี้ให้ผลทีละบรรทัดตามปกติ (โดยปกติคุณจะต้องรอให้กระบวนการย่อยเสร็จสิ้นเพื่อให้ได้ผลลัพธ์ทั้งหมด)

สำหรับกรณีของคุณการใช้จะเป็น:

for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
    print line,

ต้องแน่ใจว่าได้ติดตั้งแอคทีฟลูปบางประเภทเพื่อรับเอาต์พุตเพื่อหลีกเลี่ยงการหยุดชะงักwaitและcallฟังก์ชั่นที่อาจเกิดขึ้น
André Caron

@Silver Light: กระบวนการของคุณอาจรอการป้อนข้อมูลจากผู้ใช้ ลองระบุPIPEค่าสำหรับstdinและปิดไฟล์นั้นทันทีที่Popenส่งคืน
André Caron

4
-1: มันเป็นห่วงอนันต์ถ้ามีretcode 0การตรวจสอบควรเป็นif retcode is not Noneการตรวจสอบควรจะเป็นคุณไม่ควรผลผลิตสตริงว่าง (แม้แต่บรรทัดว่างอย่างน้อยหนึ่งสัญลักษณ์ '\ if line: yield linen'): โทรp.stdout.close()มาท้าย
jfs

2
ฉันลองใช้รหัสด้วย ls -l / dirname และแบ่งหลังจากที่แสดงรายการไฟล์สองไฟล์ในขณะที่มีไฟล์จำนวนมากในไดเรกทอรี
Vasilis

3
@fuenfundachtzig: .readlines()จะไม่ส่งคืนจนกว่าจะอ่านเอาต์พุตทั้งหมดและดังนั้นจึงแยกเป็นเอาต์พุตขนาดใหญ่ที่ไม่พอดีกับหน่วยความจำ นอกจากนี้เพื่อหลีกเลี่ยงข้อมูลบัฟเฟอร์ที่หายไปหลังจากออกจากกระบวนการย่อยควรมีแอนะล็อกของif retcode is not None: yield from p.stdout.readlines(); break
jfs

67

คำตอบของ Vartecไม่ได้อ่านทุกบรรทัดดังนั้นฉันจึงสร้างเวอร์ชันที่ทำขึ้น:

def run_command(command):
    p = subprocess.Popen(command,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT)
    return iter(p.stdout.readline, b'')

การใช้งานเหมือนกับคำตอบที่ยอมรับ:

command = 'mysqladmin create test -uroot -pmysqladmin12'.split()
for line in run_command(command):
    print(line)

6
คุณสามารถใช้return iter(p.stdout.readline, b'')แทนขณะลูป
jfs

2
นั่นเป็นการใช้งานที่ยอดเยี่ยมของ iter ไม่รู้เลย! ฉันอัปเดตรหัสแล้ว
Max Ekman

ฉันค่อนข้างมั่นใจว่า stdout เก็บเอาท์พุททั้งหมดมันเป็นวัตถุสตรีมที่มีบัฟเฟอร์ ฉันใช้เทคนิคที่คล้ายกันมากในการทำให้หมดสิ้นเอาต์พุตที่เหลือหลังจากที่ Popen เสร็จสมบูรณ์และในกรณีของฉันใช้ Poll () และ readline ระหว่างการดำเนินการเพื่อจับภาพเอาต์พุตสดเช่นกัน
Max Ekman

ฉันได้ลบความคิดเห็นที่ทำให้เข้าใจผิด ฉันสามารถยืนยันได้p.stdout.readline()อาจส่งคืนเอาต์พุตที่ไม่บัฟเฟอร์ก่อนหน้านี้ที่ไม่ว่างเปล่าแม้ว่ากระบวนการลูกได้ออกแล้ว ( p.poll()ไม่ใช่None)
jfs

รหัสนี้ใช้ไม่ได้ ดูที่นี่stackoverflow.com/questions/24340877/...
Thang

61

นี้เป็นเรื่องยุ่งยากแต่ง่ายสุดวิธีการแก้ปัญหาที่ทำงานในหลาย ๆ สถานการณ์:

import os
os.system('sample_cmd > tmp')
print open('tmp', 'r').read()

ไฟล์ชั่วคราว (นี่คือ tmp) สร้างขึ้นด้วยเอาต์พุตของคำสั่งและคุณสามารถอ่านจากเอาต์พุตที่คุณต้องการ

หมายเหตุเพิ่มเติมจากความคิดเห็น: คุณสามารถลบไฟล์ tmp ในกรณีของงานครั้งเดียว หากคุณจำเป็นต้องทำหลาย ๆ ครั้งไม่จำเป็นต้องลบ tmp

os.remove('tmp')

5
Hacky แต่ super simple + ทำงานได้ทุกที่ .. สามารถรวมเข้าด้วยกันmktempเพื่อให้มันทำงานในสถานการณ์แบบเธรดที่ฉันเดา
Prakash Rajagaopal

2
อาจเป็นวิธีที่เร็วที่สุด แต่ควรเพิ่มos.remove('tmp')เพื่อให้เป็น "fileless"
XuMuK

@XuMuK คุณถูกต้องในกรณีของงานครั้งเดียว หากเป็นงานซ้ำ ๆ อาจไม่จำเป็นต้องลบ
Mehdi Saman Booy

1
ไม่ดีสำหรับการทำงานพร้อมกัน, ไม่ดีสำหรับฟังก์ชั่น reentrant, ไม่ดีสำหรับการออกจากระบบเหมือนเดิมก่อนที่มันจะเริ่ม (ไม่มีการทำความสะอาด)
2mia

1
@ 2mia เห็นได้ชัดว่ามันเป็นเรื่องง่ายสำหรับเหตุผล! หากคุณต้องการใช้ไฟล์เป็นหน่วยความจำแบบแบ่งใช้สำหรับการอ่านและเขียนพร้อมกันนี่ไม่ใช่ตัวเลือกที่ดี แต่สำหรับ s.th เช่นมีเอาต์พุตของคำสั่ง (เช่น ls หรือ find หรือ ... ) มันสามารถเป็นตัวเลือกที่ดีและรวดเร็ว Btw ถ้าคุณต้องการวิธีแก้ปัญหาอย่างรวดเร็วสำหรับปัญหาง่าย ๆ มันดีที่สุดที่ฉันคิด หากคุณต้องการไพพ์ไลน์การประมวลผลย่อยจะทำงานได้อย่างมีประสิทธิภาพยิ่งขึ้น
Mehdi Saman Booy

44

ฉันมีปัญหาเดียวกัน แต่หาวิธีง่ายๆในการทำสิ่งนี้:

import subprocess
output = subprocess.getoutput("ls -l")
print(output)

หวังว่าจะช่วยออก

หมายเหตุ: วิธีนี้เป็น Python3 เฉพาะเนื่องจากsubprocess.getoutput()ไม่สามารถใช้งานได้ใน Python2


วิธีนี้แก้ปัญหาของ OP ได้อย่างไร กรุณาอธิบายอย่างละเอียด
RamenChef

4
มันจะส่งกลับผลลัพธ์ของคำสั่งเป็นสตริงง่ายเหมือนที่
azhar22k

1
แน่นอนพิมพ์เป็นคำสั่งใน Python 2 คุณควรจะเข้าใจว่านี่เป็นคำตอบของ Python 3

2
@Dev print เป็น python ที่ถูกต้อง 2 subprocess.getoutput ไม่ใช่
user48956

2
สำหรับกรณีการใช้งานส่วนใหญ่นี่คือสิ่งที่ผู้คนจะต้องการ: ง่ายต่อการจดจำไม่ต้องถอดรหัสผลลัพธ์ ฯลฯ ขอบคุณ
bwv549

18

คุณสามารถใช้คำสั่งต่อไปนี้เพื่อเรียกใช้คำสั่งเชลล์ใด ๆ ฉันใช้มันกับอูบุนตูแล้ว

import os
os.popen('your command here').read()

หมายเหตุ:สิ่งนี้เลิกใช้แล้วตั้งแต่ python 2.6 subprocess.Popenตอนนี้คุณต้องใช้ ด้านล่างเป็นตัวอย่าง

import subprocess

p = subprocess.Popen("Your command", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
print p.split("\n")

2
เลิกใช้แล้วตั้งแต่รุ่น 2.6 - docs.python.org/2/library/os.html#os.popen
Filippo Vitale

1
@FilippoVitale ขอบคุณ ฉันไม่ทราบว่ามันเลิกใช้แล้ว
มูฮัมหมัดฮัสซัน

1
ตามraspberrypi.stackexchange.com/questions/71547/... os.popen()จะเลิกในหลาม 2.6 แต่มันก็ไม่ได้เลิกใช้ในหลาม 3.x ตั้งแต่ใน 3.x subprocess.Popen()จะดำเนินการโดยใช้
JL

12

การสะสมไมล์ของคุณอาจแตกต่างกันฉันพยายามหมุน @ senderle ในโซลูชันของ Vartec ใน Windows บน Python 2.6.5 แต่ฉันพบข้อผิดพลาดและไม่มีวิธีแก้ไขปัญหาอื่น ๆ ข้อผิดพลาดของฉันคือ: WindowsError: [Error 6] The handle is invalid.

ฉันพบว่าฉันต้องกำหนด PIPE ให้กับหมายเลขอ้างอิงทุกตัวเพื่อให้ได้ผลลัพธ์ที่ฉันต้องการ - รายการต่อไปนี้ใช้ได้สำหรับฉัน

import subprocess

def run_command(cmd):
    """given shell command, returns communication tuple of stdout and stderr"""
    return subprocess.Popen(cmd, 
                            stdout=subprocess.PIPE, 
                            stderr=subprocess.PIPE, 
                            stdin=subprocess.PIPE).communicate()

และเรียกสิ่งนี้ ( [0]รับส่วนแรกของ tuple stdout):

run_command('tracert 11.1.0.1')[0]

หลังจากเรียนรู้เพิ่มเติมฉันเชื่อว่าฉันต้องการข้อโต้แย้งไปป์เหล่านี้เพราะฉันทำงานกับระบบที่กำหนดเองที่ใช้มือจับที่แตกต่างกันดังนั้นฉันจึงต้องควบคุม std ทั้งหมดโดยตรง

หากต้องการหยุดป๊อปอัปคอนโซล (กับ Windows) ให้ทำดังนี้

def run_command(cmd):
    """given shell command, returns communication tuple of stdout and stderr"""
    # instantiate a startupinfo obj:
    startupinfo = subprocess.STARTUPINFO()
    # set the use show window flag, might make conditional on being in Windows:
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    # pass as the startupinfo keyword argument:
    return subprocess.Popen(cmd,
                            stdout=subprocess.PIPE, 
                            stderr=subprocess.PIPE, 
                            stdin=subprocess.PIPE, 
                            startupinfo=startupinfo).communicate()

run_command('tracert 11.1.0.1')

1
น่าสนใจ - นี่ต้องเป็นสิ่งที่ Windows ฉันจะเพิ่มบันทึกย่อที่ชี้ไปในกรณีนี้ผู้คนจะได้รับข้อผิดพลาดที่คล้ายกัน
senderle

ใช้DEVNULLแทนของsubprocess.PIPEถ้าคุณไม่ได้เขียน / อ่านจากท่อมิฉะนั้นคุณอาจแขวนกระบวนการเด็ก
jfs

10

ฉันมีปัญหาที่แตกต่างเล็กน้อยของปัญหาเดียวกันกับข้อกำหนดต่อไปนี้:

  1. จับภาพและส่งคืนข้อความ STDOUT ตามที่สะสมในบัฟเฟอร์ STDOUT (เช่นในเรียลไทม์)
    • @vartec แก้ไข Pythonically นี้ด้วยการใช้เครื่องกำเนิดไฟฟ้าและ
      คำหลัก'ผลผลิต' ด้านบน
  2. พิมพ์บรรทัด STDOUT ทั้งหมด ( แม้ว่ากระบวนการจะออกก่อนที่บัฟเฟอร์ STDOUT จะสามารถอ่านได้อย่างเต็มที่ )
  3. อย่าเสีย CPU รอบการสำรวจกระบวนการที่ความถี่สูง
  4. ตรวจสอบรหัสส่งคืนของกระบวนการย่อย
  5. พิมพ์ STDERR (แยกจาก STDOUT) หากเราได้รับรหัสส่งคืนข้อผิดพลาดที่ไม่เป็นศูนย์

ฉันได้รวมและปรับคำตอบก่อนหน้านี้เพื่อให้ได้สิ่งต่อไปนี้:

import subprocess
from time import sleep

def run_command(command):
    p = subprocess.Popen(command,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         shell=True)
    # Read stdout from subprocess until the buffer is empty !
    for line in iter(p.stdout.readline, b''):
        if line: # Don't print blank lines
            yield line
    # This ensures the process has completed, AND sets the 'returncode' attr
    while p.poll() is None:                                                                                                                                        
        sleep(.1) #Don't waste CPU-cycles
    # Empty STDERR buffer
    err = p.stderr.read()
    if p.returncode != 0:
       # The run_command() function is responsible for logging STDERR 
       print("Error: " + str(err))

รหัสนี้จะถูกดำเนินการเช่นเดียวกับคำตอบก่อนหน้า:

for line in run_command(cmd):
    print(line)

1
คุณทราบหรือไม่ว่าการเพิ่มโหมดสลีป (.1) จะไม่สิ้นเปลืองรอบ CPU หรือไม่
Moataz Elmasry

2
หากเรายังคงโทรต่อp.poll()โดยไม่พักระหว่างการโทรเราจะเสียรอบ CPU โดยการเรียกใช้ฟังก์ชันนี้นับล้านครั้ง แต่เรา "เค้น" วนรอบของเราโดยบอกระบบปฏิบัติการว่าเราไม่จำเป็นต้องกังวลในอีก 1 / 10th วินาทีดังนั้นมันจึงสามารถทำงานอื่น ๆ ได้ (เป็นไปได้ว่า p.poll () นอนด้วยทำให้งบการนอนหลับของเราซ้ำซ้อน)
The Aelfinn

5

การแยกคำสั่งเริ่มต้นสำหรับผู้ที่subprocessอาจยุ่งยากและยุ่งยาก

ใช้shlex.split()เพื่อช่วยตัวเองออก

คำสั่งตัวอย่าง

git log -n 5 --since "5 years ago" --until "2 year ago"

รหัส

from subprocess import check_output
from shlex import split

res = check_output(split('git log -n 5 --since "5 years ago" --until "2 year ago"'))
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'

ไม่มีshlex.split()รหัสจะมีลักษณะดังนี้

res = check_output([
    'git', 
    'log', 
    '-n', 
    '5', 
    '--since', 
    '5 years ago', 
    '--until', 
    '2 year ago'
])
print(res)
>>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...'

1
shlex.split()คือความสะดวกโดยเฉพาะอย่างยิ่งถ้าคุณไม่ทราบว่าการอ้างถึงเชลล์ทำงานอย่างไร แต่การแปลงสตริงนี้ด้วยตนเองเป็นรายการ['git', 'log', '-n', '5', '--since', '5 years ago', '--until', '2 year ago']ไม่ยากเลยถ้าคุณเข้าใจการอ้างอิง
tripleee

4

หากคุณจำเป็นต้องเรียกใช้คำสั่ง shell ในหลาย ๆ ไฟล์นี่เป็นเคล็ดลับสำหรับฉัน

import os
import subprocess

# Define a function for running commands and capturing stdout line by line
# (Modified from Vartec's solution because it wasn't printing all lines)
def runProcess(exe):    
    p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    return iter(p.stdout.readline, b'')

# Get all filenames in working directory
for filename in os.listdir('./'):
    # This command will be run on each file
    cmd = 'nm ' + filename

    # Run the command and capture the output line by line.
    for line in runProcess(cmd.split()):
        # Eliminate leading and trailing whitespace
        line.strip()
        # Split the output 
        output = line.split()

        # Filter the output and print relevant lines
        if len(output) > 2:
            if ((output[2] == 'set_program_name')):
                print filename
                print line

แก้ไข: เพิ่งเห็นวิธีแก้ปัญหาของ Max Persson พร้อมคำแนะนำของ JF Sebastian ไปข้างหน้าและรวมที่


Popenยอมรับสตริง แต่จากนั้นคุณต้องshell=Trueหรือรายการของการขัดแย้งซึ่งในกรณีนี้คุณควรจะส่งผ่าน['nm', filename]แทนสตริง ดีกว่าเพราะเชลล์เพิ่มความซับซ้อนโดยไม่ต้องระบุค่าใด ๆ ที่นี่ การส่งผ่านสตริงโดยไม่shell=Trueเกิดขึ้นกับการทำงานบน Windows แต่นั่นอาจเปลี่ยนแปลงได้ใน Python เวอร์ชั่นถัดไป
tripleee

2

ตามที่ @senderle หากคุณใช้ python3.6 เช่นฉัน:

def sh(cmd, input=""):
    rst = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, input=input.encode("utf-8"))
    assert rst.returncode == 0, rst.stderr.decode("utf-8")
    return rst.stdout.decode("utf-8")
sh("ls -a")

จะทำหน้าที่เหมือนกับที่คุณเรียกใช้คำสั่งใน bash


คุณกำลังสร้างข้อโต้แย้งคำหลักcheck=True, universal_newlines=Trueใหม่ กล่าวอีกนัยหนึ่งsubprocess.run()ก็ทำทุกอย่างที่รหัสของคุณทำแล้ว
tripleee

1

หากคุณใช้subprocessโมดูลหลามคุณจะสามารถจัดการกับ STDOUT, STDERR และส่งคืนรหัสคำสั่งแยกจากกัน คุณสามารถดูตัวอย่างสำหรับการใช้งานคำสั่งที่สมบูรณ์ แน่นอนคุณสามารถขยายด้วยtry..exceptถ้าคุณต้องการ

ฟังก์ชั่นด้านล่างส่งคืน STDOUT, STDERR และโค้ดส่งคืนเพื่อให้คุณสามารถจัดการได้ในสคริปต์อื่น

import subprocess

def command_caller(command=None)
    sp = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=False)
    out, err = sp.communicate()
    if sp.returncode:
        print(
            "Return code: %(ret_code)s Error message: %(err_msg)s"
            % {"ret_code": sp.returncode, "err_msg": err}
            )
    return sp.returncode, out, err

อีก reimplementation subprocess.run()ที่ดีของ อย่าประดิษฐ์ล้อใหม่
tripleee

0

เช่นดำเนินการ ('ls -ahl') สร้างผลตอบแทนที่เป็นไปได้สามถึงสี่และแพลตฟอร์มระบบปฏิบัติการ:

  1. ไม่มีเอาต์พุต แต่รันสำเร็จ
  2. บรรทัดที่ว่างเปล่าเอาท์พุททำงานได้สำเร็จ
  3. การเรียกใช้ล้มเหลว
  4. ส่งออกบางสิ่งบางอย่างทำงานได้สำเร็จ

ฟังก์ชั่นด้านล่าง

def execute(cmd, output=True, DEBUG_MODE=False):
"""Executes a bash command.
(cmd, output=True)
output: whether print shell output to screen, only affects screen display, does not affect returned values
return: ...regardless of output=True/False...
        returns shell output as a list with each elment is a line of string (whitespace stripped both sides) from output
        could be 
        [], ie, len()=0 --> no output;    
        [''] --> output empty line;     
        None --> error occured, see below

        if error ocurs, returns None (ie, is None), print out the error message to screen
"""
if not DEBUG_MODE:
    print "Command: " + cmd

    # https://stackoverflow.com/a/40139101/2292993
    def _execute_cmd(cmd):
        if os.name == 'nt' or platform.system() == 'Windows':
            # set stdin, out, err all to PIPE to get results (other than None) after run the Popen() instance
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        else:
            # Use bash; the default is sh
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable="/bin/bash")

        # the Popen() instance starts running once instantiated (??)
        # additionally, communicate(), or poll() and wait process to terminate
        # communicate() accepts optional input as stdin to the pipe (requires setting stdin=subprocess.PIPE above), return out, err as tuple
        # if communicate(), the results are buffered in memory

        # Read stdout from subprocess until the buffer is empty !
        # if error occurs, the stdout is '', which means the below loop is essentially skipped
        # A prefix of 'b' or 'B' is ignored in Python 2; 
        # it indicates that the literal should become a bytes literal in Python 3 
        # (e.g. when code is automatically converted with 2to3).
        # return iter(p.stdout.readline, b'')
        for line in iter(p.stdout.readline, b''):
            # # Windows has \r\n, Unix has \n, Old mac has \r
            # if line not in ['','\n','\r','\r\n']: # Don't print blank lines
                yield line
        while p.poll() is None:                                                                                                                                        
            sleep(.1) #Don't waste CPU-cycles
        # Empty STDERR buffer
        err = p.stderr.read()
        if p.returncode != 0:
            # responsible for logging STDERR 
            print("Error: " + str(err))
            yield None

    out = []
    for line in _execute_cmd(cmd):
        # error did not occur earlier
        if line is not None:
            # trailing comma to avoid a newline (by print itself) being printed
            if output: print line,
            out.append(line.strip())
        else:
            # error occured earlier
            out = None
    return out
else:
    print "Simulation! The command is " + cmd
    print ""

0

เอาต์พุตสามารถเปลี่ยนเส้นทางไปยังไฟล์ข้อความแล้วอ่านกลับ

import subprocess
import os
import tempfile

def execute_to_file(command):
    """
    This function execute the command
    and pass its output to a tempfile then read it back
    It is usefull for process that deploy child process
    """
    temp_file = tempfile.NamedTemporaryFile(delete=False)
    temp_file.close()
    path = temp_file.name
    command = command + " > " + path
    proc = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    if proc.stderr:
        # if command failed return
        os.unlink(path)
        return
    with open(path, 'r') as f:
        data = f.read()
    os.unlink(path)
    return data

if __name__ == "__main__":
    path = "Somepath"
    command = 'ecls.exe /files ' + path
    print(execute(command))

แน่ใจว่ามันสามารถจะทำได้แต่ทำไมคุณต้องการ; และทำไมคุณถึงใช้เชลล์แทนการผ่านstdout=temp_file?
tripleee

ที่จริงแล้วโดยทั่วไปแล้วคุณพูดถูก แต่ในตัวอย่างของฉันecls.exeดูเหมือนจะปรับใช้เครื่องมือบรรทัดคำสั่งอื่นดังนั้นบางครั้งวิธีที่ง่ายไม่ทำงาน
MR

0

เพิ่งเขียนสคริปต์ทุบตีขนาดเล็กเพื่อทำสิ่งนี้โดยใช้ curl

https://gist.github.com/harish2704/bfb8abece94893c53ce344548ead8ba5

#!/usr/bin/env bash

# Usage: gdrive_dl.sh <url>

urlBase='https://drive.google.com'
fCookie=tmpcookies

curl="curl -L -b $fCookie -c $fCookie"
confirm(){
    $curl "$1" | grep jfk-button-action | sed -e 's/.*jfk-button-action" href="\(\S*\)".*/\1/' -e 's/\&amp;/\&/g'
}

$curl -O -J "${urlBase}$(confirm $1)"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.