Jak analizować argumenty wiersza poleceń w Pythonie

Zastanawiasz się, jak uruchamiać skrypty Pythona z parametrami przekazywanymi w linii komend? Ten artykuł wyjaśni, jak efektywnie przetwarzać argumenty wiersza poleceń, wykorzystując w tym celu moduły sys, getopt oraz argparse w języku Python.

W Pythonie, standardowo do pobierania danych od użytkownika wykorzystuje się funkcję `input()`. Niemniej jednak, w pewnych sytuacjach, potrzebne może być przekazywanie dodatkowych informacji do skryptu bezpośrednio podczas jego uruchamiania w terminalu.

W niniejszym poradniku nauczymy się, jak uruchamiać skrypty Pythona z opcjami i argumentami wprowadzonymi w linii poleceń. Następnie, zgłębimy wiedzę o tym, jak za pomocą wbudowanych modułów Pythona skutecznie analizować te opcje i argumenty.

Zacznijmy!

Zrozumienie `sys.argv` w Pythonie

Osoby z doświadczeniem w programowaniu w C wiedzą, że jednym z podstawowych sposobów przekazywania danych do programu jest linia komend. W C, można zorganizować funkcję `main` w następujący sposób:

#include<stdio.h>

int main(int argc, char **argv){
    //argc: licznik argumentów
    //argv: tablica argumentów
    
    //operacje na argumentach

    return 0;
}

W powyższym przykładzie, `argc` przechowuje liczbę argumentów, natomiast `argv` to tablica, w której przechowywane są te argumenty.

Uruchamianie skryptów Pythona z argumentami wiersza poleceń

W Pythonie, skrypt można uruchomić za pomocą polecenia `python3 nazwa_pliku.py`. Wykonując to polecenie, możemy również przekazać dowolną liczbę dodatkowych parametrów:

$ python3 filename.py arg1 arg2 ... argn

Moduł `sys` umożliwia łatwy dostęp i przetwarzanie tych argumentów. `sys.argv` to lista wszystkich argumentów wiersza poleceń, które zostały podane przy uruchamianiu skryptu Pythona.

Oto prosty przykład, w którym uruchamiamy skrypt `main.py` z dodatkowymi parametrami:

$ python3 main.py hello world python script

Możemy iterować przez wektor argumentów za pomocą pętli `for` i funkcji `enumerate`:

# main.py

import sys

for idx, arg in enumerate(sys.argv):
    print(f"arg{idx}: {arg}")
# Wynik
arg0:main.py
arg1:hello
arg2:world
arg3:python
arg4:script

Widzimy, że pierwszy argument (o indeksie 0) to nazwa samego skryptu. Kolejne argumenty są indeksowane od 1.

Jest to podstawowy, działający program, który akceptuje i przetwarza argumenty wiersza poleceń. Zauważamy jednak pewne niedociągnięcia:

  • Skąd użytkownicy programu mają wiedzieć, jakie parametry należy przekazać?
  • Jaki jest cel tych argumentów?

Nie jest to oczywiste. Aby rozwiązać ten problem, można skorzystać z modułów `getopt` lub `argparse`. Omówimy je w kolejnych sekcjach.✅

Analiza argumentów wiersza poleceń za pomocą `getopt`

Przeanalizujmy teraz argumenty wiersza poleceń przy użyciu wbudowanego modułu `getopt`.

Po zaimportowaniu `getopt` z modułu `getopt`, możemy zdefiniować argumenty do analizy, jak również krótkie i długie opcje. Chcemy analizować argumenty od indeksu 1 z listy `sys.argv`. Zatem, wycinek do przeanalizowania to `sys.argv[1:]`.

W naszym przykładzie będziemy potrzebować wiadomości oraz nazwy pliku. Użyjemy `m` i `f` jako opcji krótkich, a `message` i `file` jako opcji długich.

