เหตุใดจึงต้องใช้วิธีการโมดูลระบบปฏิบัติการของ Python แทนที่จะเรียกใช้คำสั่งเชลล์โดยตรง


157

ฉันพยายามเข้าใจว่าอะไรคือแรงจูงใจเบื้องหลังการใช้ฟังก์ชั่นไลบรารี่ของ Python สำหรับการทำงานเฉพาะระบบปฏิบัติการเช่นการสร้างไฟล์ / ไดเรกทอรีการเปลี่ยนคุณสมบัติของไฟล์ ฯลฯ แทนที่จะเพียงแค่เรียกใช้คำสั่งเหล่านั้นผ่านทางos.system()หรือsubprocess.call()?

ยกตัวอย่างเช่นทำไมฉันต้องการที่จะใช้os.chmodแทนการทำos.system("chmod...")?

ฉันเข้าใจว่ามันเป็น "pythonic" มากกว่าที่จะใช้วิธีการไลบรารีที่มีอยู่ของ Python ให้มากที่สุดแทนที่จะเป็นเพียงการดำเนินการคำสั่งเชลล์โดยตรง แต่มีแรงจูงใจอื่น ๆ ที่อยู่เบื้องหลังการทำเช่นนี้จากมุมมองการทำงานหรือไม่?

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


6
โดยพื้นฐานแล้วคุณใช้ตะปูบนหัว ภารกิจระดับ OS ที่คุณอ้างถึงนั้นเป็นเรื่องธรรมดามากพอที่พวกเขาจะรับประกันการทำงานของตัวเองแทนที่จะถูกผลักไสให้ถูกเรียกผ่าน os.system
deweyredman

7
BTW คุณไม่พยายามที่จะเวลาดำเนินการเวลา - os.chmodกับos.system ( "chmod ...") ฉันเดาว่ามันจะเป็นส่วนหนึ่งของคำถามของคุณ
ภูเขาไฟ

61
ทำไมprintเมื่อคุณสามารถos.system("echo Hello world!")?
253751

25
ด้วยเหตุผลเดียวกันคุณควรใช้os.pathเพื่อจัดการเส้นทางแทนที่จะจัดการด้วยตนเอง: มันทำงานได้กับทุก OS ที่มันทำงาน
บากูริว

51
"การเรียกใช้งานคำสั่งเชลล์โดยตรง" จริง ๆ แล้วจะลดลงโดยตรง เชลล์ไม่ใช่อินเตอร์เฟสระดับต่ำกับระบบและos.chmodจะไม่เรียกchmodโปรแกรมที่เชลล์ใช้ การใช้การos.system('chmod ...')เรียกใช้เชลล์เพื่อตีความสตริงเพื่อเรียกใช้ไฟล์ปฏิบัติการอื่นเพื่อโทรไปยังchmodฟังก์ชันC ในขณะที่os.chmod(...)ไปยัง C chmodโดยตรง
user2357112 รองรับ Monica

คำตอบ:


