python subprocess.call () non funziona come previsto


11

Ho iniziato questa tana di coniglio come mezzo per familiarizzare con come si sarebbe potuto creare uno script di installazione in Python. La scelta di Python era semplicemente radicata nella mia familiarità con esso mentre sono sicuro che ci sarebbero alternative migliori di Python per questo compito.

L'obiettivo di questo script era installare ROS sul computer che esegue lo script e anche impostare l'ambiente catkin. Le indicazioni possono essere trovate qui e qui , rispettivamente.

Lo script così com'è attualmente è il seguente:

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

Quando lo script è attualmente in esecuzione, si esaurisce con l'errore:

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

Ho verificato che il comando funzioni correttamente quando eseguito manualmente da una finestra del terminale e, pertanto, credo che questo sia un malinteso fondamentale su come questo script e il suo ambito vengono gestiti all'interno del sistema operativo. La parte che mi sta causando molta confusione è il motivo per cui si lamenta che non è in grado di individuare la directory fornita, mentre ho verificato che questa directory esiste. Quando il comando viene piuttosto stampato da Python e incollato in una finestra del terminale, non si riscontrano errori.


Python ha il suoos.chdir()
Jacob Vlijm il

1
Se stai usando Python 3, passa l' cwdargomento acall
intsco il

Risposte:


18

Di default subprocess.callnon usa una shell per eseguire i nostri comandi, quindi non è possibile eseguire comandi di shell simili cd.

Per usare una shell per eseguire i tuoi comandi usa shell=Truecome parametro. In tal caso, si consiglia di passare i comandi come una singola stringa anziché come un elenco. E poiché è gestito da una shell puoi usare anche ~/nel tuo percorso:

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

1
Grazie! Avevo l'impressione che subprocess.call usasse una shell e non sapevo che dovesse essere esplicitamente dichiarato. Il comando sopra ha funzionato esattamente come previsto
beeedy il

1
Perché non usare os.chdir()?
Jacob Vlijm,

3
Che ne dici subprocess.call(['catkin_make'], cwd=os.path.expanduser('~/catkin_ws/src'))?
Matt Nordhoff,

shell=Truechiamerà shell predefinita, che è trattino. Se uno script in cui OP contiene bashismi potrebbe rompersi. Ho aggiunto modifica alla mia risposta, la soluzione alternativa sarebbe quella di chiamare esplicitamente shell specifica. Particolarmente utile se qualcuno ha a che fare con la sceneggiatura di csh
Sergiy Kolodyazhnyy il

1
La soluzione migliore è il suggerimento di Matt Nordhoff. L'uso shell=True anche con comandi fissi apre vulnerabilità di sicurezza (ad esempio shellshock potrebbe essere attivato su un sistema vulnerabile). La regola empirica: se riesci a evitare di usarlo shell=True, dovresti evitarlo. Il cwdparametro è esattamente lì per fare il tipo di chiamata che l'OP vuole.
Bakuriu,

5

subprocess.call() si aspetta un elenco, con il primo elemento ovviamente un comando shell legittimo. Confronta questo per esempio:

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

Nel tuo caso, subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws/src)"])ci aspettiamo di trovare un binario che assomigli a questo (nota la barra rovescia che designa il carattere spaziale):

 cd\ /home/user/catkin_ws/src

Questo è trattato come un singolo nome che ci si aspetta che risieda da qualche parte nel sistema. Quello che vorresti davvero fare è:

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

Si noti che ho rimosso le parentesi attorno alla virgola, poiché non vi è alcun motivo per utilizzare subshell.

MODIFICA :

Ma è già stato citato da Progo nei commenti che l'utilizzo cdin questo caso è ridondante. La risposta di Florian menziona anche correttamente che subprocess.call()non usa la shell. Potresti affrontarlo in due modi. Uno, potresti usaresubprocess.call("command string",shell=True)

L'altro modo, è quello di chiamare esplicitamente shell specifica. Ciò è particolarmente utile se si desidera eseguire uno script che richiede una shell specifica. Quindi potresti fare:

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

1
call()non prevede un comando shell legittimo; si aspetta di trovare un percorso per un vero eseguibile. E chiamare un standalone cdnon ottiene nulla: il CWD è una variabile specifica del processo che cessa di esistere una volta terminato il processo.
nperson325681

@progo buon punto, ero così concentrato sulla modifica del comando di OP che non mi ero nemmeno accorto che cdqui non avrei fatto nulla. . . . Ma per quanto riguarda "legittimo", credo che sia ancora appropriato il fraseggio - se do subprocess.call()qualcosa che non riesce a trovare, ['ls -l'] non sarà legittimo
Sergiy Kolodyazhnyy,

@progo ha apportato una piccola modifica, si prega di rivedere
Sergiy Kolodyazhnyy il

3

Usa os.chdir()invece.

A parte i problemi, citati nelle risposte esistenti, non preferirei utilizzare shell=Truesubprocess.call()qui per cambiare directory.

Python ha il suo modo di cambiare directory os.chdir()(non dimenticare di farlo import os). ~("home") può essere definito in diversi modi, ao os.environ["HOME"].

I motivi per preferire questo shell=Truepossono essere letti qui


0

Si noti che l'utilizzo os.chdir()può causare effetti collaterali indesiderati, ad esempio se si utilizza il multithreading . subprocesstutti i metodi forniscono un cwdargomento di parole chiave che eseguirà il sottoprocesso richiesto in quella directory, senza influenzare altre parti del processo di Python.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.