Warum sollten Sie die OS-Modulmethoden von Python verwenden, anstatt Shell-Befehle direkt auszuführen?

157

Ich versuche zu verstehen, was die Motivation für die Verwendung der Bibliotheksfunktionen von Python ist, um betriebssystemspezifische Aufgaben wie das Erstellen von Dateien / Verzeichnissen, das Ändern von Dateiattributen usw. auszuführen, anstatt diese Befehle nur über os.system()oder auszuführen subprocess.call().

Warum sollte ich zum Beispiel verwenden, os.chmodanstatt zu tun os.system("chmod...")?

Ich verstehe, dass es "pythonischer" ist, die verfügbaren Bibliotheksmethoden von Python so oft wie möglich zu verwenden, anstatt nur Shell-Befehle direkt auszuführen. Aber gibt es eine andere Motivation, dies unter funktionalen Gesichtspunkten zu tun?

Ich spreche hier nur von der Ausführung einfacher einzeiliger Shell-Befehle. Wenn wir mehr Kontrolle über die Ausführung der Aufgabe benötigen, ist die Verwendung von subprocessModulen meines Erachtens beispielsweise sinnvoller.

Koderok
quelle
6
Sie haben im Grunde den Nagel auf den Kopf getroffen. Die Aufgaben auf Betriebssystemebene, auf die Sie sich beziehen, sind häufig genug, um ihre eigene Funktion zu gewährleisten, anstatt nur über os.system aufgerufen zu werden.
Deweyredman
7
Übrigens, haben Sie versucht, die Ausführungszeit zeitlich festzulegen - os.chmod vs. os.system ("chmod ...") . Ich würde eine Vermutung wagen, dass es einen Teil Ihrer Frage beantworten wird.
Vulkan
61
Warum haben, printwenn Sie könnten os.system("echo Hello world!")?
user253751
25
Aus dem gleichen Grund sollten Sie os.pathPfade verwenden, anstatt sie manuell zu behandeln: Es funktioniert auf jedem Betriebssystem, auf dem es ausgeführt wird.
Bakuriu
51
"Shell-Befehle direkt ausführen" ist eigentlich weniger direkt. Die Shell ist keine einfache Schnittstelle zum System und os.chmodwird das chmodProgramm, das die Shell ausführen würde, nicht aufrufen . Durch os.system('chmod ...')Starten einer Shell wird eine Zeichenfolge interpretiert, um eine andere ausführbare Datei aufzurufen, um die C- chmodFunktion aufzurufen , während die C- Funktion os.chmod(...)viel direkter aufgerufen wird chmod.
user2357112 unterstützt Monica

Antworten:

325
  1. Es ist schneller , os.systemund subprocess.callneue Prozesse zu schaffen , die für etwas so einfach nicht notwendig ist. In der Tat os.systemund subprocess.callmit dem shellArgument erstellen Sie normalerweise mindestens zwei neue Prozesse: Der erste ist die Shell und der zweite ist der Befehl, den Sie ausführen (wenn es sich nicht um eine integrierte Shell handelt test).

  2. Einige Befehle sind in einem separaten Prozess unbrauchbar . Wenn Sie beispielsweise ausführen os.spawn("cd dir/"), wird das aktuelle Arbeitsverzeichnis des untergeordneten Prozesses geändert, nicht jedoch des Python-Prozesses. Sie müssen dafür verwenden os.chdir.

  3. Sie müssen sich keine Gedanken über Sonderzeichen machen , die von der Shell interpretiert werden. os.chmod(path, mode)funktioniert unabhängig vom Dateinamen, während os.spawn("chmod 777 " + path)es schrecklich fehlschlägt, wenn der Dateiname so etwas wie ist ; rm -rf ~. (Beachten Sie, dass Sie dies umgehen können, wenn Sie subprocess.callohne das shellArgument verwenden.)

  4. Sie müssen sich keine Gedanken über Dateinamen machen, die mit einem Bindestrich beginnen . os.chmod("--quiet", mode)ändert die Berechtigungen der genannten Datei --quiet, schlägt os.spawn("chmod 777 --quiet")jedoch fehl, da dies --quietals Argument interpretiert wird. Dies gilt auch für subprocess.call(["chmod", "777", "--quiet"]).

  5. Sie haben weniger plattform- und Shell-übergreifende Probleme, da die Standardbibliothek von Python dies für Sie erledigen soll. Hat Ihr System einen chmodBefehl? Ist es installiert? Unterstützt es die Parameter, die Sie erwarten? Das osModul wird versuchen, so plattformübergreifend wie möglich zu sein und dokumentiert, wann dies nicht möglich ist.

  6. Wenn der Befehl, den Sie ausführen, eine Ausgabe hat , die Sie interessiert, müssen Sie sie analysieren, was schwieriger ist als es sich anhört, da Sie möglicherweise Eckfälle (Dateinamen mit Leerzeichen, Tabulatoren und Zeilenumbrüchen) vergessen, selbst wenn Sie dies tun Portabilität ist mir egal.

