Jaki jest najlepszy sposób na analizę argumentów wiersza poleceń? [Zamknięte]

250

Jaka jest najłatwiejsza , najkrótsza i najbardziej elastyczna metoda lub biblioteka do analizowania argumentów wiersza poleceń Pythona?

kamens
źródło

Odpowiedzi:

183

Ta odpowiedź sugeruje, optparsektóre rozwiązanie jest odpowiednie dla starszych wersji Pythona. W przypadku Python 2.7 i nowszych argparsezamienia optparse. Zobacz tę odpowiedź, aby uzyskać więcej informacji.

Jak zauważyli inni, lepiej wybrać optparse niż getopt. getopt jest mapowaniem jeden do jednego standardowych funkcji biblioteki getopt (3) C i nie jest bardzo łatwy w użyciu.

optparse, choć jest nieco bardziej gadatliwy, ma znacznie lepszą strukturę i jest łatwiejsze do rozszerzenia w późniejszym terminie.

Oto typowy wiersz, aby dodać opcję do analizatora składni:

parser.add_option('-q', '--query',
            action="store", dest="query",
            help="query string", default="spam")

Prawie mówi samo za siebie; w czasie przetwarzania przyjmuje -q lub --query jako opcje, przechowuje argument w atrybucie o nazwie zapytanie i ma wartość domyślną, jeśli go nie określisz. Jest to również dokumentowanie sam w sobie, ponieważ deklarujesz argument pomocy (który zostanie użyty, gdy zostanie uruchomiony z opcją -h / - help) z opcją.

Zwykle analizujesz argumenty za pomocą:

options, args = parser.parse_args()

Spowoduje to domyślną analizę standardowych argumentów przekazanych do skryptu (sys.argv [1:])

options.query zostanie wówczas ustawiona na wartość przekazaną do skryptu.

Parser tworzy się po prostu przez wykonanie

parser = optparse.OptionParser()

To wszystko, czego potrzebujesz. Oto kompletny skrypt Pythona, który to pokazuje:

import optparse

parser = optparse.OptionParser()

parser.add_option('-q', '--query',
    action="store", dest="query",
    help="query string", default="spam")

options, args = parser.parse_args()

print 'Query string:', options.query

5 linii pytona, które pokazują podstawy.

Zapisz go w sample.py i uruchom raz

python sample.py

i raz z

python sample.py --query myquery

Poza tym przekonasz się, że optparse można bardzo łatwo rozszerzyć. W jednym z moich projektów stworzyłem klasę Command, która umożliwia łatwe zagnieżdżanie podkomend w drzewie poleceń. Silnie wykorzystuje optparse do łączenia poleceń. Nie jest to coś, co mogę łatwo wyjaśnić w kilku wierszach, ale możesz swobodnie przeglądać w moim repozytorium klasę główną, a także klasę, która z niej korzysta i parser opcji

Thomas Vander Stichele
źródło
9
Ta odpowiedź jest cudownie jasna i łatwa do naśladowania - dla python 2.3 do 2.6. Dla Pythona 2.7+ nie jest to najlepsza odpowiedź, ponieważ argparse jest teraz częścią standardowej biblioteki, a optparse jest przestarzałe.
matt wilkie
W moim przypadku chcę profilować moją aplikację w celu wykrycia powolności. Istnieje inne narzędzie o nazwie [tuna] ( github.com/nschloe/tuna ), które pozwala mi profilować całą aplikację, po prostu dodając agrs, -mcProfile -o program.profale agrparcer przechwytuje te argumenty, jak przekazać te argumenty do exe Pythona ???
Jogeshwar
231

argparsejest droga. Oto krótkie podsumowanie tego, jak z niego korzystać:

1) Zainicjuj

import argparse

# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')

2) Dodaj argumenty

# Required positional argument
parser.add_argument('pos_arg', type=int,
                    help='A required integer positional argument')

# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
                    help='An optional integer positional argument')

# Optional argument
parser.add_argument('--opt_arg', type=int,
                    help='An optional integer argument')

# Switch
parser.add_argument('--switch', action='store_true',
                    help='A boolean switch')

3) Analizuj

args = parser.parse_args()

4) Dostęp

print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)

5) Sprawdź wartości

if args.pos_arg > 10:
    parser.error("pos_arg cannot be larger than 10")

Stosowanie

Prawidłowe użycie:

$ ./app 1 2 --opt_arg 3 --switch

Argument values:
1
2
3
True

Błędne argumenty:

$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'

$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10

Pełna pomoc:

$ ./app -h

usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]

Optional app description

positional arguments:
  pos_arg            A required integer positional argument
  opt_pos_arg        An optional integer positional argument

optional arguments:
  -h, --help         show this help message and exit
  --opt_arg OPT_ARG  An optional integer argument
  --switch           A boolean switch