325
  1. มันเร็วขึ้น , os.systemและsubprocess.callสร้างกระบวนการใหม่ซึ่งเป็นที่ไม่จำเป็นสำหรับสิ่งง่ายๆเหล่านี้ ในความเป็นจริงos.systemและsubprocess.callโดยshellปกติแล้วอาร์กิวเมนต์จะสร้างกระบวนการใหม่อย่างน้อยสองกระบวนการ: กระบวนการแรกเป็นเชลล์และกระบวนการที่สองเป็นคำสั่งที่คุณใช้งานอยู่ (หากไม่ใช่เชลล์ในตัวtest)

  2. คำสั่งบางอย่างจะไร้ประโยชน์ในกระบวนการแยก ตัวอย่างเช่นหากคุณเรียกใช้os.spawn("cd dir/")มันจะเปลี่ยนไดเรกทอรีการทำงานปัจจุบันของกระบวนการลูก แต่ไม่ใช่กระบวนการ Python คุณต้องใช้os.chdirเพื่อสิ่งนั้น

  3. คุณไม่ต้องกังวลเกี่ยวกับอักขระพิเศษที่ตีความโดยเชลล์ os.chmod(path, mode)จะทำงานว่าสิ่งที่ชื่อไฟล์ไม่มีในขณะที่จะล้มเหลวอย่างน่ากลัวถ้าชื่อไฟล์เป็นสิ่งที่ต้องการos.spawn("chmod 777 " + path) ; rm -rf ~(โปรดทราบว่าคุณสามารถแก้ไขได้หากคุณใช้subprocess.callโดยไม่มีshellอาร์กิวเมนต์)

  4. คุณไม่ต้องกังวลเกี่ยวกับชื่อไฟล์ที่ขึ้นต้นด้วยเส้นประ os.chmod("--quiet", mode)จะเปลี่ยนการอนุญาตของไฟล์ที่ตั้งชื่อ--quietแต่os.spawn("chmod 777 --quiet")จะล้มเหลวตามที่--quietตีความว่าเป็นข้อโต้แย้ง subprocess.call(["chmod", "777", "--quiet"])นี่คือความจริงแม้สำหรับ

  5. คุณมีความกังวลข้ามแพลตฟอร์มและ cross-shell น้อยลงเนื่องจากไลบรารีมาตรฐานของ Python ควรจะจัดการกับสิ่งนั้นให้คุณ ระบบของคุณมีchmodคำสั่งหรือไม่? ติดตั้งแล้วหรือยัง มันรองรับพารามิเตอร์ที่คุณคาดหวังว่าจะสนับสนุนหรือไม่ osโมดูลจะพยายามที่จะเป็นข้ามแพลตฟอร์มที่เป็นไปได้และเอกสารเมื่อว่ามันเป็นไปไม่ได้

  6. หากคำสั่งที่คุณใช้อยู่มีเอาต์พุตที่คุณสนใจคุณจะต้องวิเคราะห์คำสั่งซึ่งมีความซับซ้อนกว่าเสียงดังเพราะคุณอาจลืมมุมตัวพิมพ์เล็ก (ชื่อไฟล์ที่มีช่องว่างแท็บและบรรทัดใหม่ในคำเหล่านั้น) แม้ว่าคุณจะ ไม่สนใจเกี่ยวกับการพกพา


38
ในการเพิ่มจุด "ข้ามแพลตฟอร์ม" การแสดงรายการไดเรกทอรีคือ "ls" บน linux, "dir" บน windows การรับเนื้อหาของไดเรกทอรีนั้นเป็นงานที่มีระดับต่ำมาก
Cort Ammon

1
@CortAmmon: "ระดับต่ำ" เป็นญาติlsหรือdirเป็นระดับสูงสวยบางประเภทของนักพัฒนาเช่นเดียวกับที่bashหรือcmdหรือkshหรือสิ่งเปลือกคุณต้องการเป็น
Sebastian Mach

1
@phresnel: ฉันไม่เคยคิดแบบนั้น สำหรับฉัน "การเรียกโดยตรงไปยัง API ของเคอร์เนลระบบปฏิบัติการของคุณ" อยู่ในระดับต่ำมาก ฉันสมมติว่ามีมุมมองที่แตกต่างกันเกี่ยวกับเรื่องนี้ซึ่งทำให้ฉันพ้นเพราะฉัน (โดยธรรมชาติ) เข้าหามันด้วยอคติของฉันเอง
Cort Ammon

5
@CortAmmon: ถูกต้องและlsอยู่ในระดับที่สูงกว่านั้นเนื่องจากไม่ใช่การเรียกโดยตรงไปยังเคอร์เนล API ของระบบปฏิบัติการ มันเป็นแอปพลิเคชั่น (เล็ก)
Steve Jessop

1
@SteveJessop ฉันเรียกว่า "การได้รับเนื้อหาของไดเรกทอรี" ในระดับต่ำ ฉันไม่ได้คิดlsหรือdirแต่opendir()/readdir()(Linux API) หรือFindFirstFile()/FindNextFile()(Windows API) หรือFile.listFiles(Java API) หรือDirectory.GetFiles()(C #) สิ่งเหล่านี้เชื่อมโยงอย่างใกล้ชิดกับการโทรโดยตรงไปยังระบบปฏิบัติการ บางคนอาจทำได้ง่ายเพียงกดหมายเลขลงในการลงทะเบียนและการโทรint 13hเพื่อเปิดใช้งานโหมดเคอร์เนล
Cort Ammon

133

มันปลอดภัยกว่า เพื่อให้แนวคิดแก่คุณที่นี่เป็นสคริปต์ตัวอย่าง

import os
file = raw_input("Please enter a file: ")
os.system("chmod 777 " + file)

หากอินพุตจากผู้ใช้เป็นtest; rm -rf ~สิ่งนี้จะเป็นการลบโฮมไดเร็กทอรี

นี่คือเหตุผลว่าทำไมจึงปลอดภัยกว่าที่จะใช้ฟังก์ชันในตัว

ดังนั้นทำไมคุณควรใช้กระบวนการย่อยแทนระบบด้วย


26
หรือวิธีอื่นในการดูสิ่งที่ถูกต้องง่ายกว่าการเขียนโปรแกรม Python หรือเขียนโปรแกรม Python ที่เขียนเชลล์สคริปต์ :-)
Steve Jessop

