Pourquoi utiliser les méthodes du module os de Python au lieu d'exécuter directement des commandes shell?

157

J'essaie de comprendre quelle est la motivation derrière l'utilisation des fonctions de bibliothèque de Python pour exécuter des tâches spécifiques au système d'exploitation telles que la création de fichiers / répertoires, la modification des attributs de fichier, etc. au lieu de simplement exécuter ces commandes via os.system()ou subprocess.call()?

Par exemple, pourquoi voudrais-je utiliser os.chmodau lieu de faire os.system("chmod...")?

Je comprends qu'il est plus «pythonique» d'utiliser autant que possible les méthodes de bibliothèque disponibles de Python au lieu de simplement exécuter directement des commandes shell. Mais y a-t-il une autre motivation derrière cela du point de vue de la fonctionnalité?

Je ne parle ici que d'exécuter de simples commandes shell sur une ligne. Lorsque nous avons besoin de plus de contrôle sur l'exécution de la tâche, je comprends que l'utilisation du subprocessmodule a plus de sens, par exemple.

Koderok
la source
6
Vous avez essentiellement frappé le clou sur la tête. Les tâches au niveau du système d'exploitation auxquelles vous faites référence sont suffisamment courantes pour justifier leur propre fonction au lieu d'être simplement reléguées à être appelées via os.system.
deweyredman
7
BTW, avez-vous essayé de chronométrer le temps d'exécution - os.chmod contre os.system ("chmod ...") . J'imagine que cela répondra à une partie de votre question.
volcan
61
Pourquoi avez print-vous pu os.system("echo Hello world!")?
user253751
25
Pour la même raison, vous devriez utiliser os.pathpour gérer les chemins au lieu de les gérer manuellement: cela fonctionne sur tous les systèmes d'exploitation sur lesquels il s'exécute.
Bakuriu
51
«Exécuter directement les commandes shell» est en fait moins direct. Le shell n'est pas une interface de bas niveau avec le système et os.chmodne va pas appeler le chmodprogramme que le shell ferait. L' utilisation os.system('chmod ...')lance un shell pour interpréter une chaîne pour appeler un autre exécutable pour faire un appel à la C chmodfonction, tout os.chmod(...)va beaucoup plus directement au C chmod.
user2357112 prend en charge Monica le

Réponses:

325
  1. Il est plus rapide , os.systemet de subprocess.callcréer de nouveaux processus qui est inutile pour quelque chose aussi simple. En fait, os.systemet subprocess.callavec l' shellargument, créez généralement au moins deux nouveaux processus: le premier étant le shell, et le second étant la commande que vous exécutez (si ce n'est pas un shell intégré comme test).

  2. Certaines commandes sont inutiles dans un processus séparé . Par exemple, si vous exécutez os.spawn("cd dir/"), cela changera le répertoire de travail actuel du processus enfant, mais pas du processus Python. Vous devez utiliser os.chdirpour cela.

  3. Vous n'avez pas à vous soucier des caractères spéciaux interprétés par le shell. os.chmod(path, mode)fonctionnera quel que soit le nom de fichier, alors que os.spawn("chmod 777 " + path)cela échouera horriblement si le nom de fichier est quelque chose comme ; rm -rf ~. (Notez que vous pouvez contourner ce problème si vous utilisez subprocess.callsans l' shellargument.)

  4. Vous n'avez pas à vous soucier des noms de fichiers commençant par un tiret . os.chmod("--quiet", mode)changera les permissions du fichier nommé --quiet, mais os.spawn("chmod 777 --quiet")échouera, comme cela --quietest interprété comme un argument. Cela est vrai même pour subprocess.call(["chmod", "777", "--quiet"]).

  5. Vous avez moins de problèmes multi-plateformes et inter-shell, car la bibliothèque standard de Python est censée gérer cela pour vous. Votre système a-t-il la chmodcommande? Est-il installé? Prend-il en charge les paramètres que vous attendez de lui? Le osmodule essaiera d'être aussi multiplateforme que possible et documentera lorsque ce n'est pas possible.

  6. Si la commande que vous exécutez a une sortie qui vous tient à cœur, vous devez l'analyser, ce qui est plus délicat qu'il n'y paraît, car vous risquez d'oublier les cas d'angle (noms de fichiers contenant des espaces, des tabulations et des retours à la ligne), même lorsque vous ne vous souciez pas de la portabilité.

