440 votes

Signification réelle de "shell=True" dans un sous-processus

J'appelle différents processus avec le subprocess module. Cependant, j'ai une question.

Dans les codes suivants :

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

et

callProcess = subprocess.Popen(['ls', '-l']) # without shell

Les deux fonctionnent. Après avoir lu les docs, j'ai appris que shell=True signifie exécuter le code par l'intermédiaire du shell. Cela signifie donc qu'en cas d'absence, le processus est directement lancé.

Alors, que dois-je préférer dans mon cas - j'ai besoin d'exécuter un processus et d'obtenir sa sortie. Quel avantage ai-je à l'appeler depuis le shell ou en dehors de celui-ci ?

49 votes

La première commande est incorrecte : -l est transmis à /bin/sh (le shell) au lieu de ls programme sous Unix si shell=True . L'argument chaîne doit être utilisé avec shell=True dans la plupart des cas, au lieu d'une liste.

3 votes

Re "le processus est directement lancé" : Quoi ?

21 votes

L'affirmation "Les deux fonctionnent" à propos de ces deux appels est incorrecte et trompeuse. Les appels fonctionnent différemment. Il suffit de passer de shell=True a False et vice versa est une erreur. De docs : "Sur POSIX avec shell=True, (...) Si args est une séquence, le premier élément spécifie la chaîne de commande, et tous les éléments supplémentaires seront traités comme des arguments supplémentaires pour le shell lui-même.". Sous Windows, il y a conversion automatique ce qui pourrait être indésirable.

316voto

Heath Hunnicutt Points 9801

L'avantage de ne pas appeler via le shell est que vous n'invoquez pas un "programme mystère". Sous POSIX, la variable d'environnement SHELL contrôle quel binaire est invoqué comme "shell". Sous Windows, il n'y a pas de descendant de bourne shell, seulement cmd.exe.

Ainsi, l'invocation du shell invoque un programme choisi par l'utilisateur et dépend de la plate-forme. De manière générale, évitez les invocations via le shell.

L'invocation via l'interpréteur de commandes vous permet d'étendre les variables d'environnement et les goujons de fichiers selon le mécanisme habituel de l'interpréteur de commandes. Sur les systèmes POSIX, l'interpréteur de commandes développe les globes de fichiers en une liste de fichiers. Sous Windows, un globule de fichier (par exemple, "*.*") n'est pas développé par l'interpréteur de commandes, de toute façon (mais les variables d'environnement sur une ligne de commande son élargi par cmd.exe).

Si vous pensez avoir besoin d'expansions de variables d'environnement et de gothiques de fichiers, recherchez l'option ILS Les attaques des années 1992 sur les services réseau qui effectuaient des invocations de sous-programmes via le shell. Les exemples incluent les diverses sendmail des portes dérobées impliquant ILS .

En résumé, utilisez shell=False .

4 votes

Merci pour la réponse. Je n'en suis pas encore au stade où je dois m'inquiéter des exploits, mais je comprends où vous voulez en venir.

106 votes

Si vous êtes négligent au début, aucun souci ne vous aidera à vous rattraper plus tard. ;)

0 votes

Que faire si l'on veut limiter la mémoire maximale du sous-processus ? stackoverflow.com/questions/3172470/

244voto

Mina Gabriel Points 2172
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

En donnant à l'argument shell une valeur vraie, subprocess génère un processus shell intermédiaire et lui demande d'exécuter la commande. En d'autres termes, l'utilisation d'un interpréteur de commandes intermédiaire signifie que les variables, les motifs globaux et les autres caractéristiques spéciales de l'interpréteur de commandes dans la chaîne de commande sont traités avant l'exécution de la commande. Ici, dans l'exemple, $HOME a été traité avant la commande echo. En fait, il s'agit d'une commande avec expansion de l'interpréteur de commandes alors que la commande ls -l est considérée comme une commande simple.

source : Module de sous-processus

68voto

Richeek Points 1131

Voici un exemple où les choses peuvent mal tourner avec Shell=True.

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

Consultez le document ici : sous-processus.call()

47voto

lunaryorn Points 13621