Flimm
quelle
38
Um den plattformübergreifenden Punkt zu erweitern, lautet die Auflistung eines Verzeichnisses unter Linux "ls" und unter Windows "dir". Das Abrufen des Inhalts eines Verzeichnisses ist eine sehr häufige Aufgabe auf niedriger Ebene.
Cort Ammon
1
@CortAmmon: "Low-Level" ist relativ lsoder dirfür bestimmte Entwicklertypen ziemlich hoch, genau wie bashoder cmdoder kshoder welche Shell Sie bevorzugen.
Sebastian Mach
1
@phresnel: Ich habe nie so darüber nachgedacht. Für mich war "direkter Aufruf der Kernel-API Ihres Betriebssystems" sehr niedrig. Ich gehe davon aus, dass es eine andere Perspektive gibt, die mir entgeht, weil ich mich (natürlich) mit meinen eigenen Vorurteilen dem nähere.
Cort Ammon
5
@CortAmmon: Richtig, und lsist höher als das, da es kein direkter Aufruf der Kernel-API Ihres Betriebssystems ist. Es ist eine (kleine) Anwendung.
Steve Jessop
1
@SteveJessop. Ich habe "Abrufen des Inhalts eines Verzeichnisses" auf niedriger Ebene aufgerufen. Ich denke nicht lsoder diraber opendir()/readdir()(Linux API) oder FindFirstFile()/FindNextFile()(Windows API) oderFile.listFiles (Java API) oder Directory.GetFiles()(C #). All dies ist eng mit einem direkten Aufruf an das Betriebssystem verbunden. Einige können so einfach sein, wie eine Nummer in ein Register zu schieben und aufzurufen int 13h, um den Kernelmodus auszulösen.
Cort Ammon
133

Es ist sicherer. Um Ihnen hier eine Idee zu geben, finden Sie ein Beispielskript

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

Wenn die Eingabe des Benutzers test; rm -rf ~dies wäre, würde das Home-Verzeichnis gelöscht.

Aus diesem Grund ist es sicherer, die integrierte Funktion zu verwenden.

Daher sollten Sie auch den Unterprozess anstelle des Systems verwenden.

iProgramm
quelle
26
Oder eine andere Sichtweise: Was ist einfacher, wenn man Python-Programme oder Python-Programme schreibt, die Shell-Skripte schreiben? :-)
Steve Jessop
3
@SteveJessop, ein Kollege von mir, war erstaunt, dass ein kleines Python-Skript, das ich ihm beim Schreiben half, 20 (!) Mal schneller als das Tan-Shell-Skript funktionierte. Ich erklärte, dass die Ausgabeumleitung sexy aussehen kann - aber das Öffnen und Schließen der Datei bei jeder Iteration. Aber einige lieben es, Sachen auf die harte Tour zu machen - :)
Vulkan
1
@SteveJessop, das ist eine Trickfrage - du würdest es erst zur Laufzeit wissen! :)
60

Es gibt vier starke Fälle, in denen Pythons spezifischere Methoden im osModul gegenüber der Verwendung os.systemoder dem subprocessModul bei der Ausführung eines Befehls bevorzugt werden :

  • Redundanz - Das Starten eines anderen Prozesses ist redundant und verschwendet Zeit und Ressourcen.
  • Portabilität - Viele der Methoden im osModul sind auf mehreren Plattformen verfügbar, während viele Shell-Befehle os-spezifisch sind.
  • Grundlegendes zu den Ergebnissen - Wenn Sie einen Prozess zum Ausführen beliebiger Befehle erstellen, müssen Sie die Ergebnisse aus der Ausgabe analysieren und verstehen, ob und warum ein Befehl etwas falsch gemacht hat.
  • Sicherheit - Ein Prozess kann möglicherweise jeden Befehl ausführen, den er erhält. Dies ist ein schwaches Design und kann durch Verwendung bestimmter Methoden im osModul vermieden werden .