Jak określić, czy dana opcja powinna przyjmować argument?

  • W opcjach krótkich, dodanie dwukropka (:) po nazwie opcji, oznacza, że opcja wymaga argumentu.
  • W opcjach długich, znak równości (=) po nazwie opcji sygnalizuje, że opcja wymaga argumentu. Za pomocą tej metody, możemy przechwycić opcje oraz przypisane do nich wartości.

Po wprowadzeniu tych zmian, nasz kod w `main.py` powinien wyglądać następująco:

# main.py

import sys
from getopt import getopt

opts, args = getopt(sys.argv[1:],'m:f:',['message=','file='])

print(opts)
print(args)

Tutaj, zmienna `opts` zawiera opcje oraz ich argumenty jako listę krotek. Wszystkie inne argumenty, które nie są opcjami, są zapisane w zmiennej `args`.

Możemy teraz przekazać komunikat oraz nazwę pliku podczas uruchamiania skryptu, używając zarówno krótkich, jak i długich opcji.

Uruchamiając `main.py` z opcjami długimi, otrzymujemy:

$ python3 main.py --message hello --file somefile.txt

Opcje i ich argumenty są zapisane jako krotki w zmiennej `opts`. Ponieważ nie przekazaliśmy dodatkowych argumentów pozycyjnych, lista `args` jest pusta.

# Wynik
[('--message', 'hello'), ('--file', 'somefile.txt')]
[]

Równoważnie, możemy również użyć opcji krótkich, jak pokazano poniżej:

$ python3 main.py -m hello -f somefile.txt
# Wynik
[('-m', 'hello'), ('-f', 'somefile.txt')]
[]

⚠️ Opcja `-m` w tym przykładzie nie powinna być mylona z flagą wiersza poleceń `-m`, która służy do uruchamiania modułu jako głównego modułu podczas uruchamiania skryptu Pythona.

Na przykład, aby uruchomić moduł `unittest` jako główny moduł podczas uruchamiania `main.py`, użyjemy polecenia `python3 -m unittest main.py`.

Wcześniej wspominaliśmy, że wszystkie dodatkowe argumenty pozycyjne zostaną zapisane w zmiennej `args`. Oto przykład:

$ python3 main.py -m hello -f somefile.txt another_argument

W tym przypadku, lista argumentów zawiera argument pozycyjny `another_argument`.

# Wynik
[('-m', 'hello'), ('-f', 'somefile.txt')]
['another_argument']

Jak widzieliśmy, `opts` to lista krotek. Możemy iterować po tej liście, rozpakowując krotki, aby uzyskać argumenty przypisane do poszczególnych opcji.

Co jednak zrobimy z nazwą pliku i komunikatem po przetworzeniu tych argumentów? Otworzymy plik w trybie zapisu i zapiszemy do niego wiadomość przekonwertowaną na wielkie litery.

# main.py
import sys
from getopt import getopt

opts, args = getopt(sys.argv[1:],'m:f:',['message=','file='])

print(opts)
print(args)

for option, argument in opts:
    if option == "-m":
        message = argument
    if option == '-f':
        file = argument

with open(file,'w') as f:
    f.write(message.upper())

Uruchommy teraz skrypt `main.py` z krótkimi opcjami oraz argumentami wiersza poleceń.

$ python main.py -m hello -f thisfile.txt
[('-m', 'hello'), ('-f', 'thisfile.txt')]
[]

Po uruchomieniu `main.py` w bieżącym katalogu roboczym, pojawi się plik `thisfile.txt`, zawierający wiadomość „hello”, przekonwertowaną na wielkie litery („HELLO”).

$ ls
main.py  thisfile.txt
$ cat thisfile.txt
HELLO

Jak analizować argumenty wiersza poleceń za pomocą `argparse`

Moduł `argparse`, będący częścią standardowej biblioteki Pythona, oferuje bardziej zaawansowane narzędzia do analizy argumentów wiersza poleceń, a także do tworzenia interfejsów wiersza poleceń.

Aby skorzystać z `argparse` importujemy klasę `ArgumentParser` z modułu `argparse`. Następnie, tworzymy instancję `arg_parser` – obiekt klasy `ArgumentParser`:

