python subprocess.call () ne fonctionne pas comme prévu


11

J'ai commencé dans ce trou de lapin comme un moyen de me familiariser avec la façon de créer un script de configuration en python. Le choix de python était simplement enraciné dans ma familiarité avec lui alors que je suis sûr qu'il y aurait de meilleures alternatives que python pour cette tâche.

Le but de ce script était d'installer ROS sur la machine exécutant le script et également de configurer l'environnement catkin. Les directions peuvent être trouvées ici et ici , respectivement.

Le script tel qu'il se trouve actuellement est le suivant:

subprocess.call(["sudo", "sh", "-c", "'echo \"deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main\" > /etc/apt/sources.list.d/ros-latest.list'"])
subprocess.call(["sudo", "apt-key", "adv", "--keyserver", "hkp://ha.pool.sks-keyserver.net:80", "--recv-key", "0xB01FA116"])
subprocess.call(["sudo", "apt-get", "update"])
subprocess.call(["sudo", "apt-get", "install", "ros-kinetic-desktop-full", "-y"])
subprocess.call(["sudo", "rosdep", "init"])
subprocess.call(["rosdep", "update"])
subprocess.call(["echo", '"source /opt/ros/kinetic/setup.bash"', ">>", "~/.bashrc", "source", "~/.bashrc"])
subprocess.call(["sudo", "apt-get", "install", "python-rosinstall", "-y"])
mkdir_p(os.path.expanduser('~') + "/catkin_ws/src")
subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws/src)"])
subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws && catkin_make)"])
subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws && source devel/setup.bash"])

Lorsque le script est actuellement exécuté, il génère une erreur avec l'erreur:

Traceback (most recent call last):
  File "setup.py", line 46, in <module>
    subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws/src)"])
  File "/usr/lib/python2.7/subprocess.py", line 523, in call
    return Popen(*popenargs, **kwargs).wait()
  File "/usr/lib/python2.7/subprocess.py", line 711, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1343, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

J'ai vérifié que la commande fonctionne correctement lorsqu'elle est exécutée manuellement à partir d'une fenêtre de terminal, et en tant que telle, je pense que c'est un malentendu fondamental sur la façon dont ce script et sa portée sont traités dans le système d'exploitation. La partie qui me cause beaucoup de confusion est la raison pour laquelle il se plaint de ne pas pouvoir localiser le répertoire fourni, alors que j'ai vérifié que ce répertoire existe. Lorsque la commande est plutôt imprimée à partir de python et collée dans une fenêtre de terminal, aucune erreur n'est rencontrée.


Python a son propreos.chdir()
Jacob Vlijm

1
Si vous utilisez Python 3, passez simplement l' cwdargument àcall
intsco

Réponses:


18

Par défaut, subprocess.calln'utilise pas de shell pour exécuter nos commandes, vous ne pouvez donc pas shell comme cd.

Pour utiliser un shell pour exécuter vos commandes, utilisez shell=Truecomme paramètre. Dans ce cas, il est recommandé de passer vos commandes comme une chaîne unique plutôt que comme une liste. Et comme il est géré par un shell, vous pouvez également l'utiliser ~/dans votre chemin:

subprocess.call("(cd ~/catkin_ws/src && catkin_make)", shell=True)

1
Je vous remercie! J'avais l'impression que subprocess.call utilisait un shell, et je ne savais pas qu'il devait être explicitement déclaré. La commande ci-dessus a fonctionné exactement comme prévu
beeedy

1
Pourquoi ne pas utiliser os.chdir()?
Jacob Vlijm

3
Et alors subprocess.call(['catkin_make'], cwd=os.path.expanduser('~/catkin_ws/src'))?
Matt Nordhoff du

shell=Trueappellera le shell par défaut, qui est un tiret. Si un script qui OP contient des bashismes, il peut se casser. J'ai ajouté la modification à ma réponse, une solution alternative serait d'appeler explicitement un shell spécifique. Particulièrement utile si quelqu'un a affaire au script csh
Sergiy Kolodyazhnyy

1
La meilleure solution est la suggestion de Matt Nordhoff. L'utilisation shell=True même avec des commandes fixes ouvre des failles de sécurité (par exemple, shellshock pourrait être déclenché sur un système vulnérable). La règle de base: si vous pouvez éviter d' utiliser , shell=Truevous devez éviter. Le cwdparamètre est là pour faire exactement le type d'appel que l'OP veut.
Bakuriu

5

subprocess.call() attend une liste, le premier élément étant évidemment une commande shell légitime. Comparez cela par exemple:

>>> subprocess.call(['echo hello'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/subprocess.py", line 523, in call
    return Popen(*popenargs, **kwargs).wait()
  File "/usr/lib/python2.7/subprocess.py", line 711, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1343, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory
>>> subprocess.call(['echo', 'hello'])
hello
0

Dans votre cas, subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws/src)"])attendez-vous à trouver un binaire qui ressemble à cela (notez la barre oblique inverse désignant le caractère de l'espace):

 cd\ /home/user/catkin_ws/src

Cela est traité comme un seul nom qui doit résider quelque part sur votre système. Ce que vous voudriez vraiment faire, c'est:

 subprocess.call(["cd", os.path.expanduser('~') + "/catkin_ws/src"])

Notez que j'ai supprimé les parenthèses autour de la virgule, car il n'y a aucune raison d'utiliser le sous-shell.

MODIFIER :

Mais il a déjà été mentionné par progo dans les commentaires que l'utilisation cddans ce cas est redondante. La réponse de Florian mentionne également correctement qu'il subprocess.call()n'utilise pas de shell. Vous pouvez aborder cela de deux manières. Un, vous pouvez utilisersubprocess.call("command string",shell=True)

L'autre façon consiste à appeler explicitement un shell spécifique. Ceci est particulièrement utile si vous souhaitez exécuter un script qui nécessite un shell spécifique. Ainsi, vous pourriez faire:

subprocess.call(['bash' , os.path.expanduser('~')  + "/catkin_ws/src"  ) ] )

1
call()ne s'attend pas à une commande shell légitime; il s'attend à trouver un chemin vers un exécutable réel. Et appeler un autonome cdne sert à rien: le CWD est une variable spécifique au processus qui cesse d'exister une fois le processus terminé.
nperson325681

@progo bon point, j'étais tellement concentré sur l'édition de la commande OP que je n'ai même pas remarqué que cdcela ne ferait rien ici. . . . Mais en ce qui concerne "légitime", c'est toujours une formulation appropriée, je crois - si je donne subprocess.call()quelque chose qu'il ne peut pas trouver, comme ['ls -l'] , ce ne sera pas légitime
Sergiy Kolodyazhnyy

@progo a fait un petit montage, veuillez réviser
Sergiy Kolodyazhnyy

3

Utilisez os.chdir()plutôt.

Mis à part les problèmes mentionnés dans les réponses existantes, je ne préférerais pas utiliser shell=Trueni subprocess.call()ici pour changer de répertoire.

Python a sa propre façon de changer de répertoire os.chdir()(n'oubliez pas import os). ~("domicile") peut être défini de plusieurs manières, entre autres os.environ["HOME"].

Les raisons de préférer cela shell=Truepeuvent être lues ici


0

Notez que l'utilisation os.chdir()peut provoquer des effets secondaires involontaires, par exemple si vous utilisez le multithreading . subprocesstoutes les méthodes fournissent un cwdargument de mot clé qui exécutera le sous-processus demandé dans ce répertoire, sans affecter les autres parties de votre processus python.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.