Andrzej Pronobis
źródło
10
Jest to bardzo zwięzłe i przydatne i oto oficjalny dokument (dla wygody): docs.python.org/3/library/argparse.html
Christophe Roussy
1
Jeśli uważasz, że argparse jest zbyt szczegółowe, użyj zamiast tego plac .
Nimitz14
76

Korzystanie z docopt

Od 2012 roku istnieje bardzo łatwy, wydajny i naprawdę fajny moduł do analizy argumentów o nazwie docopt . Oto przykład zaczerpnięty z jego dokumentacji:

"""Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

"""
from docopt import docopt


if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')
    print(arguments)

Więc to jest to: 2 linie kodu plus ciąg dokumentów, który jest niezbędny, a argumenty są analizowane i dostępne w obiekcie argumentów.

Korzystanie z python-fire

Od 2017 roku istnieje kolejny fajny moduł o nazwie python-fire . Może wygenerować interfejs CLI dla twojego kodu, gdy parsujesz zero argumentów. Oto prosty przykład z dokumentacji (ten mały program udostępnia funkcję doubledo wiersza poleceń):

import fire

class Calculator(object):

  def double(self, number):
    return 2 * number

if __name__ == '__main__':
  fire.Fire(Calculator)

Z wiersza poleceń możesz uruchomić:

> calculator.py double 10
20
> calculator.py double --number=15
30
ndemou
źródło
4
w jaki sposób docopt „nie wymaga instalacji”? jest to moduł pythonowy, więc trzeba go zainstalować. „ImportError: Brak modułu o nazwie docopt”
chętny
1
@keen na pewno nie jest dołączony do Pythona, ale nie trzeba go instalować: „możesz po prostu upuścić plik docopt.py do swojego projektu - jest samowystarczalny” - github.com/docopt/docopt
ndemou
9
mamy tylko różne definicje instalacji - i chciałem to podkreślić dla przyszłych czytelników.
zapalony
1
@keen Dodałem notatkę na temat „bez instalacji” dla osób udostępniających twoją definicję :-)
ndemou
39

Nowy sposób hop jest argparsedla tych powodów. argparse> optparse> getopt

Aktualizacja: Jak z py2.7 argparse jest częścią biblioteki standardowej i optparse jest przestarzała.

Silfheed
źródło
Twój główny link to 404, więc zastąpiłem go linkiem do pytania SO, które dotyczy tego samego tematu.
Joe Holloway,
28

Wolę kliknij . Wyodrębnia zarządzanie opcjami i pozwala „(...) tworzyć piękne interfejsy wiersza poleceń w łatwy do skomponowania sposób z tak małą ilością kodu, jak to konieczne”.

Oto przykładowe użycie:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

Automatycznie generuje również dobrze sformatowane strony pomocy:

$ python hello.py --help
Usage: hello.py [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER  Number of greetings.
  --name TEXT      The person to greet.
  --help           Show this message and exit.
suda
źródło
14

Niemal wszyscy używają getopt

Oto przykładowy kod dla dokumentu:

import getopt, sys

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])
    except getopt.GetoptError:
        # print help information and exit:
        usage()
        sys.exit(2)
    output = None
    verbose = False
    for o, a in opts:
        if o == "-v":
            verbose = True
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-o", "--output"):
            output = a

Jednym słowem, oto jak to działa.

Masz dwa rodzaje opcji. Ci, którzy otrzymują argumenty, i ci, którzy są jak przełączniki.

sys.argvjest prawie char** argvw twoim C. Podobnie jak w C pomijasz pierwszy element, który jest nazwą twojego programu i analizujesz tylko argumenty:sys.argv[1:]

Getopt.getopt przeanalizuje to zgodnie z regułą, którą podasz w argumencie.

"ho:v"tutaj opisuje krótkie argumenty -ONELETTER. Te :środki, które -oakceptuje jeden argument.

Wreszcie ["help", "output="]opisuje długie argumenty ( --MORETHANONELETTER). Wynik =po raz kolejny oznacza, że ​​dane wyjściowe akceptują jeden argument.

Wynikiem jest lista par (opcja, argument)

Jeśli opcja nie przyjmuje żadnego argumentu (jak --helptutaj), argczęść jest pustym ciągiem. Następnie zwykle chcesz zapętlić tę listę i przetestować nazwę opcji jak w przykładzie.

Mam nadzieję, że to ci pomogło.

fulmikoton
źródło
6
Po wycofaniu się getoptw nowszych wersjach Pythona ta odpowiedź jest nieaktualna.
Shuttle87
1
@ Shuttle87 Począwszy od python3.7.2, getoptnadal nie jest przestarzały… Ale jego dokumentacja stwierdza, że ​​jest on przeznaczony głównie dla użytkowników zaznajomionych z getopt()funkcją C i potwierdza, że ​​dla innych użytkowników argparsemoże być lepszym rozwiązaniem, pozwalającym „napisać mniej kodu i uzyskać lepsza pomoc i komunikaty o błędach ”.
Skippy le Grand Gourou
14

