web-dev-qa-db-fra.com

Signification réelle de 'Shell = True' dans le sous-processus

J'appelle différents processus avec le module subprocess. 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 la documentation, j'ai appris que Shell=True signifie exécuter le code via le shell. Cela signifie donc qu'en l'absence, le processus est lancé directement.

Alors, que devrais-je préférer dans mon cas - je dois exécuter un processus et obtenir son résultat. Quel avantage ai-je à l'appel depuis Shell ou depuis l'extérieur?.

222
user225312

L'avantage de ne pas appeler via le shell, c'est que vous n'invoquez pas de "programme mystère". Sur POSIX, la variable d'environnement Shell contrôle quel binaire est appelé "Shell". Sous Windows, il n’ya pas de descendant de bourne Shell, seulement cmd.exe.

Donc, l'invocation du shell appelle un programme choisi par l'utilisateur et dépend de la plate-forme. En règle générale, évitez les invocations via le shell.

L'appel via le shell vous permet de développer les variables d'environnement et les fichiers globaux selon le mécanisme habituel du shell. Sur les systèmes POSIX, le shell étend les globs de fichiers à une liste de fichiers. Sous Windows, un fichier glob (par exemple, "*. *") N'est pas développé par le shell, de toute façon (mais les variables d'environnement sur une ligne de commande are sont développées par cmd.exe).

Si vous pensez que vous souhaitez développer des variables d'environnement et des fichiers globaux, recherchez les attaques de ILS de 1992-ish sur des services réseau ayant effectué des invocations de sous-programmes via le shell. Les exemples incluent les diverses portes arrière sendmail impliquant ILS.

En résumé, utilisez Shell=False.

155
Heath Hunnicutt
>>> 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 indique d'exécuter la commande. En d'autres termes, l'utilisation d'un shell intermédiaire signifie que les variables, les modèles globaux et les 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 réalité, c'est le cas d'une commande avec développement du shell alors que la commande ls -l est considérée comme une commande simple.

source: module de sous-processus

96
Mina Gabriel

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

Shell=True est parfois pratique d'utiliser des fonctionnalités spécifiques du shell telles que le fractionnement de mots ou le développement de paramètres. Toutefois, si une telle fonctionnalité est requise, d'autres modules vous sont fournis (par exemple, os.path.expandvars() pour l'extension des paramètres ou shlex pour le fractionnement de Word). Cela signifie plus de travail, mais évite d’autres problèmes.

En bref: Évitez Shell=True par tous les moyens.

36
lunaryorn

Un exemple de problème avec Shell = True est présenté 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...

Vérifiez la doc ici: subprocess.call ()

32
Richeek

Les autres réponses ici expliquent de manière adéquate les problèmes de sécurité qui sont également mentionnés dans la documentation subprocess. Mais en plus de cela, les frais généraux liés au démarrage d'un shell pour démarrer le programme que vous souhaitez exécuter sont souvent inutiles et certainement ridicules dans les cas où vous n'utilisez réellement aucune des fonctionnalités du shell. De plus, la complexité cachée supplémentaire devrait vous effrayer , en particulier si vous n'êtes pas très familiarisé avec Shell et les services qu'il fournit.

Le développement de caractères génériques, l'interpolation de variable et la redirection sont faciles à remplacer par les constructions natives Python. Un pipeline Shell complexe dans lequel une partie ou la totalité ne peut pas être réécrite de manière raisonnable en Python serait le seul cas où vous pourriez peut-être envisager d'utiliser le shell. Vous devez néanmoins vous assurer de bien comprendre les conséquences 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'])
_

Notez que le premier argument est une liste de chaînes à transmettre à execvp(), et qu'il est généralement inutile de citer des chaînes et des métacaractères Shell utilisant une barre oblique inversée (ni utile, ni correct).

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

  • Avec _check=True_, cela échouera si la commande que vous avez exécutée a échoué.
  • Avec _stdout=subprocess.PIPE_, il va capturer la sortie de la commande.
  • Un peu obscurément, avec _universal_newlines=True_, il décodera la sortie en une chaîne Unicode appropriée (sinon, bytes dans le codage du système sinon, sur Python 3).

Sinon, pour de nombreuses tâches, vous souhaitez check_output obtenir le résultat d'une commande, tout en vérifiant qu'elle a réussi, ou check_call si 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 vers Windows.

15
tripleee