Signification réelle de «shell = True» dans le sous-processus

260

J'appelle différents processus avec le subprocessmodule. 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 documents, j'ai appris que cela shell=Truesignifie exécuter le code via le shell. Cela signifie donc qu'en l'absence, le processus est directement démarré.

Alors, que dois-je préférer pour mon cas - j'ai besoin d'exécuter un processus et d'obtenir sa sortie. Quel avantage ai-je à l'appeler depuis l'intérieur ou l'extérieur de la coque.

user225312
la source
21
la première commande est incorrecte: -lest passée à /bin/sh(le shell) au lieu du lsprogramme sous Unix sishell=True . L'argument chaîne doit être utilisé avec shell=Truedans la plupart des cas au lieu d'une liste.
jfs
1
re "le processus est démarré directement": Wut?
allyourcode
9
La déclaration "Les deux fonctionnent." à propos de ces 2 appels est incorrect et trompeur. Les appels fonctionnent différemment. Passer de shell=Trueà Falseet vice versa est une erreur. De la documentation : "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 existe une conversion automatique , ce qui peut être indésirable.
mbdevpl

Réponses:

183

L'avantage de ne pas appeler via le shell est que vous n'invoquez pas un «programme mystère». Sur POSIX, la variable d'environnement SHELLcontrôle quel binaire est appelé en tant que "shell". Sous Windows, il n'y a pas de bourne shell descendant, seulement cmd.exe.

Donc, invoquer le shell appelle 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 le shell vous permet d'étendre les variables d'environnement et les globes de fichiers selon le mécanisme habituel du shell. Sur les systèmes POSIX, le shell étend les globes de fichiers à une liste de fichiers. Sous Windows, un glob de fichier (par exemple, "*. *") N'est de toute façon pas développé par le shell (mais les variables d'environnement sur une ligne de commande sont développées par cmd.exe).

Si vous pensez que vous voulez des extensions de variables d'environnement et des globes de fichiers, recherchez les ILSattaques de 1992-ish sur les services réseau qui ont effectué des invocations de sous-programme via le shell. Les exemples incluent les divers sendmailbackdoors impliquant ILS.

En résumé, utilisez shell=False.

Heath Hunnicutt
la source
2
Merci d'avoir répondu. Bien que je ne sois vraiment pas à ce stade où je devrais me soucier des exploits, mais je comprends où vous en êtes.
user225312
55
Si vous êtes imprudent au début, aucune inquiétude ne vous aidera à vous rattraper plus tard. ;)
Heath Hunnicutt
Que faire si vous souhaitez limiter la mémoire maximale du sous-processus? stackoverflow.com/questions/3172470/…
Pramod
8
la déclaration sur $SHELLn'est pas correcte. Pour citer subprocess.html: "Sous Unix avec shell=True, le shell est par défaut /bin/sh". (pas $SHELL)
marcin
1
@ user2428107: Oui, si vous utilisez l'invocation de backtick sur Perl, vous utilisez l'invocation du shell et ouvrez les mêmes problèmes. Utilisez 3+ arg opensi vous voulez des moyens sécurisés d'appeler un programme et de capturer la sortie.
ShadowRanger
137
>>> 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

Si vous définissez l'argument shell sur une valeur vraie, le sous-processus génère un processus shell intermédiaire et lui demande d'exécuter la commande. En d'autres termes, l'utilisation d'un shell intermédiaire signifie que les variables, les modèles glob et d'autres fonctionnalités spéciales du shell 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, c'est le cas de la commande avec expansion du shell alors que la commande ls -l est considérée comme une commande simple.

source: Module de sous-processus