Użyj, optparsektóry jest dostarczany ze standardową biblioteką. Na przykład:

#!/usr/bin/env python
import optparse

def main():
  p = optparse.OptionParser()
  p.add_option('--person', '-p', default="world")
  options, arguments = p.parse_args()
  print 'Hello %s' % options.person

if __name__ == '__main__':
  main()

Źródło: Używanie Pythona do tworzenia narzędzi wiersza poleceń UNIX

Jednak od wersji Python 2.7 optparse jest przestarzałe, zobacz: Dlaczego warto używać argparse zamiast optparse?

Corey
źródło
6

Na wszelki wypadek może to pomóc, jeśli chcesz przechwycić argumenty Unicode w Win32 (2K, XP itp.):


from ctypes import *

def wmain(argc, argv):
    print argc
    for i in argv:
        print i
    return 0

def startup():
    size = c_int()
    ptr = windll.shell32.CommandLineToArgvW(windll.kernel32.GetCommandLineW(), byref(size))
    ref = c_wchar_p * size.value
    raw = ref.from_address(ptr)
    args = [arg for arg in raw]
    windll.kernel32.LocalFree(ptr)
    exit(wmain(len(args), args))
startup()
Shadow2531
źródło
Dziękuję Ci. Ten skrypt pomógł mi opracować naprawdę skomplikowane cytaty, które musiałem wykonać, przekazując komendy uruchamiania do GVim.
telotortium
6

Domyślne argumenty linii poleceń

Chociaż argparsejest świetny i jest właściwą odpowiedzią na w pełni udokumentowane przełączniki wiersza poleceń i zaawansowane funkcje, możesz użyć domyślnych argumentów funkcji, aby bardzo prosto obsługiwać proste argumenty pozycyjne.

import sys

def get_args(name='default', first='a', second=2):
    return first, int(second)

first, second = get_args(*sys.argv)
print first, second

Argument „nazwa” przechwytuje nazwę skryptu i nie jest używany. Wyjście testowe wygląda następująco:

> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20

W przypadku prostych skryptów, w których chcę tylko wartości domyślne, uważam to za wystarczające. Możesz także uwzględnić przymus typu w wartościach zwracanych lub wszystkie wartości wiersza poleceń będą ciągami.

Simon Hibbs
źródło
2
cytaty są niedopasowane w instrukcji def.
historystamp
3

Wolę optparse niż getopt. Jest bardzo deklaratywny: podajesz nazwy opcji i efekty, które powinny one mieć (np. Ustawiając pole boolowskie), i przekazuje ci słownik wypełniony zgodnie ze specyfikacjami.

http://docs.python.org/lib/module-optparse.html

Chris Conway
źródło
3

Myślę, że najlepszym sposobem dla większych projektów jest optparse, ale jeśli szukasz łatwego sposobu, może http://werkzeug.pocoo.org/documentation/script jest dla Ciebie.

from werkzeug import script

# actions go here
def action_foo(name=""):
    """action foo does foo"""
    pass

def action_bar(id=0, title="default title"):
    """action bar does bar"""
    pass

if __name__ == '__main__':
    script.run()

Zasadniczo każda funkcja action_ * jest widoczna w wierszu poleceń, a miły komunikat pomocy jest generowany za darmo.

python foo.py 
usage: foo.py <action> [<options>]
       foo.py --help

actions:
  bar:
    action bar does bar

    --id                          integer   0
    --title                       string    default title

  foo:
    action foo does foo

    --name                        string
Peter Hoffmann
źródło
Mam opracowane paczuszkę z wykorzystaniem automatycznego tworzenia argumenty declarative_parser. Oczywiście, jeśli ktoś pracuje z werkzeug, może być lepiej zachować werkzung.script. W każdym razie jestem wielkim fanem takiego podejścia.
krassowski
3

Kod argparse może być dłuższy niż rzeczywisty kod implementacyjny!

Jest to problem, który widzę w przypadku najbardziej popularnych opcji analizy argumentów polega na tym, że jeśli parametry są tylko skromne, kod do ich udokumentowania staje się nieproporcjonalnie duży z korzyścią, którą zapewniają.

Względnym nowicjuszem w scenie analizowania argumentów (myślę) jest plac .

Dokonuje pewnych uznanych kompromisów z argparse, ale używa wbudowanej dokumentacji i owija się wokół main()funkcji funkcji typu:

