Zanurkuj w Pythonie/Obsługa argumentów linii poleceń

Obsługa argumentów linii poleceń

edytuj

Python całkowicie wspomaga tworzenie programów, które mogą zostać uruchomione z linii poleceń, łącznie z argumentami linii poleceń, czy zarówno z krótkim lub długim stylem flag, które określają opcje. Nie ma to nic wspólnego z XML-em, ale omawiany skrypt wykorzystuje w dobry sposób linię poleceń, dlatego też nadeszła odpowiednia pora, aby o nich wspomnieć.

Ciężko mówić o linii poleceń bez wiedzy, w jaki sposób argumenty linii poleceń są ujawniane do programu, dlatego też napiszmy prosty program, aby to zobaczyć.

Przykład. Wprowadzenie do sys.argv
#argecho.py
import sys

for arg in sys.argv: #(1)
    print arg
  1. Każdy argument linii poleceń przekazany do programu, zostanie umieszczony w sys.argv, który jest właściwie listą. W tym miejscu wypisujemy każdy argument w oddzielnej linii.
Przykład. Zawartość sys.argv
[you@localhost py]$ python argecho.py          #(1)
argecho.py
[you@localhost py]$ python argecho.py abc def  #(2)
argecho.py
abc
def
[you@localhost py]$ python argecho.py −−help   #(3)
argecho.py
−−help
[you@localhost py]$ python argecho.py −m kant.xml #(4)
argecho.py
−m
kant.xml
  1. Najpierw musimy sobie uświadomić, że sys.argv przechowuje nazwę uruchomionego skryptu. Wiedzę tę wykorzystamy później, w rozdziale "Programowanie funkcyjne". Na razie nie zamartwiaj się tym.
  2. Argumenty linii poleceń są oddzielane przez spacje i każdy z nich ukazuje się w liście sys.argv jako oddzielny argument.
  3. Flagi linii poleceń np. −−help, także pokażą się jako osobne elementy w sys.argv.
  4. Żeby było ciekawiej, niektóre z flag linii poleceń same przyjmują, wymagają argumentów. Na przykład, tutaj mamy jedną flagę (−m), która na dodatek także przyjmuje argument (w przykładzie kant.xml). Zarówno flaga sama w sobie, a także argument flagi są kolejnymi elementami w liście sys.argv. Python w żaden sposób nie będzie próbował ich powiązać; otrzymamy samą listę.

Jak możemy zobaczyć, z pewnością mamy wszystkie informacje przekazane do linii poleceń, ale nie wyglądają na tak proste, aby z nich faktycznie skorzystać. Dla nieskomplikowanych programów, które przyjmują tylko jeden argument bez żadnych flag, możemy po prostu wykorzystać sys.argv[1], aby się do niego dostać. Nie ma się czego wstydzić. Dla bardziej złożonych programów będzie potrzebny moduł getopt.

Przykład. Wprowadzenie do getopt
def main(argv):
    grammar = "kant.xml"                 #(1)
    try:
        opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="]) #(2)
    except getopt.GetoptError:           #(3)
        usage()                          #(4)
        sys.exit(2)

# ...
 
if __name__ == "__main__":
     main(sys.argv[1:])
  1. Od razu zobacz na sam dół przykładu. Wywołujemy funkcję main z argumentem sys.argv[1:]. Zapamiętaj, sys.argv[0] jest nazwą skryptu, który uruchomiliśmy; nie martwimy się o to, w jaki sposób jest przetwarzana linia poleceń, więc odcinamy i przekazujemy resztę listy.
  2. To najciekawsze miejsce w tym przykładzie. Funkcja getopt modułu getopt przyjmuje trzy parametry: listę argumentów (którą otrzymaliśmy z sys.argv[1:]), napis zawierający wszystkie możliwe jedno-znakowe flagi, które program akceptuje, a także listę dłuższych flag, które są odpowiednikami krótszych, jedno-znakowych wersji. Na pierwszy rzut oka wydaje się to trochę zamotane, ale w dalszej części szerzej to omówimy.
  3. Jeśli coś nie poszło pomyślnie podczas parsowania flag linii poleceń, getopt rzuca wyjątek, który następnie przechwytujemy. Informujemy funkcję getopt o wszystkich flagach, które rozumie nasz program, zatem ostatecznie oznacza, że użytkownik przekazał niektóre niezrozumiałe przez nas flagi.
  4. Jest to praktycznie standard wykorzystywany w świecie Uniksa, kiedy do skryptu zostaną przekazane niezrozumiałe flagi, wypisujemy streszczoną pomoc dotyczącą użycia programu i wdzięcznie zakańczamy go. Dodajmy, że nie przedstawiliśmy tutaj funkcji usage. Jeszcze trzeba będzie ją gdzieś zaimplementować, aby wypisywała streszczenie pomocy; nie dzieje się to automatycznie.

Czym są te wszystkie parametry przekazane do funkcji getopt? Pierwszy jest po prostu surową listą argumentów i flag przekazanych do linii poleceń (bez pierwszego elementu, czyli nazwy skryptu, który wycięliśmy przed wywołaniem funkcji main). Drugi parametr jest listą krótkich flag linii poleceń, które akceptuje skrypt.