Mina Gabriel
la source
16
Je ne sais pas pourquoi ce n'est pas la réponse choisie. De loin celui qui correspond réellement à la question
Rodrigo Lopez Guerra
1
se mettre d'accord. c'est un bon exemple pour moi de comprendre ce que shell = True signifie.
user389955
2
La définition de la valeur true de l'argument shell fait que le sous-processus génère un processus shell intermédiaire et lui dit d'exécuter la commande Oh mon dieu, cela dit tout. Pourquoi cette réponse n'est pas acceptée ??? Pourquoi?
pouya
Je pense que le problème est le premier argument à appeler est une liste, pas une chaîne, mais qui donne l'erreur si le shell est faux. Changer la commande en liste fera que cela fonctionne
Lincoln Randall McFarland
Désolé mon commentaire précédent est allé avant que j'aie fini. Pour être clair: je vois souvent l'utilisation du sous-processus avec shell = True et la commande est une chaîne, par exemple 'ls -l', (je m'attends à éviter cette erreur) mais le sous-processus prend une liste (et une chaîne comme une liste à un élément) . Pour exécuter sans invoquer un shell (et les problèmes de sécurité avec cela ), utilisez une liste subprocess.call (['ls', '-l'])
Lincoln Randall McFarland
42

Un exemple où les choses pourraient mal se passer avec Shell = True est montré ici

>>> 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 la doc ici: subprocess.call ()

Richeek
la source
6
Le lien est très utile. Comme l'indique le lien: L' exécution de commandes shell qui intègrent des entrées non autorisées à partir d'une source non fiable rend un programme vulnérable à l'injection shell, une faille de sécurité grave qui peut entraîner l'exécution arbitraire de commandes. Pour cette raison, l'utilisation de shell = True est fortement déconseillée dans les cas où la chaîne de commande est construite à partir d'une entrée externe.
jtuki
39

L'exécution de programmes via le shell signifie que toutes les entrées utilisateur transmises au programme sont interprétées selon la syntaxe et les règles sémantiques du shell invoqué. Au mieux, cela ne cause que des inconvénients à l'utilisateur, car l'utilisateur doit respecter ces règles. Par exemple, les chemins contenant des caractères shell spéciaux comme les guillemets ou les blancs doivent être échappés. Au pire, cela provoque des fuites de sécurité, car l'utilisateur peut exécuter des programmes arbitraires.

shell=Trueest parfois pratique d'utiliser des fonctionnalités de shell spécifiques comme le fractionnement de mots ou l'expansion de paramètres. Cependant, si une telle fonctionnalité est requise, utilisez d'autres modules qui vous sont fournis (par exemple os.path.expandvars()pour l'expansion des paramètres ou shlexpour la division des mots). Cela signifie plus de travail, mais évite d'autres problèmes.

En bref: évitez shell=Truepar tous les moyens.

lunaryorn
la source
16

Les autres réponses ici expliquent de manière adéquate les avertissements de sécurité qui sont également mentionnés dans la subprocessdocumentation. Mais en plus de cela, la surcharge de démarrage d'un shell pour démarrer le programme que vous souhaitez exécuter est souvent inutile et définitivement idiote dans les situations où vous n'utilisez aucune des fonctionnalités du shell. De plus, la complexité cachée supplémentaire devrait vous faire peur, surtout si vous n'êtes pas très familier avec le shell ou les services qu'il fournit.

Lorsque les interactions avec le shell ne sont pas triviales, vous avez maintenant besoin que le lecteur et le responsable du script Python (qui peut ou non être votre futur moi) comprennent à la fois Python et le script shell. Souvenez-vous de la devise Python "explicite vaut mieux qu'implicite"; même lorsque le code Python va être un peu plus complexe que le script shell équivalent (et souvent très laconique), il vaut mieux supprimer le shell et remplacer la fonctionnalité par des constructions Python natives. Minimiser le travail effectué dans un processus externe et garder le contrôle de votre propre code dans la mesure du possible est souvent une bonne idée simplement parce qu'il améliore la visibilité et réduit les risques d'effets secondaires voulus ou indésirables.

L'expansion des caractères génériques, l'interpolation variable et la redirection sont toutes simples à remplacer par des constructions Python natives. Un pipeline de shell complexe où certaines parties ou toutes 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 toujours vous assurer que vous comprenez les implications en termes de performances et de sécurité.

Dans le cas trivial, pour éviter shell=True, remplacez simplement

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 les guillemets et les métacaractères shell échappant les barres obliques inverses ne sont généralement pas nécessaires (ou utiles ou corrects). Peut-être voir aussi Quand mettre des guillemets autour d'une variable shell?

En passant, vous voulez très souvent éviter Popensi l'un des wrappers les plus simples du subprocesspackage fait ce que vous voulez. Si vous avez un Python assez récent, vous devriez probablement l'utiliser subprocess.run.

  • Avec check=Trueelle échouera si la commande que vous avez exécutée a échoué.
  • Avec stdout=subprocess.PIPEelle capturera la sortie de la commande.
  • Un peu obscurément, universal_newlines=Trueil décodera la sortie dans une chaîne Unicode appropriée (c'est juste bytesdans le codage du système sinon, sur Python 3).

Sinon, pour de nombreuses tâches, vous souhaitez check_outputobtenir la sortie d'une commande, tout en vérifiant qu'elle a réussi, ou check_calls'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 shell portable." Même subprocess.run('echo "$HOME"', shell=True)n'est pas portable pour Windows.

tripleee
la source
Je pensais que la citation venait de Larry Wall, mais Google me dit le contraire.
tripleee
C'est un discours élevé - mais aucune suggestion technique pour le remplacement: Me voici, sur OS-X, essayant d'acquérir le pid d'une application Mac que j'ai lancée via 'open': process = subprocess.Popen ('/ usr / bin / pgrep - n '+ app_name, shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE) app_pid, err = process.communicate () --- mais cela ne fonctionne que si j'utilise shell = True. Maintenant quoi?
Motti Shneor
Il y a une tonne de questions sur la façon d'éviter shell=True, beaucoup avec d'excellentes réponses. Il vous est arrivé de choisir celui qui explique pourquoi à la place.
tripleee
@MottiShneor Merci pour la rétroaction; exemple simple ajouté
tripleee