python subprocess.call () não está funcionando como esperado

11

Comecei nesta toca de coelho como um meio de me familiarizar com como alguém criaria um script de instalação em python. A escolha do python estava simplesmente enraizada na minha familiaridade com ele, enquanto tenho certeza de que haveria alternativas melhores do que o python para esta tarefa.

O objetivo desse script era instalar o ROS na máquina executando o script e também configurar o ambiente catkin. As instruções podem ser encontradas aqui e aqui , respectivamente.

O script como está atualmente é o seguinte:

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 o script está sendo executado no momento, ocorre um erro com o erro:

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

Eu verifiquei que o comando funciona corretamente quando executado manualmente a partir de uma janela de terminal e, como tal, acredito que seja um mal-entendido fundamental sobre como esse script e seu escopo são tratados no sistema operacional. A parte que está me causando muita confusão é a razão pela qual reclama que não conseguiu localizar o diretório fornecido, enquanto eu verifiquei que esse diretório existe. Quando o comando é impresso em python e colado em uma janela de terminal, nenhum erro é encontrado.

beeedy
fonte
Python tem o seu próprioos.chdir()
Jacob Vlijm
1
Se você estiver usando Python 3, basta passar o cwdargumento paracall
intsco

Respostas:

18

Por padrão subprocess.call, não usa um shell para executar nossos comandos, então você não pode usar comandos como cd.

Para usar um shell para executar seus comandos, use shell=Truecomo parâmetro. Nesse caso, é recomendável passar seus comandos como uma única sequência e não como uma lista. E como é executado por um shell, você também pode usar ~/no seu caminho:

subprocess.call("(cd ~/catkin_ws/src && catkin_make)", shell=True)
Florian Diesch
fonte
1
Obrigado! Fiquei com a impressão de que subprocess.call usava um shell e não sabia que tinha que ser explicitamente declarado. O comando acima funcionou exatamente como planejado
beeedy
1
Por que não usar os.chdir()?
22616 Jacob Vlijm
3
Que tal subprocess.call(['catkin_make'], cwd=os.path.expanduser('~/catkin_ws/src'))?
22616 Matt Mthoff
shell=Truechamará shell padrão, que é dash. Se um script que OP contém bashisms, pode quebrar. Adicionei edição à minha resposta, a solução alternativa seria chamar explicitamente o shell específico. Especialmente útil se alguém está lidando com o script csh
Sergiy Kolodyazhnyy
1
A melhor solução é a sugestão de Matt Nordhoff. Usar shell=True até mesmo com comandos fixos abre vulnerabilidades de segurança (por exemplo, o shellshock pode ser acionado em um sistema vulnerável). A regra de ouro: se você pode evitar o uso shell=Trueque você deve evitá-lo. O cwdparâmetro está lá exatamente para fazer o tipo de chamada que o OP deseja.
Bakuriu 22/07/2016
5

subprocess.call() espera uma lista, com o primeiro item obviamente sendo um comando de shell legítimo. Compare isso, por exemplo:

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

No seu caso, subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws/src)"])você espera encontrar o binário com a aparência semelhante (observe a barra invertida designando o caractere de espaço):

 cd\ /home/user/catkin_ws/src

Isso é tratado como um nome único que se espera que esteja em algum lugar do sistema. O que você realmente gostaria de fazer é:

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

Observe que removi os parênteses ao redor da vírgula, pois não há motivo para usar o subshell.

EDIT :

Mas já foi mencionado por progo nos comentários que a utilização cdneste caso é redundante. A resposta de Florian também menciona corretamente que subprocess.call()não usa shell. Você pode abordar isso de duas maneiras. Um, você poderia usarsubprocess.call("command string",shell=True)

A outra maneira, é chamar shell específico explicitamente. Isso é especialmente útil se você deseja executar um script que requer shell específico. Assim, você poderia fazer:

subprocess.call(['bash' , os.path.expanduser('~')  + "/catkin_ws/src"  ) ] )
Sergiy Kolodyazhnyy
fonte
1
call()não espera um comando legítimo do shell; espera encontrar um caminho para um executável real. E chamar um autônomo cdnão alcança nada: o CWD é uma variável específica do processo que deixa de existir quando o processo termina.
N26325681
@progo bom ponto, eu estava tão focado em editar o comando do OP que nem percebi que cdnão faria nada aqui. . . . Mas, como para "legítima", ainda é fraseado apropriado Eu acredito - se eu dar subprocess.call()algo que não pode encontrar, como ['ls -l'] , não será legítimo
Sergiy Kolodyazhnyy
@progo fez uma pequena edição, por favor revise #
Sergiy Kolodyazhnyy
3

Use em os.chdir()vez disso.

Além dos problemas mencionados nas respostas existentes, eu não preferiria usar shell=Truenem subprocess.call()aqui para alterar o diretório.

O Python tem sua própria maneira de alterar o diretório os.chdir()(não esqueça import os). ~("home") pode ser definido de várias maneiras, ao os.environ["HOME"].

Razões para preferir que ao longo shell=Truepodem ser lidas aqui

Jacob Vlijm
fonte
0

Observe que o uso os.chdir()pode causar efeitos colaterais indesejados, por exemplo, se você estiver usando multithreading . subprocessTodos os métodos fornecem um cwdargumento de palavra - chave que executará o subprocesso solicitado nesse diretório, sem afetar outras partes do seu processo python.

ipetrik
fonte