L'exécution de programmes par le biais de l'interpréteur de commandes signifie que toutes les entrées utilisateur transmises au programme sont interprétées selon les règles syntaxiques et sémantiques de l'interpréteur de commandes invoqué. Au mieux, cela ne cause que des désagréments à l'utilisateur, car celui-ci doit obéir à ces règles. Par exemple, les chemins contenant des caractères spéciaux de l'interpréteur de commandes, comme des guillemets ou des blancs, doivent être échappés. Au pire, cela provoque des fuites de sécurité, car l'utilisateur peut exécuter des programmes arbitraires.

shell=True est parfois pratique pour utiliser des fonctionnalités spécifiques de l'interpréteur de commandes, comme le découpage des mots ou l'expansion des paramètres. Cependant, si une telle fonctionnalité est nécessaire, utilisez d'autres modules qui vous sont proposés (par ex. os.path.expandvars() pour l'expansion des paramètres ou shlex pour le fractionnement des mots). Cela représente plus de travail, mais évite d'autres problèmes.

En bref : évitez shell=True par tous les moyens.

30voto

tripleee Points 28746

Les autres réponses expliquent de manière adéquate les avertissements en matière de sécurité, qui sont également mentionnés dans le document intitulé subprocess documentation. Mais en plus de cela, les frais généraux liés au lancement d'un shell pour lancer le programme que vous voulez exécuter sont souvent inutiles et définitivement stupides dans les situations où vous n'utilisez pas réellement les fonctionnalités du shell. De plus, la complexité cachée supplémentaire devrait vous effrayer, notamment si vous n'êtes pas très familier avec le shell ou les services qu'il fournit.

Lorsque les interactions avec l'interpréteur de commandes sont non triviales, vous exigez maintenant que le lecteur et le mainteneur du script de Python (qui peut ou non être votre futur moi) comprenne à la fois Python et le script de l'interpréteur de commandes. Rappelez-vous la devise de Python "L'explicite est meilleur que l'implicite" ; même lorsque le code Python sera un peu plus complexe que le script équivalent (et souvent très laconique) de l'interpréteur de commandes, il peut être préférable de supprimer l'interpréteur de commandes et de remplacer la fonctionnalité par des constructions Python natives. Minimiser le travail effectué dans un processus externe et garder le contrôle dans votre propre code autant que possible est souvent une bonne idée, simplement parce que cela améliore la visibilité et réduit les risques d'effets secondaires -- voulus ou non --.

L'expansion des caractères génériques, l'interpolation des variables et la redirection sont toutes simples à remplacer par des constructions Python natives. Un pipeline shell complexe dont certaines parties ou la totalité ne peuvent pas être raisonnablement réécrites en Python serait la seule situation où vous pourriez peut-être envisager d'utiliser le shell. Vous devez néanmoins vous assurer que vous comprenez les implications en termes de performances et de sécurité.

Dans le cas trivial, pour éviter shell=True il suffit de remplacer

subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)

avec

subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])

Remarquez comment le premier argument est une liste de chaînes à passer à execvp() et comment le fait de citer des chaînes de caractères et de mettre en évidence les métacaractères de l'interpréteur de commandes n'est généralement pas nécessaire (ou utile, ou correct). Voir aussi Quand mettre des guillemets autour d'une variable shell ?

Si vous ne voulez pas vous débrouiller tout seul, les shlex.split() La fonction peut le faire pour vous. Elle fait partie de la bibliothèque standard de Python, mais bien sûr, si votre chaîne de commande shell est statique, vous pouvez simplement l'exécuter une fois, pendant le développement, et coller le résultat dans votre script.

En passant, vous voulez très souvent éviter Popen si l'un des wrappers les plus simples de la section subprocess Le paquet fait ce que vous voulez. Si vous avez un Python assez récent, vous devriez probablement utiliser subprocess.run .

  • Con check=True il échouera si la commande que vous avez exécutée a échoué.
  • Con stdout=subprocess.PIPE il capturera la sortie de la commande.
  • Con text=True (ou de manière quelque peu obscure, avec le synonyme universal_newlines=True ) il décodera la sortie en une chaîne Unicode correcte (c'est juste bytes dans l'encodage du système sinon, sur Python 3).

Sinon, pour de nombreuses tâches, vous voulez check_output pour obtenir la sortie d'une commande, tout en vérifiant qu'elle a réussi, ou encore check_call s'il n'y a pas de sortie à collecter.

Je terminerai par une citation de David Korn : "Il est plus facile d'écrire un shell portable qu'un script portable". Même subprocess.run('echo "$HOME"', shell=True) n'est pas portable sous Windows.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X