Flimm
la source
38
Pour ajouter au point "multiplateforme", la liste d'un répertoire est "ls" sous linux, "dir" sous windows. Obtenir le contenu d'un répertoire est une tâche de bas niveau très courante.
Cort Ammon
1
@CortAmmon: "Low-Level" est relatif, lsou direst assez élevé pour certains types de développeurs, tout comme bashou cmdou kshou quel que soit le shell que vous préférez.
Sebastian Mach
1
@phresnel: Je n'y ai jamais pensé de cette façon. Pour moi, "l'appel direct à l'API du noyau de votre système d'exploitation" était de très faible niveau. Je suppose qu'il y a une perspective différente à ce sujet qui m'échappe parce que je l'aborde (naturellement) avec mes propres préjugés.
Cort Ammon
5
@CortAmmon: c'est vrai, et lsc'est un niveau plus élevé que cela, car ce n'est pas un appel direct à l'API du noyau de votre système d'exploitation. C'est une (petite) application.
Steve Jessop
1
@SteveJessop. J'ai appelé "obtenir le contenu d'un répertoire" de bas niveau. Je ne pense pas lsou dirmais opendir()/readdir()(api linux) ou FindFirstFile()/FindNextFile()(api windows) ou File.listFiles(API java) ou Directory.GetFiles()(C #). Tous ces éléments sont étroitement liés à un appel direct au système d'exploitation. Certains peuvent être aussi simples que de pousser un nombre dans un registre et d'appeler int 13hpour déclencher le mode noyau.
Cort Ammon
133

C'est plus sûr. Pour vous donner une idée voici un exemple de script

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

Si l'entrée de l'utilisateur était, test; rm -rf ~cela supprimerait le répertoire de base.

C'est pourquoi il est plus sûr d'utiliser la fonction intégrée.

Par conséquent, vous devriez également utiliser le sous-processus au lieu du système.

iProgramme
la source
26
Ou une autre façon de voir les choses, qu'est-ce qui est plus facile à faire, écrire des programmes Python ou écrire des programmes Python qui écrivent des scripts shell? :-)
Steve Jessop
3
@SteveJessop, un de mes collègues a été étonné qu'un petit script Python que je l'ai aidé à écrire fonctionne 20 (!) Fois plus vite que le script tan shell. J'ai expliqué que la redirection de sortie peut sembler sexy - mais elle implique l'ouverture et la fermeture du fichier à chaque itération. Mais certains aiment faire les choses à la dure - :)
volcan
1
@SteveJessop, c'est une question piège - vous ne le sauriez pas avant l'exécution! :)
60

Il existe quatre cas forts pour préférer les méthodes plus spécifiques de Python dans le osmodule à l'utilisation de os.systemou du subprocessmodule lors de l'exécution d'une commande:

  • Redondance - engendrer un autre processus est redondant et gaspille du temps et des ressources.
  • Portabilité - De nombreuses méthodes du osmodule sont disponibles sur plusieurs plates-formes tandis que de nombreuses commandes shell sont spécifiques à l'OS.
  • Comprendre les résultats - Le lancement d'un processus pour exécuter des commandes arbitraires vous oblige à analyser les résultats de la sortie et à comprendre si et pourquoi une commande a fait quelque chose de mal.
  • Sécurité - Un processus peut potentiellement exécuter toute commande qui lui est donnée. Cette conception est faible et peut être évitée en utilisant des méthodes spécifiques dans le osmodule.

Redondance (voir code redondant ):

Vous exécutez en fait un "intermédiaire" redondant sur votre chemin vers les éventuels appels système ( chmoddans votre exemple). Cet intermédiaire est un nouveau processus ou sous-shell.

De os.system:

Exécutez la commande (une chaîne) dans un sous-shell ...

Et subprocessc'est juste un module pour engendrer de nouveaux processus.

Vous pouvez faire ce dont vous avez besoin sans engendrer ces processus.

Portabilité (voir portabilité du code source ):

L' osobjectif du module est de fournir des services génériques de système d'exploitation et sa description commence par:

Ce module fournit un moyen portable d'utiliser les fonctionnalités dépendant du système d'exploitation.

Vous pouvez utiliser os.listdirà la fois Windows et Unix. Essayer d'utiliser os.system/ subprocesspour cette fonctionnalité vous obligera à maintenir deux appels (pour ls/ dir) et à vérifier sur quel système d'exploitation vous êtes. Ce n'est pas aussi portable et va causer encore plus de frustration plus tard (voir Gestion de sortie ).

Comprendre les résultats de la commande:

Supposons que vous souhaitiez lister les fichiers dans un répertoire.

Si vous utilisez os.system("ls")/ subprocess.call(['ls']), vous ne pouvez récupérer que la sortie du processus, qui est essentiellement une grande chaîne avec les noms de fichiers.

Comment pouvez-vous distinguer un fichier avec un espace dans son nom de deux fichiers?

Que faire si vous n'êtes pas autorisé à répertorier les fichiers?

Comment devez-vous mapper les données sur des objets python?

Ce ne sont que de ma tête, et bien qu'il existe des solutions à ces problèmes, pourquoi résoudre à nouveau un problème qui a été résolu pour vous?

Ceci est un exemple de suivi du principe Ne vous répétez pas (souvent appelé «DRY») en ne répétant pas une implémentation qui existe déjà et qui est librement disponible pour vous.

Sécurité:

os.systemet subprocesssont puissants. C'est bien quand vous avez besoin de ce pouvoir, mais c'est dangereux quand vous n'en avez pas. Lorsque vous utilisez os.listdir, vous savez qu'il ne peut rien faire d'autre que répertorier les fichiers ou générer une erreur. Lorsque vous utilisez os.systemou subprocesspour obtenir le même comportement, vous pouvez éventuellement finir par faire quelque chose que vous n'aviez pas l'intention de faire.

Sécurité d'injection (voir exemples d'injection de coque ) :

Si vous utilisez l'entrée de l'utilisateur comme nouvelle commande, vous lui avez essentiellement donné un shell. C'est un peu comme l'injection SQL fournissant un shell dans la base de données pour l'utilisateur.

Un exemple serait une commande de la forme:

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

Cela peut être facilement exploité pour exécuter n'importe quel code arbitraire en utilisant l'entrée: NASTY COMMAND;#pour créer l'éventuel:

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

Il existe de nombreuses commandes de ce type qui peuvent mettre votre système en danger.

Reut Sharabani
la source
3
Je dirais 2. est la raison principale.
jaredad7
23

Pour une raison simple - lorsque vous appelez une fonction shell, cela crée un sous-shell qui est détruit après que votre commande existe, donc si vous changez de répertoire dans un shell - cela n'affecte pas votre environnement en Python.

De plus, la création d'un sous-shell prend du temps, donc l'utilisation directe des commandes du système d'exploitation aura un impact sur vos performances

ÉDITER

J'ai eu quelques tests de chronométrage en cours:

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

La fonction interne s'exécute plus de 10 fois plus vite

EDIT2

Il peut y avoir des cas où l'invocation d'un exécutable externe peut donner de meilleurs résultats que les packages Python - je viens de me souvenir d'un courrier envoyé par un de mes collègues selon lequel les performances de gzip appelées via un sous-processus étaient bien supérieures à celles d'un package Python qu'il utilisait. Mais certainement pas quand nous parlons de packages OS standard émulant des commandes OS standard

volcan
la source
Par hasard, est-ce fait avec iPython? Je ne pensais pas pouvoir utiliser des fonctions spéciales en commençant par %utiliser l'interpréteur normal.
iProgram
@aPyDeveloper, oui, c'était iPython - sur Ubuntu. "Magical" % timeit est une bénédiction - bien qu'il y ait certains cas - principalement avec un formatage de chaîne - qu'il ne peut pas traiter
volcan
1
Ou vous pouvez également créer un script python, puis taper time <path to script> terminal et il vous indiquera le temps réel, utilisateur et processus pris. C'est si vous n'avez pas iPython et que vous avez accès à la ligne de commande Unix.
iProgram
1
@aPyDeveloper, je ne vois aucune raison de travailler dur - quand j'ai iPython sur ma machine
volcan
Vrai! J'ai dit que si vous n'aviez pas iPython. :)
iProgram
16

Les appels du shell sont spécifiques au système d'exploitation alors que les fonctions du module Python os ne le sont pas, dans la plupart des cas. Et cela évite de générer un sous-processus.

JoshRomRock
la source
1
Les fonctions du module Python génèrent également de nouveaux sous-processus pour appeler un nouveau sous-shell.
Koderok
7
@Koderok non-sens, les fonctions du module sont appelées en cours
dwurf
3
@Koderok: le module os utilise les appels système sous-jacents utilisés par la commande shell, il n'utilise pas les commandes shell. Cela signifie que les appels système os sont généralement plus sûrs et plus rapides (pas d'analyse de chaîne, boo fork, pas d'exec, à la place c'est juste un appel du noyau) que les commandes shell. Notez que dans la plupart des cas, l'appel shell et l'appel système ont souvent un nom similaire ou identique, mais documenté séparément; l'appel shell est dans la section man 1 (la section man par défaut) tandis que l'appel système nommé de manière équivalente est dans la section man 2 (par exemple man 2 chmod).
Lie Ryan
1
@ dwurf, LieRyan: Mon mauvais! J'avais une fausse idée, semble-t-il. Merci!
Koderok
11

C'est beaucoup plus efficace. Le "shell" est juste un autre binaire du système d'exploitation qui contient beaucoup d'appels système. Pourquoi engager la surcharge de création de l'ensemble du processus shell uniquement pour cet appel système unique?

La situation est encore pire lorsque vous utilisez os.systempour quelque chose qui n'est pas un shell intégré. Vous démarrez un processus shell qui à son tour démarre un exécutable qui ensuite (à deux processus) effectue l'appel système. Au moins subprocessaurait éliminé le besoin d'un processus d'intermédiaire shell.

Ce n'est pas spécifique à Python, ceci. systemdest une telle amélioration des temps de démarrage de Linux pour la même raison: il fait que le système nécessaire s'appelle lui-même au lieu de générer un millier de shells.

MSalters
la source