from argparse import ArgumentParser

arg_parser = ArgumentParser()

Teraz, dodajmy dwa argumenty wiersza poleceń:

  • `message`: ciąg znaków reprezentujący wiadomość oraz
  • `file`: nazwa pliku, z którym chcemy pracować.

W tym celu użyjemy metody `add_argument()` na obiekcie `arg_parser`. W wywołaniu metody możemy również dodać opis argumentu za pomocą parametru `help`.

arg_parser.add_argument('message',help='ciąg znaków wiadomości')
arg_parser.add_argument('file',help='nazwa pliku')

Do tej pory utworzyliśmy instancję `arg_parser` i dodaliśmy argumenty. Gdy program jest uruchamiany, możemy użyć metody `parse_args()` na obiekcie `arg_parser`, aby otrzymać wartości argumentów.

W tym przykładzie, wartości argumentów są zapisane w zmiennej `args`. Możemy użyć `args.argument_name`, aby uzyskać wartości poszczególnych argumentów.

Po pobraniu wartości argumentów, zapiszemy wiadomość z odwróconą wielkością liter (metodą `swapcase()`) do pliku.

args = arg_parser.parse_args()

message = args.message
file = args.file

with open(file,'w') as f:
     f.write(message.swapcase())

Łącząc wszystkie elementy, nasz plik `main.py` będzie wyglądać następująco:

# main.py

from argparse import ArgumentParser

arg_parser = ArgumentParser()
arg_parser.add_argument('message',help='ciąg znaków wiadomości')
arg_parser.add_argument('file',help='nazwa pliku')

args = arg_parser.parse_args()
print(args)

message = args.message
file = args.file

with open(file,'w') as f:
     f.write(message.swapcase())

Zrozumienie użycia argumentów wiersza poleceń

Aby dowiedzieć się, jak używać argumentów podczas uruchamiania skryptu `main.py`, skorzystajmy z opcji `–help`:

$ python3 main.py --help
usage: main.py [-h] message file

positional arguments:
  message     ciąg znaków wiadomości
  file        nazwa pliku

optional arguments:
  -h, --help  show this help message and exit

W tym przypadku nie mamy żadnych argumentów opcjonalnych. Zarówno `message`, jak i `file` są argumentami pozycyjnymi, a więc są wymagane. Możemy również użyć opcji `-h`:

$ python3 main.py -h
usage: main.py [-h] message file

positional arguments:
  message     ciąg znaków wiadomości
  file        nazwa pliku

optional arguments:
  -h, --help  show this help message and exit

Jak widzimy, oba argumenty są domyślnie argumentami pozycyjnymi. Jeżeli nie przekażemy jednego lub więcej z nich, wystąpi błąd.

W poniższym przykładzie, podaliśmy argument pozycyjny (`Hello`) dla wiadomości, ale nie podaliśmy żadnej wartości dla argumentu plik.

W rezultacie otrzymujemy informację o tym, że argument pliku jest wymagany.

$ python3 main.py Hello
usage: main.py [-h] message file
main.py: error: the following arguments are required: file

Kiedy uruchomimy `main.py`, przekazując oba argumenty pozycyjne, zauważymy, że przestrzeń nazw `args` zawiera wartości argumentów.

$ python3 main.py Hello file1.txt
# Wynik
Namespace(file="file1.txt", message="Hello")

Sprawdzając zawartość bieżącego katalogu roboczego, zobaczymy, że skrypt utworzył plik `file1.txt`:

$ ls
file1.txt  main.py

Oryginalny ciąg znaków to „Hello”. Po zamianie wielkości liter, w pliku `file1.txt` zapisana jest wartość „hELLO”.

$ cat file1.txt
hELLO

Jak sprawić, by argumenty wiersza poleceń były opcjonalne

Aby uczynić argumenty wiersza poleceń opcjonalnymi, należy poprzedzić nazwę argumentu znakiem `–`.