3
@SteveJessop เพื่อนร่วมงานของฉันประหลาดใจที่สคริปต์ Python ขนาดเล็กที่ฉันช่วยให้เขาเขียนทำงานได้เร็วขึ้น 20 เท่า (!) สคริปต์ tan shell เร็วขึ้น ฉันอธิบายว่าการเปลี่ยนเส้นทางผลลัพธ์อาจดูเซ็กซี่ - แต่มันเป็นการเปิดและปิดไฟล์ในการทำซ้ำแต่ละครั้ง แต่บางคนชอบทำสิ่งที่ยาก - :)
ภูเขาไฟ

1
@SteveJessop นี่เป็นคำถามที่หลอกลวง - คุณจะไม่รู้จนกว่ารันไทม์! :)

60

มีสี่กรณีที่คาดเดายากสำหรับการเลือกวิธีเฉพาะเจาะจงของ Python ในosโมดูลมากกว่าการใช้os.systemหรือsubprocessโมดูลเมื่อเรียกใช้คำสั่ง:

  • ความซ้ำซ้อน - การวางไข่กระบวนการอื่นซ้ำซ้อนและเสียเวลาและทรัพยากร
  • ความสามารถในการพกพา - วิธีการหลายอย่างในosโมดูลนั้นมีอยู่ในหลายแพลตฟอร์มในขณะที่คำสั่งเชลล์จำนวนมากเป็นระบบปฏิบัติการเฉพาะ
  • ทำความเข้าใจเกี่ยวกับผล - วางไข่กระบวนการในการดำเนินการโดยพลกองกำลังคำสั่งคุณจะแยกเป็นผลมาจากการส่งออกและเข้าใจถ้าและทำไมคำสั่งมีบางอย่างผิดปกติทำ
  • ความปลอดภัย - กระบวนการสามารถประมวลผลคำสั่งใด ๆ นี่คือการออกแบบที่อ่อนแอและสามารถหลีกเลี่ยงได้โดยใช้วิธีการเฉพาะในosโมดูล

ความซ้ำซ้อน (ดูรหัสที่ซ้ำซ้อน ):

คุณกำลังดำเนินการ "คนกลาง" ที่ซ้ำซ้อนระหว่างทางไปยังการเรียกระบบในที่สุด ( chmodในตัวอย่างของคุณ) ชายกลางคนนี้เป็นกระบวนการใหม่หรือ sub-shell

จากos.system:

ดำเนินการคำสั่ง (สตริง) ใน subshell ...

และsubprocessเป็นเพียงโมดูลสำหรับวางไข่กระบวนการใหม่

คุณสามารถทำสิ่งที่คุณต้องการโดยไม่ต้องวางไข่กระบวนการเหล่านี้

ความเบา (ดูที่การพกพาซอร์สโค้ด ):

osจุดมุ่งหมายของโมดูลคือการให้บริการระบบปฏิบัติการทั่วไปและมันเป็นคำอธิบายเริ่มต้นด้วย:

โมดูลนี้มีวิธีพกพาในการใช้ฟังก์ชั่นการทำงานของระบบ

คุณสามารถใช้งานได้os.listdirทั้งบน windows และ unix การพยายามใช้os.system/ subprocessสำหรับฟังก์ชันนี้จะบังคับให้คุณรักษาการโทรสองสาย (สำหรับls/ dir) และตรวจสอบว่าคุณใช้ระบบปฏิบัติการใดอยู่ สิ่งนี้ไม่ใช่แบบพกพาและจะทำให้เกิดความยุ่งยากมากขึ้นในภายหลัง (ดูการจัดการเอาต์พุต )

การทำความเข้าใจผลลัพธ์ของคำสั่ง:

สมมติว่าคุณต้องการแสดงรายการไฟล์ในไดเรกทอรี

