Python subprocess.call () nie działa zgodnie z oczekiwaniami

11

Zacząłem od tej króliczej nory, aby poznać zasady tworzenia skryptu instalacyjnego w Pythonie. Wybór Pythona był po prostu zakorzeniony w mojej znajomości, podczas gdy jestem pewien, że byłyby lepsze alternatywy dla tego zadania.

Celem tego skryptu była instalacja ROS na maszynie z uruchomionym skryptem, a także skonfigurowanie środowiska catkin. Wskazówki można znaleźć tutaj i tutaj , odpowiednio.

Obecny skrypt jest następujący:

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

Gdy skrypt jest aktualnie uruchomiony, pojawia się błąd:

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

Sprawdziłem, czy polecenie działa poprawnie po ręcznym wykonaniu z okna terminala i dlatego uważam, że jest to podstawowe nieporozumienie dotyczące sposobu obsługi tego skryptu i jego zakresu w systemie operacyjnym. Częścią, która sprawia mi wiele zamieszania, jest to, że skarży się, że nie jest w stanie zlokalizować podanego katalogu, podczas gdy sprawdziłem, że ten katalog istnieje. Gdy polecenie jest raczej drukowane z pythona i wklejane do okna terminala, nie występują błędy.

beeedy
źródło
Python ma swójos.chdir()
Jacob Vlijm
1
Jeśli używasz Python 3, po prostu przekaż cwdargument docall
intsco

Odpowiedzi:

18

Domyślnie subprocess.callnie używa powłoki do uruchamiania naszych poleceń, więc nie można wykonywać poleceń powłoki takich jak cd.

Aby użyć powłoki do uruchomienia poleceń, użyj shell=Trueparametru. W takim przypadku zaleca się przekazywanie poleceń jako pojedynczego ciągu, a nie jako listy. Ponieważ jest uruchamiany przez powłokę, której możesz używać ~/na swojej ścieżce:

subprocess.call("(cd ~/catkin_ws/src && catkin_make)", shell=True)
Florian Diesch
źródło
1
Dziękuję Ci! Miałem wrażenie, że wywołanie subprocess.call używało powłoki i nie wiedziałem, że trzeba to wyraźnie zaznaczyć. Powyższe polecenie działało dokładnie zgodnie z przeznaczeniem
beeedy
1
Dlaczego nie użyć os.chdir()?
Jacob Vlijm
3
Jak o subprocess.call(['catkin_make'], cwd=os.path.expanduser('~/catkin_ws/src'))?
Matt Nordhoff,
shell=Truewywoła domyślną powłokę, którą jest myślnik. Jeśli skrypt, który zawiera OP, zawiera bashizmy, może się złamać. Dodałem edycję do mojej odpowiedzi, alternatywnym rozwiązaniem byłoby jawne wywołanie określonej powłoki. Szczególnie przydatne, jeśli ktoś ma do czynienia ze skryptem csh
Sergiy Kolodyazhnyy
1
Najlepszym rozwiązaniem jest sugestia Matta Nordhoffa. Używanie shell=True nawet ze stałymi poleceniami otwiera luki w zabezpieczeniach (np. W systemie podatnym na atak może zostać wyzwolony szok). Zasada praktyczna: jeśli możesz uniknąć używania shell=True, powinieneś tego unikać. Ten cwdparametr służy właśnie do wykonania takiego połączenia, jakiego chce OP.
Bakuriu
5

subprocess.call() oczekuje listy, przy czym pierwszy element jest oczywiście prawidłowym poleceniem powłoki. Porównaj to na przykład:

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

W twoim przypadku subprocess.call(["(cd "+ os.path.expanduser('~') + "/catkin_ws/src)"])spodziewasz się znaleźć plik binarny, który wygląda tak (uwaga: odwrotny ukośnik oznacza znak spacji):

 cd\ /home/user/catkin_ws/src

Jest to traktowane jako pojedyncza nazwa, która powinna mieszkać gdzieś w twoim systemie. To, co naprawdę chciałbyś zrobić, to:

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

Zauważ, że usunąłem nawias wokół przecinka, ponieważ nie ma powodu, aby używać podpowłoki.

EDYCJA :

Jednak w komentarzach progo już wspomniało, że użycie cdw tym przypadku jest zbędne. Odpowiedź Floriana również poprawnie wspomina, że subprocess.call()nie używa powłoki. Możesz podejść do tego na dwa sposoby. Po pierwsze, możesz użyćsubprocess.call("command string",shell=True)

Innym sposobem jest jawne wywołanie konkretnej powłoki. Jest to szczególnie przydatne, jeśli chcesz uruchomić skrypt wymagający określonej powłoki. W ten sposób możesz:

subprocess.call(['bash' , os.path.expanduser('~')  + "/catkin_ws/src"  ) ] )
Sergiy Kolodyazhnyy
źródło
1
call()nie oczekuje prawidłowego polecenia powłoki; oczekuje znalezienia ścieżki do rzeczywistego pliku wykonywalnego. A wywoływanie autonomicznego cdniczego nie osiąga: CWD jest zmienną specyficzną dla procesu, która przestaje istnieć po zakończeniu procesu.
nperson325681
@progo dobry punkt, byłem tak skoncentrowany na edytowaniu polecenia OP, że nawet nie zauważyłem, że cdtutaj nic nie zrobię. . . . Ale jeśli chodzi o „uzasadnione”, nadal uważam, że właściwe jest sformułowanie - jeśli dam subprocess.call()coś, czego nie można znaleźć, na przykład ['ls -l'] , nie będzie to uzasadnione
Sergiy Kolodyazhnyy,
@progo dokonał niewielkiej edycji, proszę przejrzeć
Sergiy Kolodyazhnyy
3

Użyj os.chdir()zamiast tego.

Poza problemami wymienionymi w istniejących odpowiedziach nie wolałbym używać shell=True, ani subprocess.call()tutaj, aby zmieniać katalogu.

Python ma swój własny sposób na zmianę katalogu w os.chdir()(nie zapomnij import os). ~(„dom”) można zdefiniować na kilka sposobów, np os.environ["HOME"].

Powody, dla których wolę to, shell=Truemożesz przeczytać tutaj

Jacob Vlijm
źródło
0

Pamiętaj, że używanie os.chdir()może powodować niezamierzone skutki uboczne, np. Jeśli używasz wielowątkowości . subprocesswszystkie metody dostarczają cwdargument słowa kluczowego, który uruchomi żądany podproces w tym katalogu, bez wpływu na inne części procesu python.

ipetrik
źródło