2012-12-31 14 views
14

Mam zorientowali się, jak korzystać z call(), aby mój skrypt Pythona uruchomić polecenie:Python, podproces, call(), check_call i returncode znaleźć jeśli komenda istnieje

import subprocess 

mycommandline = ['lumberjack', '-sleep all night', '-work all day'] 
subprocess.call(mycommandline) 

to działa ale jest problem, co jeśli użytkownicy nie mają drwala w swojej ścieżce poleceń? To działałoby, gdyby drwala została umieszczona w tym samym katalogu, co skrypt Pythona, ale skąd skrypt wie, że powinien szukać drwala? Pomyślałem, że jeśli wystąpił błąd związany z poleceniem, to drwal nie znalazłby się na ścieżce poleceń, skrypt mógłby spróbować dowiedzieć się, co to jest jego katalog i szukać tam drwala, a na koniec ostrzec użytkownika, by skopiował drwala do jednego z te dwa miejsca, jeśli nie znaleziono ich w żadnej z nich. Jak mogę się dowiedzieć, jaki jest komunikat o błędzie? Czytałem, że funkcja check_call() może zwrócić komunikat o błędzie i coś o atrybucie kodu powrotu. Nie mogłem znaleźć przykładów, jak używać check_call() i kodu powrotu, jaka byłaby wiadomość lub jak mogłem stwierdzić, czy komunikat nie jest znaleziony.

Czy nawet robię to w odpowiedni sposób?

+2

'check_call()' powinien wywołać błąd, jeśli polecenie nie kończy się w sposób czysty (co jest oczekiwane od nieistniejących poleceń). – inspectorG4dget

+3

Polecam także, aby nie mówić użytkownikowi, aby kopiował pliki wykonywalne, z wyjątkiem w ostateczności . Menedżerowie pakietów są prawie zawsze najlepszym rozwiązaniem do instalowania rzeczy. –

+1

@DaveBrunker: Wydajesz się nieco zdezorientowany w kwestii 'call' vs.' check_call'. 'call' już daje kod powrotu - nie jako atrybut, podobnie jak wartość zwracana przez funkcję. Powodem użycia 'check_call' jest to, że dba o sprawdzenie kodu powrotu dla ciebie, podnosząc' CalledProcessError'. Każdy z nich podnosi "OSError" (lub podobny, w zależności od wersji Pythona), aby program nie został znaleziony. – abarnert

Odpowiedz

2

Wow, to było szybkie!I połączeniu prosty przykład Theodros Zelleke oraz korzystanie steveha za funkcje z abarnert komentarz na temat OSError and Lattyware w komentarzu o przenoszeniu plików:

import os, sys, subprocess 

def nameandpath(): 
    try: 
     subprocess.call([os.getcwd() + '/lumberjack']) 
     # change the word lumberjack on the line above to get an error 
    except OSError: 
     print('\nCould not find lumberjack, please reinstall.\n') 
     # if you're using python 2.x, change the() to spaces on the line above 

try: 
    subprocess.call(['lumberjack']) 
    # change the word lumberjack on the line above to get an error 
except OSError: 
    nameandpath() 

Testowałem go na Mac OS-X (6,8/Snow Leopard), Debian (Squeeze) i System Windows 7). Wydawało się działać tak, jak chciałem, na wszystkich trzech systemach operacyjnych. Próbowałem używać check_call i CalledProcessError, ale bez względu na to, co zrobiłem, wydawało mi się, że za każdym razem otrzymuję błąd i nie mogłem uzyskać skryptu, który poradziłby sobie z błędami. Aby przetestować skrypt, zmieniłem nazwę z "lumberjack" na "deadparrot", ponieważ miałem drwal w katalogu z moim skryptem.

Czy widzisz jakieś problemy z tym skryptem w sposób napisany?

4

subprocess spowoduje zgłoszenie wyjątku, OSError, gdy polecenie nie zostanie znalezione.

Po znalezieniu polecenia i subprocess uruchamia polecenie dla użytkownika, kod wyniku jest zwracany z polecenia. Standardem jest, że kod 0 oznacza sukces, a każda awaria to niezerowy kod błędu (który jest różny, sprawdź dokumentację dla konkretnego polecenia, które uruchamiasz).