หากคุณใช้os.system("ls")/ subprocess.call(['ls'])คุณจะได้ผลลัพธ์ของกระบวนการกลับคืนมาซึ่งโดยทั่วไปจะเป็นสตริงขนาดใหญ่ที่มีชื่อไฟล์

คุณจะบอกไฟล์ที่มีช่องว่างในชื่อนั้นจากสองไฟล์ได้อย่างไร?

จะทำอย่างไรถ้าคุณไม่ได้รับอนุญาตให้แสดงรายการไฟล์

คุณควรแมปข้อมูลกับวัตถุหลามอย่างไร

นี่เป็นเพียงส่วนหัวของฉันเท่านั้นและในขณะที่มีวิธีแก้ปัญหาเหล่านี้ - ทำไมต้องแก้ปัญหาอีกครั้งสำหรับคุณ

นี่คือตัวอย่างของการทำตามหลักการอย่าทำซ้ำตัวเอง (มักอ้างว่าเป็น "DRY") โดยไม่ทำซ้ำการใช้งานที่มีอยู่แล้วและพร้อมให้คุณใช้งานได้ฟรี

ความปลอดภัย:

os.systemและsubprocessมีพลัง มันดีเมื่อคุณต้องการพลังนี้ แต่มันอันตรายเมื่อคุณไม่ต้องการ เมื่อคุณใช้งานos.listdirคุณจะรู้ว่าไม่สามารถทำสิ่งอื่นใดแล้วแสดงรายการไฟล์หรือทำให้เกิดข้อผิดพลาด เมื่อคุณใช้os.systemหรือsubprocessเพื่อให้บรรลุพฤติกรรมเดียวกันคุณสามารถลงเอยทำสิ่งที่คุณไม่ได้ตั้งใจทำ

ความปลอดภัยในการฉีด (ดูตัวอย่างการฉีดเปลือก ) :

หากคุณใช้อินพุตจากผู้ใช้เป็นคำสั่งใหม่คุณได้ให้เชลล์แก่เขา นี่เหมือนกับการฉีด SQL โดยให้เชลล์ในฐานข้อมูลสำหรับผู้ใช้

ตัวอย่างจะเป็นคำสั่งของแบบฟอร์ม:

# ... read some user input
os.system(user_input + " some continutation")

นี้สามารถใช้ประโยชน์ได้อย่างง่ายดายเพื่อให้ทำงานใด ๆรหัสที่กำหนดเองโดยใช้การป้อนข้อมูล: NASTY COMMAND;#การสร้างที่สุด:

os.system("NASTY COMMAND; # some continuation")

มีคำสั่งมากมายที่สามารถทำให้ระบบของคุณมีความเสี่ยง


3
ฉันจะบอกว่า 2. เป็นเหตุผลหลัก
jaredad7

23

ด้วยเหตุผลง่ายๆ - เมื่อคุณเรียกใช้ฟังก์ชันเชลล์มันจะสร้าง sub-shell ซึ่งถูกทำลายหลังจากคำสั่งของคุณมีอยู่ดังนั้นหากคุณเปลี่ยนไดเรกทอรีในเชลล์ - จะไม่ส่งผลกระทบต่อสภาพแวดล้อมของคุณใน Python

นอกจากนี้การสร้าง sub-shell นั้นใช้เวลานานดังนั้นการใช้คำสั่ง OS โดยตรงจะส่งผลต่อประสิทธิภาพของคุณ

แก้ไข

ฉันมีการทดสอบเวลาทำงาน:

In [379]: %timeit os.chmod('Documents/recipes.txt', 0755)
10000 loops, best of 3: 215 us per loop

In [380]: %timeit os.system('chmod 0755 Documents/recipes.txt')
100 loops, best of 3: 2.47 ms per loop

In [382]: %timeit call(['chmod', '0755', 'Documents/recipes.txt'])
100 loops, best of 3: 2.93 ms per loop

ฟังก์ชั่นภายในทำงานได้เร็วกว่า 10 เท่า

EDIT2

อาจมีบางกรณีที่เรียกใช้งานปฏิบัติการภายนอกอาจให้ผลลัพธ์ที่ดีกว่าแพ็คเกจ Python - ฉันเพิ่งจำอีเมลที่ส่งโดยเพื่อนร่วมงานของฉันว่าประสิทธิภาพของgzip ที่เรียกผ่าน subprocess นั้นสูงกว่าประสิทธิภาพของแพ็คเกจ Python ที่เขาใช้ แต่ไม่แน่นอนเมื่อเรากำลังพูดถึงแพ็คเกจ OS มาตรฐานที่จำลองคำสั่ง OS มาตรฐาน