"hg:d"

-h
    wyświetla streszczoną pomoc 
-g ...
    korzysta z określonego pliku gramatyki lub URL-a
-d
    pokazuje informacje debugujące podczas parsowania

Pierwsza i trzecia flaga są zwykłymi, samodzielnymi flagami; możemy je określić lub nie. Flagi te wykonują pewne czynności (wypisują pomoc) lub zmieniają stan (włączają debugowanie). Jakkolwiek, za drugą flagą (-g) musi się znaleźć pewien argument, który będzie nazwą pliku gramatyki, który ma zostać wykorzystany. W rzeczywistości może być nazwą pliku lub adresem strony strony web, ale w tym momencie nie wiemy jeszcze, czym jest (zostanie to sprawdzone później), ale wiemy, że ma być czymś. Poinformowaliśmy getopt o tym, że ma być coś za tą flagą, poprzez wstawienie w drugim parametrze dwukropka po literze g.

Żeby to bardziej skomplikować, skrypt akceptuje zarówno krótkie flagi (np. -h, jak i długie flagi (jak --help), a my chcemy, żeby służyły one do tego samego. I po to jest trzeci parametr w getopt. Określa on listę długich flag, które odpowiadają krótkim flagom zdefiniowanym w drugim parametrze.

["help", "grammar="]

--help
    wyświetla streszczoną pomoc
--grammar ...
    korzysta z określonego pliku gramatyki lub URL-a

Zwróćmy uwagę na trzy sprawy:

  1. Wszystkie długie flagi w linii poleceń są poprzedzone dwoma myślnikami, ale podczas wywoływania getopt nie dołączamy tych myślników.
  2. Po fladze --grammar musi zawsze wystąpić dodatkowy argument, identycznie jak z flagą -g. Informujemy o tym poprzez znak równości w "grammar=".
  3. Lista długich flag jest krótsza niż lista krótkich flag, ponieważ flaga -d nie ma swojego dłuższego odpowiednika. Jedynie -d będzie włączał debugowanie. Jednak porządek krótkich i długich flag musi być ten sam, dlatego też najpierw musimy określić wszystkie krótkie flagi odpowiadające dłuższym flagom, a następnie pozostałą część krótszych flag, które nie mają swojego dłuższego odpowiednika.

Jeszcze się nie pogubiłeś? To spójrz na właściwy kod i zobacz, czy nie staje się dla ciebie zrozumiały.

Przykład. Obsługa argumentów linii poleceń w kgp.py
def main(argv):                          #(1)
    grammar = "kant.xml"                
    try:                                
        opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="])
    except getopt.GetoptError:          
        usage()                         
        sys.exit(2)                     
    for opt, arg in opts:                #(2)
        if opt in ("-h", "--help"):      #(3)
            usage()                     
            sys.exit()                  
        elif opt == '-d':                #(4)
            global _debug               
            _debug = 1                  
        elif opt in ("-g", "--grammar"): #(5)
            grammar = arg               

    source = "".join(args)               #(6)

    k = KantGenerator(grammar, source)
    print k.output()
  1. Zmienna grammar będzie przechowywać ścieżkę do pliku gramatyki, z którego będziemy korzystać. W tym miejscu inicjalizujemy ją tak, aby w przypadku, gdy nie zostanie określona w linii poleceń (za pomocą flagi -g lub --grammar) miała jakąś domyślną wartość.
  2. Zmienną opts, którą otrzymujemy z wartość zwróconej przez getopt, przechowuje listę krotek: flagę i argument. Jeśli flaga nie przyjmuje argumentu, to argument będzie miał wartość None. Ułatwia to wykonywanie pętli na flagach.
  3. getopt kontroluje, czy flagi linii poleceń są akceptowalne, ale nie wykonuje żadnej konwersji między długimi, a krótkimi flagami. Jeśli określimy flagę -h, opt będzie zawierać "-h", natomiast jeśli określimy flagę --help, opt będzie zawierać "--help". Zatem musimy kontrolować obydwa warianty.
  4. Pamiętamy, że fladze -d nie odpowiada żadna dłuższa wersja, dlatego też kontrolujemy tylko tę krótką flagę. Jeśli zostanie ona odnaleziona, ustawiamy globalną zmienną, do której później będziemy się odwoływać, aby wypisywać informacje debugujące. (Flaga ta była wykorzystywana podczas projektowania skryptu. Nie myślisz chyba, że wszystkie przedstawione przykłady działały od razu?)
  5. Jeśli znajdziemy plik gramatyki spotykając flagę -g lub −−grammar, zapisujemy argument, który następuje po tej fladze (przechowywany w zmiennej arg), do zmiennej grammar, nadpisując przy tym domyślną wartość, zainicjalizowaną na początku funkcji main.
  6. Ok. Wykonaliśmy pętlę przez wszystkie flagi i przetworzyliśmy je. Oznacza to, że pozostała część musi być argumentami linii poleceń, a zostały one zwrócone przez funkcje getopt do zmiennej args. W tym przypadku traktujemy je jako materiał źródłowy dla parsera. Jeśli nie zostały określone żadne argumenty linii poleceń, args będzie pustą listą, więc source w wyniku tego będzie pustym napisem.