Redundanz (siehe redundanten Code ):

Sie führen tatsächlich einen redundanten "Middle-Man" auf dem Weg zu den eventuellen Systemaufrufen aus (chmod in Ihrem Beispiel). Dieser mittlere Mann ist ein neuer Prozess oder eine neue Unterschale.

Von os.system:

Führen Sie den Befehl (eine Zeichenfolge) in einer Subshell aus ...

Und subprocess ist nur ein Modul, um neue Prozesse hervorzubringen.

Sie können tun, was Sie brauchen, ohne diese Prozesse zu erzeugen.

Portabilität (siehe Quellcode-Portabilität ):

Das osZiel des Moduls ist die Bereitstellung allgemeiner Betriebssystemdienste. Die Beschreibung beginnt mit:

Dieses Modul bietet eine tragbare Möglichkeit zur Verwendung betriebssystemabhängiger Funktionen.

Sie können os.listdirsowohl unter Windows als auch unter Unix verwenden. Wenn Sie versuchen, os.system/ subprocessfür diese Funktionalität zu verwenden, müssen Sie zwei Aufrufe (für ls/ dir) beibehalten und überprüfen, auf welchem ​​Betriebssystem Sie sich befinden. Dies ist nicht so portabel und wird später noch mehr Frustration verursachen (siehe Umgang mit der Ausgabe ).

Grundlegendes zu den Ergebnissen des Befehls:

Angenommen, Sie möchten die Dateien in einem Verzeichnis auflisten.

Wenn Sie os.system("ls")/ verwendensubprocess.call(['ls']) , können Sie nur die Ausgabe des Prozesses zurückerhalten. Dies ist im Grunde eine große Zeichenfolge mit den Dateinamen.

Wie können Sie eine Datei mit einem Leerzeichen im Namen von zwei Dateien unterscheiden?

Was ist, wenn Sie keine Berechtigung zum Auflisten der Dateien haben?

Wie sollten Sie die Daten Python-Objekten zuordnen?

Diese sind nur aus dem Kopf, und obwohl es Lösungen für diese Probleme gibt - warum sollte ich ein Problem, das für Sie gelöst wurde, erneut lösen?

Dies ist ein Beispiel für die folgenden nicht selbst wiederholen Prinzip (oft als „trocken“ reffered to) von nicht eine Implementierung zu wiederholen , die bereits vorhanden und ist für Sie frei zur Verfügung.

Sicherheit:

os.systemund subprocesssind mächtig. Es ist gut, wenn Sie diese Kraft brauchen, aber es ist gefährlich, wenn Sie es nicht tun. Wenn Sie verwenden os.listdir, wissen Sie, dass es nichts anderes tun kann, als Dateien aufzulisten oder einen Fehler auszulösen. Wenn Sie os.systemoder verwendensubprocess dasselbe Verhalten erreichen, können Sie möglicherweise etwas tun, was Sie nicht wollten.

Einspritzsicherheit (siehe Beispiele für die Einspritzung der Schale ) :

Wenn Sie die Eingabe des Benutzers als neuen Befehl verwenden, haben Sie ihm im Grunde eine Shell gegeben. Dies ähnelt der SQL-Injection, die dem Benutzer eine Shell in der Datenbank bereitstellt.

Ein Beispiel wäre ein Befehl des Formulars:

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

Dies kann leicht ausgeführt werden ausgebeutet jeden beliebigen Code mit der Eingabe: NASTY COMMAND;#die schließliche zu erstellen:

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

Es gibt viele solcher Befehle, die Ihr System gefährden können.

Reut Sharabani
quelle
3
Ich würde sagen, 2. ist der Hauptgrund.
Jaredad7
23

Aus einem einfachen Grund: Wenn Sie eine Shell-Funktion aufrufen, wird eine Sub-Shell erstellt, die nach dem Vorhandensein Ihres Befehls zerstört wird. Wenn Sie also das Verzeichnis in einer Shell ändern, hat dies keine Auswirkungen auf Ihre Umgebung in Python.