มีโอกาสใดบ้างที่เกิดขึ้นกับ iPython ไม่คิดว่าคุณสามารถใช้ฟังก์ชั่นพิเศษที่เริ่มต้นด้วยการ%ใช้ล่ามปกติ
iProgram

@aPyDeveloper ใช่แล้วมันคือ iPython - บน Ubuntu "Magical" % timeitเป็นคำอวยพร - แม้ว่าจะมีบางกรณี - ส่วนใหญ่มีการจัดรูปแบบสตริง - ซึ่งไม่สามารถดำเนินการได้
ภูเขาไฟ

1
หรือคุณสามารถสร้างสคริปต์ไพ ธ อนจากนั้นพิมพ์time <path to script> เทอร์มินัลและมันจะบอกคุณจริงเวลาผู้ใช้และกระบวนการ นั่นคือถ้าคุณไม่มี iPython และคุณสามารถเข้าถึงบรรทัดคำสั่ง Unix ได้
iProgram

1
@ aPyDeveloper ฉันไม่เห็นเหตุผลที่จะทำงานหนัก - เมื่อฉันมี iPython บนเครื่องของฉัน
ภูเขาไฟ

ความจริง! ฉันพูดถ้าคุณไม่มี iPython :)
iProgram

16

การเรียก Shell เป็นระบบปฏิบัติการที่เฉพาะเจาะจงในขณะที่ฟังก์ชั่นโมดูล Python os ไม่ได้เป็นเช่นนั้นในกรณีส่วนใหญ่ และหลีกเลี่ยงการวางไข่ในกระบวนการย่อย


1
ฟังก์ชั่นโมดูล Python ยังวางไข่กระบวนการย่อยใหม่เพื่อเรียกใช้ subshell ใหม่
Koderok

7
@ Koderok เรื่องไร้สาระฟังก์ชั่นโมดูลที่เรียกว่าในกระบวนการ
dwurf

3
@Koderok: โมดูลระบบปฏิบัติการใช้การเรียกระบบพื้นฐานที่คำสั่งเชลล์ใช้ แต่ไม่ได้ใช้คำสั่งเชลล์ ซึ่งหมายความว่าการเรียกระบบ OS มักจะปลอดภัยและเร็วกว่า (ไม่มีการแยกสตริง, boo fork, exec ไม่ใช่ แต่เป็นการเรียกเคอร์เนลเท่านั้น) กว่าคำสั่งเชลล์ โปรดทราบว่าในกรณีส่วนใหญ่การเรียกเชลล์และการเรียกของระบบมักจะมีชื่อที่คล้ายกันหรือเหมือนกัน แต่มีเอกสารแยกต่างหาก การเรียก shell อยู่ใน man section 1 (ส่วน man ดีฟอลต์) ในขณะที่ call system ที่มีชื่อเทียบเท่านั้นอยู่ใน man section 2 (เช่น man 2 chmod)
Lie Ryan

1
@ dwurf, LieRyan: แย่จัง! ฉันมีความคิดที่ผิดดูเหมือนว่า ขอบคุณ!
Koderok

11

มันมีประสิทธิภาพมากกว่า "เชลล์" เป็นเพียงไบนารีระบบปฏิบัติการอื่นซึ่งมีการเรียกระบบจำนวนมาก เหตุใดจึงต้องมีค่าใช้จ่ายในการสร้างกระบวนการเชลล์ทั้งหมดสำหรับการเรียกระบบเพียงครั้งเดียว

สถานการณ์ยิ่งแย่ลงเมื่อคุณใช้os.systemบางอย่างที่ไม่ใช่กระสุนในตัว คุณเริ่มต้นกระบวนการเชลล์ซึ่งจะเริ่มต้นปฏิบัติการซึ่งหลังจากนั้น (สองกระบวนการออกไป) ทำให้การเรียกระบบ อย่างน้อยsubprocessก็จะลบความจำเป็นในกระบวนการ shell ตัวกลาง

มันไม่ได้เฉพาะเจาะจงกับงูหลาม systemdเป็นการปรับปรุงเวลาเริ่มทำงานของ Linux ด้วยเหตุผลเดียวกัน: ทำให้ระบบจำเป็นต้องเรียกตัวเองแทนที่จะวางไข่พันเชลล์

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