Zmodyfikujmy `main.py` w taki sposób, aby zarówno argumenty `message`, jak i `file` były opcjonalne.

# main.py

from argparse import ArgumentParser

arg_parser = ArgumentParser()
arg_parser.add_argument('--message',help='ciąg znaków wiadomości')
arg_parser.add_argument('--file',help='nazwa pliku')

Ponieważ oba argumenty wiersza poleceń są opcjonalne, możemy ustawić dla nich wartości domyślne.

if args.message and args.file:
    message = args.message
    file = args.file
else:
    message="Python3"
    file="myfile.txt"

W tym momencie nasz plik `main.py` zawiera następujący kod:

# main.py

from argparse import ArgumentParser

arg_parser = ArgumentParser()
arg_parser.add_argument('--message',help='ciąg znaków wiadomości')
arg_parser.add_argument('--file',help='nazwa pliku')

args = arg_parser.parse_args()
print(args)

if args.message and args.file:
    message = args.message
    file = args.file
else:
    message="Python3"
    file="myfile.txt"

with open(file,'w') as f:
     f.write(message.swapcase())

Sprawdzając użycie, zobaczymy, że zarówno `message`, jak i `file` są argumentami opcjonalnymi. Oznacza to, że możemy uruchomić `main.py` bez przekazywania żadnych argumentów.

$ python3 main.py --help
usage: main.py [-h] [--message MESSAGE] [--file FILE]

optional arguments:
  -h, --help         show this help message and exit
  --message MESSAGE  ciąg znaków wiadomości
  --file FILE        nazwa pliku
$ python3 main.py

W przestrzeni nazw argumentów, zarówno `file`, jak i `message`, mają wartość `None`.

# Wynik
Namespace(file=None, message=None)

W tym przypadku, używane są domyślne wartości: nazwa pliku `myfile.txt` oraz komunikat `Python3`. Plik `myfile.txt` znajduje się teraz w katalogu roboczym:

$ ls
file1.txt  main.py  myfile.txt

Zawiera on ciąg „Python3” z zamienionymi wielkościami liter:

$ cat myfile.txt
pYTHON3

Możemy również użyć zarówno argumentów `–message`, jak i `–file`, co czyni polecenie bardziej czytelnym.

$ python3 main.py --message Coding --file file2.txt
# Wynik
Namespace(file="file2.txt", message="Coding")

Plik `file2.txt` pojawił się w katalogu roboczym:

$ ls
file1.txt  file2.txt  main.py  myfile.txt

Zgodnie z oczekiwaniami, zawiera ciąg „cODING”.

$ cat file2.txt
cODING

Podsumowanie

Podsumowując, w tym artykule nauczyliśmy się:

  • Podobnie jak w języku C, w Pythonie dostęp do argumentów wiersza poleceń uzyskujemy poprzez iterację po wektorze `sys.argv`. `sys.argv[0]` to nazwa skryptu. Nas interesują argumenty od `sys.argv[1:]`.
  • Aby poprawić czytelność i mieć możliwość dodawania opcji, można użyć modułów `getopt` i `argparse`.
  • Moduł `getopt` służy do analizy listy argumentów, zaczynając od indeksu 1 aż do końca listy. Możemy definiować zarówno krótkie, jak i długie opcje.
  • Gdy opcja przyjmuje argument, należy dodać dwukropek (:) lub znak równości (=) po nazwie opcji (odpowiednio dla krótkiej i długiej opcji).
  • Moduł `argparse` umożliwia tworzenie instancji obiektu `ArgumentParser` oraz dodawanie wymaganych argumentów pozycyjnych za pomocą metody `add_argument()`. Użycie znaku `–` przed nazwą argumentu sprawia, że argument staje się opcjonalny.
  • Aby pobrać wartości argumentów, wywołujemy metodę `parse_args()` na obiekcie `ArgumentParser`.

Następnie, dowiedz się, jak wykonywać bezpieczne mieszanie w Pythonie.


newsblog.pl