Außerdem ist das Erstellen einer Sub-Shell zeitaufwändig, sodass sich die direkte Verwendung von Betriebssystembefehlen auf Ihre Leistung auswirkt

BEARBEITEN

Ich hatte einige Timing-Tests:

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

Die interne Funktion läuft mehr als zehnmal schneller

EDIT2

Es kann Fälle geben, in denen das Aufrufen einer externen ausführbaren Datei zu besseren Ergebnissen führt als Python-Pakete. Ich habe mich gerade an eine E-Mail erinnert, die von einem meiner Kollegen gesendet wurde, dass die Leistung von gzip, das über einen Unterprozess aufgerufen wurde, viel höher war als die Leistung eines von ihm verwendeten Python-Pakets. Aber sicherlich nicht, wenn es sich um Standard-Betriebssystempakete handelt, die Standard-Betriebssystembefehle emulieren

Vulkan
quelle
Ist das zufällig mit iPython erledigt? Ich hätte nicht gedacht, dass Sie spezielle Funktionen verwenden könnten, beginnend mit %dem normalen Interpreter.
iProgramm
@aPyDeveloper, yep, es war iPython - unter Ubuntu. "Magisch" % timeit ist ein Segen - obwohl es einige Fälle gibt - meistens mit String-Formatierung - die es nicht verarbeiten kann
Vulkan
1
Sie können auch ein Python-Skript erstellen und dann das time <path to script> Terminal eingeben, um die tatsächliche, Benutzer- und Prozesszeit anzuzeigen. Dies ist der Fall, wenn Sie nicht über iPython verfügen und Zugriff auf die Unix-Befehlszeile haben.
iProgramm
1
@aPyDeveloper, ich sehe keinen Grund, hart zu arbeiten - wenn ich iPython auf meinem Computer habe
Vulkan
Wahr! Ich habe gesagt, wenn Sie nicht iPython hatten. :)
iProgramm
16

Shell-Aufrufe sind betriebssystemspezifisch, während Python-OS-Modulfunktionen dies in den meisten Fällen nicht sind. Und es wird vermieden, einen Unterprozess zu erzeugen.

JoshRomRock
quelle
1
Python-Modulfunktionen erzeugen auch neue Unterprozesse, um eine neue Unterschale aufzurufen.
Koderok
7
@Koderok Unsinn, Modulfunktionen werden in-process genannt
dwurf
3
@Koderok: Das OS-Modul verwendet die zugrunde liegenden Systemaufrufe, die der Shell-Befehl verwendet, nicht die Shell-Befehle. Dies bedeutet, dass der OS-Systemaufruf normalerweise sicherer und schneller ist (kein String-Parsing, Boo Fork, kein Exec, stattdessen ist es nur ein Kernel-Aufruf) als die Shell-Befehle. Beachten Sie, dass in den meisten Fällen der Shell-Aufruf und der Systemaufruf häufig einen ähnlichen oder gleichen Namen haben, dort jedoch separat dokumentiert sind. Der Shell-Aufruf befindet sich in Man-Abschnitt 1 (dem Standard-Man-Abschnitt), während sich der äquivalent benannte Systemaufruf in Man-Abschnitt 2 befindet (z. B. Man 2 chmod).
Lie Ryan
1
@ dwurf, LieRyan: Mein schlechtes! Ich hatte anscheinend eine falsche Vorstellung. Vielen Dank!
Koderok
11

Es ist weitaus effizienter. Die "Shell" ist nur eine weitere Betriebssystem-Binärdatei, die viele Systemaufrufe enthält. Warum muss der gesamte Shell-Prozess nur für diesen einzelnen Systemaufruf erstellt werden?

Die Situation ist noch schlimmer, wenn Sie os.systemfür etwas verwenden, das keine eingebaute Shell ist. Sie starten einen Shell-Prozess, der wiederum eine ausführbare Datei startet, die dann (zwei Prozesse entfernt) den Systemaufruf ausführt. Zumindest subprocesshätte die Notwendigkeit eines Shell-Zwischenprozesses beseitigt.

Dies ist nicht spezifisch für Python. systemdist eine solche Verbesserung der Linux-Startzeiten aus dem gleichen Grund: Es führt die erforderlichen Systemaufrufe selbst aus, anstatt tausend Shells zu erzeugen.

MSalters
quelle