def main(excel_file_path: "Path to input training file.",
     excel_sheet_name:"Name of the excel sheet containing training data including columns 'Label' and 'Description'.",
     existing_model_path: "Path to an existing model to refine."=None,
     batch_size_start: "The smallest size of any minibatch."=10.,
     batch_size_stop:  "The largest size of any minibatch."=250.,
     batch_size_step:  "The step for increase in minibatch size."=1.002,
     batch_test_steps: "Flag.  If True, show minibatch steps."=False):
"Train a Spacy (http://spacy.io/) text classification model with gold document and label data until the model nears convergence (LOSS < 0.5)."

    pass # Implementation code goes here!

if __name__ == '__main__':
    import plac; plac.call(main)
QA Collective
źródło
Punkt informacji: najładniejsze użycie plac (jak pokazano w przykładzie) dotyczy tylko języka Python 3.x, ponieważ używa adnotacji funkcji 3.x.
barny
1

Consoleargs zasługuje na wzmiankę tutaj. Jest bardzo łatwy w użyciu. Sprawdź to:

from consoleargs import command

@command
def main(url, name=None):
  """
  :param url: Remote URL 
  :param name: File name
  """
  print """Downloading url '%r' into file '%r'""" % (url, name)

if __name__ == '__main__':
  main()

Teraz w konsoli:

% python demo.py --help
Usage: demo.py URL [OPTIONS]

URL:    Remote URL 

Options:
    --name -n   File name

% python demo.py http://www.google.com/
Downloading url ''http://www.google.com/'' into file 'None'

% python demo.py http://www.google.com/ --name=index.html
Downloading url ''http://www.google.com/'' into file ''index.html''
łodyga
źródło
Zastosowałem podobne podejście w parserze deklaratywnym , zobacz dedukcję argumentów (pisanie, docstring, kwargs) w dokumentacji. Główne różnice: python3, podpowiedzi typu, instalowalne w pip.
krassowski
1
Ostatnie zatwierdzenie w 2012 r.
Boris
0

Oto metoda, a nie biblioteka, która wydaje mi się działać.

Cele tutaj są zwięzłe, każdy argument parsowany przez pojedynczy wiersz, argumenty w linii dla czytelności, kod jest prosty i nie zależy od żadnych specjalnych modułów (tylko os + sys), z wdziękiem ostrzega o brakujących lub nieznanych argumentach , użyj prostej pętli for / range () i działa w Pythonie 2.xi 3.x

Pokazane są dwie flagi przełączania (-d, -v) oraz dwie wartości kontrolowane przez argumenty (-i xxx i -o xxx).

import os,sys

def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  = "infile"
    outfile = "outfile"

    # Parse command line
    skip = 0
    for i in range(1, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] == "-d": debug ^= 1
            elif sys.argv[i][:2] == "-v": verbose ^= 1
            elif sys.argv[i][:2] == "-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] == "-h": HelpAndExit()
            elif sys.argv[i][:1] == "-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("%d,%d,%s,%s" % (debug,verbose,infile,outfile))

Celem NextArg () jest zwrócenie następnego argumentu podczas sprawdzania brakujących danych, a „skip” pomija pętlę, gdy używana jest NextArg (), utrzymując parsowanie flagi do jednego linijki.

erco
źródło
0

Rozszerzyłem podejście Erco, aby uwzględnić wymagane argumenty pozycyjne i argumenty opcjonalne. Powinny one poprzedzać argumenty -d, -v itp.

Argumenty pozycyjne i opcjonalne można pobrać odpowiednio za pomocą PosArg (i) i OptArg (i, domyślnie). Po znalezieniu opcjonalnego argumentu pozycja początkowa wyszukiwania opcji (np. -I) przesuwa się o 1 do przodu, aby uniknąć spowodowania „nieoczekiwanego” błędu.

import os,sys


def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

def PosArg(i):
    '''Return positional argument'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return sys.argv[i]

def OptArg(i, default):
    '''Return optional argument (if there is one)'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    if sys.argv[i][:1] != '-':
        return True, sys.argv[i]
    else:
        return False, default


### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  = "infile"
    outfile = "outfile"
    options_start = 3

    # --- Parse two positional parameters ---
    n1 = int(PosArg(1))
    n2 = int(PosArg(2))

    # --- Parse an optional parameters ---
    present, a3 = OptArg(3,50)
    n3 = int(a3)
    options_start += int(present)

    # --- Parse rest of command line ---
    skip = 0
    for i in range(options_start, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] == "-d": debug ^= 1
            elif sys.argv[i][:2] == "-v": verbose ^= 1
            elif sys.argv[i][:2] == "-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] == "-h": HelpAndExit()
            elif sys.argv[i][:1] == "-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("Number 1 = %d" % n1)
    print("Number 2 = %d" % n2)
    print("Number 3 = %d" % n3)
    print("Debug    = %d" % debug)
    print("verbose  = %d" % verbose)
    print("infile   = %s" % infile)
    print("outfile  = %s" % outfile) 
Erik
źródło