Tak więc, jeśli złapiesz OSError, możesz obsłużyć nieistniejące polecenie, a jeśli sprawdzisz kod wyniku, możesz sprawdzić, czy polecenie się powiodło, czy nie.

Wspaniałą rzeczą subprocess to, że można zrobić to zebrać cały tekst z stdout i stderr, a następnie można go wyrzucić lub zwrócić je lub zaloguj się lub wyświetlić go jak chcesz. Często używam wrappera, który odrzuca wszystkie dane wyjściowe z polecenia, chyba że polecenie nie powiedzie się, w takim przypadku wyprowadzany jest tekst z stderr.

Zgadzam się, że nie należy prosić użytkowników o kopiowanie plików wykonywalnych w pobliżu. Programy powinny znajdować się w katalogu wymienionym w zmiennej PATH; jeśli brakuje jakiegoś programu, powinien on zostać zainstalowany lub jeśli jest zainstalowany w katalogu innym niż PATH, użytkownik powinien zaktualizować PATH, aby uwzględnić ten katalog.

Pamiętaj, że masz możliwość próbuje subprocess wielokrotnie z różnymi zakodowane ścieżek do plików wykonywalnych:

import os 
import subprocess as sp 

def _run_cmd(s_cmd, tup_args): 
    lst_cmd = [s_cmd] 
    lst_cmd.extend(tup_args) 
    result = sp.call(lst_cmd) 
    return result 

def run_lumberjack(*tup_args): 
    try: 
     # try to run from /usr/local/bin 
     return _run_cmd("/usr/local/bin/lumberjack", tup_args) 
    except OSError: 
     pass 

    try: 
     # try to run from /opt/forest/bin 
     return _run_cmd("/opt/forest/bin/lumberjack", tup_args) 
    except OSError: 
     pass 

    try: 
     # try to run from "bin" directory in user's home directory 
     home = os.getenv("HOME", ".") 
     s_cmd = home + "/bin/lumberjack" 
     return _run_cmd(s_cmd, tup_args) 
    except OSError: 
     pass 

    # Python 3.x syntax for raising an exception 
    # for Python 2.x, use: raise OSError, "could not find lumberjack in the standard places" 
    raise OSError("could not find lumberjack in the standard places") 

run_lumberjack("-j") 

EDIT: Po myślenie o nim trochę, postanowiłem całkowicie przepisać powyżej. O wiele łatwiej jest po prostu przesłać listę lokalizacji i wypróbować alternatywne lokalizacje, dopóki nie zadziała. Ale nie chciałem budować łańcucha dla katalogu domowego użytkownika, jeśli nie był on potrzebny, więc po prostu legalnie wprowadziłem listę do alternatywy. Jeśli masz jakieś pytania na ten temat, po prostu zapytaj.

import os 
import subprocess as sp 

def try_alternatives(cmd, locations, args): 
    """ 
    Try to run a command that might be in any one of multiple locations. 

    Takes a single string argument for the command to run, a sequence 
    of locations, and a sequence of arguments to the command. Tries 
    to run the command in each location, in order, until the command 
    is found (does not raise OSError on the attempt). 
    """ 
    # build a list to pass to subprocess 
    lst_cmd = [None] # dummy arg to reserve position 0 in the list 
    lst_cmd.extend(args) # arguments come after position 0 

    for path in locations: 
     # It's legal to put a callable in the list of locations. 
     # When this happens, we should call it and use its return 
     # value for the path. It should always return a string. 
     if callable(path): 
      path = path() 

     # put full pathname of cmd into position 0 of list  
     lst_cmd[0] = os.path.join(path, cmd) 
     try: 
      return sp.call(lst_cmd) 
     except OSError: 
      pass 
    raise OSError('command "{}" not found in locations list'.format(cmd)) 

def _home_bin(): 
    home = os.getenv("HOME", ".") 
    return os.path.join(home, "bin") 

def run_lumberjack(*args): 
    locations = [ 
     "/usr/local/bin", 
     "/opt/forest/bin", 
     _home_bin, # specify callable that returns user's home directory 
    ] 
    return try_alternatives("lumberjack", locations, args) 

run_lumberjack("-j") 
19

prosty fragment:

try: 
    subprocess.check_call(['executable']) 
except subprocess.CalledProcessError: 
    pass # handle errors in the called executable 
except OSError: 
    pass # executable not found