python subprocess.call () no funciona como se esperaba

11

Comencé por este agujero del conejo como un medio para familiarizarme con cómo se crearía un script de configuración en Python. La elección de Python simplemente se basó en mi familiaridad con él, mientras que estoy seguro de que habría mejores alternativas que Python para esta tarea.

El objetivo de este script era instalar ROS en la máquina que ejecuta el script y también configurar el entorno catkin. Las instrucciones se pueden encontrar aquí y aquí , respectivamente.

El script tal como está actualmente es el siguiente:

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"])

Cuando la secuencia de comandos se ejecuta actualmente, se produce un error con el error:

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

He verificado que el comando funciona correctamente cuando se ejecuta manualmente desde una ventana de terminal y, como tal, creo que este es un malentendido fundamental sobre cómo se maneja este script y su alcance dentro del sistema operativo. La parte que me está causando mucha confusión es por qué se queja de que no puede localizar el directorio proporcionado, mientras que he verificado que este directorio existe. Cuando el comando se imprime desde Python y se pega en una ventana de terminal, no se encuentran errores.

Beeedy
fuente
Python tiene su propioos.chdir()
Jacob Vlijm
1
Si está utilizando Python 3, simplemente pase el cwdargumento acall
intsco el

Respuestas:

18

Por defecto subprocess.call, no utiliza un shell para ejecutar nuestros comandos, por lo que no puede ejecutar comandos como cd.

Para usar un shell para ejecutar sus comandos, use shell=Truecomo parámetro. En ese caso, se recomienda pasar los comandos como una sola cadena en lugar de como una lista. Y como lo ejecuta un shell, también puede usarlo ~/en su camino:

subprocess.call("(cd ~/catkin_ws/src && catkin_make)", shell=True)
Florian Diesch
fuente
1
¡Gracias! Tenía la impresión de que subprocess.call usaba un shell, y no sabía que tenía que declararse explícitamente. El comando anterior funcionó exactamente como se pretende
beeedy
1
¿Por qué no usar os.chdir()?
Jacob Vlijm
3
¿Qué tal subprocess.call(['catkin_make'], cwd=os.path.expanduser('~/catkin_ws/src'))?
Matt Nordhoff
shell=Truellamará al shell predeterminado, que es guión. Si un script que OP contiene bashismos, puede romperse. He agregado editar a mi respuesta, la solución alternativa sería llamar explícitamente a un shell específico. Especialmente útil si alguien está tratando con un script csh
Sergiy Kolodyazhnyy
1
La mejor solución es la sugerencia de Matt Nordhoff. El uso shell=True incluso con comandos fijos abre vulnerabilidades de seguridad (por ejemplo, el shellshock podría activarse en un sistema vulnerable). La regla general: si puede evitar el uso shell=True, debe evitarlo. El cwdparámetro está ahí exactamente para hacer el tipo de llamada que quiere el OP.
Bakuriu
5

subprocess.call() espera una lista, con el primer elemento obviamente siendo un comando de shell legítimo. Compare esto por ejemplo:

>>> 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

En su caso, subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws/src)"])esperará encontrar binarios que se vean así (observe la barra diagonal inversa que designa el carácter espacial):

 cd\ /home/user/catkin_ws/src

Eso se trata como un nombre único que se espera que viva en algún lugar de su sistema. Lo que realmente quieres hacer es:

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

Tenga en cuenta que he eliminado el paréntesis alrededor de la coma, ya que no hay razón para usar subshell.

EDITAR :

Pero ya ha sido mencionado por progo en los comentarios que usar cden este caso es redundante. La respuesta de Florian también menciona correctamente que subprocess.call()no usa shell. Podrías abordar eso de dos maneras. Uno, podrías usarsubprocess.call("command string",shell=True)

La otra forma es llamar explícitamente a un shell específico. Esto es especialmente útil si desea ejecutar un script que requiere un shell específico. Así podrías hacer:

subprocess.call(['bash' , os.path.expanduser('~')  + "/catkin_ws/src"  ) ] )
Sergiy Kolodyazhnyy
fuente
1
call()no espera un comando de shell legítimo; espera encontrar una ruta a un ejecutable real. Y llamar a un independiente cdno logra nada: el CWD es una variable específica del proceso que deja de existir una vez que el proceso finaliza.
nperson325681
@progo buen punto, estaba tan concentrado en editar el comando de OP que ni siquiera me di cuenta de que cdno haría nada aquí. . . . Pero en cuanto a "legítimo", creo que es una fraseología apropiada: si doy subprocess.call()algo que no puede encontrar, como ['ls -l'] , no será legítimo
Sergiy Kolodyazhnyy
@progo hizo una pequeña edición, por favor revise
Sergiy Kolodyazhnyy
3

Usar en su os.chdir()lugar.

Aparte de los problemas, mencionados en las respuestas existentes, no preferiría usar shell=True, ni subprocess.call()aquí, para cambiar el directorio.

Python tiene su propia forma de cambiar el directorio os.chdir()(no se olvide import os). ~("hogar") se puede definir de varias maneras, entre otras os.environ["HOME"].

Las razones para preferir eso shell=Truese pueden leer aquí.

Jacob Vlijm
fuente
0

Tenga en cuenta que el uso os.chdir()puede causar efectos secundarios no deseados, por ejemplo, si está usando subprocesos múltiples . subprocessTodos los métodos proporcionan un cwdargumento de palabra clave que ejecutará el subproceso solicitado en ese directorio, sin afectar otras partes de su proceso de Python.

ipetrik
fuente