cmd - Crea processori di riga di comando

Scopo Crea processori di riga di comando.
Versione Python 1.4 e successive, con aggiunte nella 2.3

Il modulo cmd contiene una sola classe pubblica: Cmd, progettata per l'uso come classe base per processori di comando tipo shell interattive ed altri interpreti di comando. Nella modalità predefinita la classe usa readline per la gestione del prompt interattivo, per la modifica della riga di comando e per il completamento del comando.

Elaborazione dei Comandi

L'interprete usa un ciclo per leggere tutte le righe dal suo input, analizzarle, quindi inviare il comando all'appropriato gestore di comando. Le righe in input sono elaborate in due parti. Il comando, quindi qualsiasi altro testo nella riga. Se si digita il comando foo bar, e la propria classe comprende un metodo chiamato do_foo(), esso viene chiamato con "bar" come unico parametro.

Il marcatore di fine file viene inviato a do_EOF(). Se un gestore di comando restituisce un valore True. il programma uscirà in modo pulito. Quindi per fare sì che il proprio interprete esca in modo pulito, ci si deve assicurare di implementare do_EOF() in modo che restituisca True

Questo semplice programma di esempio supporta il comando "greet":

import cmd

class HelloWorld(cmd.Cmd):
    """Semplice esempio di processore di comando."""

    def do_greet(self, line):
        print "Salve"

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    HelloWorld().cmdloop()

Eseguendolo interattivamente,si può dimostrare come i comandi vengano inviati così come si può mostrare qualcuna delle caratteristiche di cui Cmd è già dotato.

$ python cmd_simple.py
(Cmd)

La prima cosa da notare è il prompt di comando, (Cmd). Il prompt può essere configurato attraverso l'attributo prompt. Se il prompt cambia per il risultato di un elaborazione di comando, il nuovo valore viene usato per la richiesta del prossimo comando.

(Cmd) help

Undocumented commands:
======================
EOF  greet  help

Il comando help è costruito dentro Cmd. Senza parametri, mostra l'elenco dei comandi disponibili. Se si aggiunge il nome di un comando per il quale si vuole aiuto, il risultato è più particolareggiato e confinato ai dettagli di quel comando, se disponibili.

Se si usa il comando greet, viene chiamato do_greet() per gestirlo:

(Cmd) greet
Salved

Se la propria classe non include uno specifico processore di comando per un comando, viene chiamato il metodo default() con l'intero input della riga come parametro. L'implementazione built-in di default() segnala un errore.

(Cmd) foo *** Unknown syntax: foo

Visto che do_EOF() restituisce True, se si digita Ctrl-D si esce dall'interprete.

(Cmd) ^D$

Si noti che non viene stampato un ritorno a capo su di una nuova riga, quindi il risultato è un poco confuso.

Parametri di Comando

Questa versione dell'esempio comprende qualche miglioria per eliminare qualche fasitdio ed aggiungere un aiuto per il comando greet.

import cmd

class HelloWorld(cmd.Cmd):
    """Semplice esempio di processore di comando."""

    def do_greet(self, person):
        """greet [persona]
        Saluta la persona"""
        if person:
            print "ciao,", person
        else:
            print 'ciao'

    def do_EOF(self, line):
        return True

    def postloop(self):
        print

if __name__ == '__main__':
    HelloWorld().cmdloop()

Per prima cosa, si chiede aiuto. La docstring aggiunta a do_greet diventa il testo di aiuto per il comando:

$ python  cmd_arguments.py
(Cmd) help

Documented commands (type help ):
========================================
greet

Undocumented commands:
======================
EOF  help

(Cmd) help greet
greet [persona]
        Saluta la persona

L'output mostra un parametro opzionale per il comando greet: persona. Sebbene il parametro sia opzionale per il comando c'è distinzione tra il comando ed il metodo di callback. Il metodo riceve sempre un parametro, ma talvolta il valore è una stringa vuota. E' compito del processore di comando determinare se un parametro vuoto sia valido, oppure se occorre eseguire una ulteriore analisi ed elaborazione del comando. In questo esempio, se il nome di una persona viene fornito, allora il saluto viene personalizzato.

(Cmd) greet Alice
ciao, Alice
(Cmd) greet
ciao

A prescindere dal fatto che un parametro sia fornito dall'utente o meno, il valore passato al processore di comando non comprende il comando stesso. Questo semplifica l'analisi al processore di comando, nel caso siano necessari parametri multipli.

Aiuto in Diretta

Nell'esempio precedente, la formattazione del testo di aiuto lascia un poco a desiderare. Visto che viene ricavato dalla docstring mantiene l'indentazione della sorgente. Si potrebbe modificare la sorgente togliendo qualche spazio extra, tuttavia l'applicazione rimarrebbe comunque formattata male. Una soluzione alternativa è implementare un gestore di aiuto per il comando greet, chiamato help_greet(). Se presente il gestore di aiuto viene chiamato per produrre un testo di aiuto per il comando specificato.

import cmd

class HelloWorld(cmd.Cmd):
    """Semplice esempio di processore di comando."""

    def do_greet(self, person):
        if person:
            print "salve,", person
        else:
            print 'salve'

    def help_greet(self):
        print '\n'.join([ 'greet [persona]',
                           'Saluta la persona',
                           ])

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    HelloWorld().cmdloop()

In questo semplice esempio, il testo è statico ma la formattazione è migliore. Sarebbe stato anche possibilie usare lo stato del comando precedente per modellare il contenuto del testo di aiuto sul contesto corrente.

$ python cmd_do_help.py
(Cmd) help greet
greet [persona]
Saluta la persona

Tocca al gestore di aiuto la stampa del messaggio di aiuto, che non deve semplicemente restituire del testo per essere gestito altrove.

Auto completamento

Cmd include il supporto per completamento del comando in base ai nomi dei comandi. L'utente attiva li completamento tramite la pressione del tasto tab al prompt. Se ci sono opzioni multiple di completamento, premendo tab due volte si stampa un elenco di opzioni.

$ python cmd_do_help.py
(Cmd) 
EOF    greet  help
(Cmd) h
(Cmd) help

Una volta che il comando viene riconosciuto, il completamento del parametro viene gestita dai metodi il cui nome inizia per complete_. In questo modo si può assemblare un elenco di possibili completamenti usando un proprio criterio (interrogare un database, cercare in un file o directory nel filesystem, ecc.). In questo caso, il programma ha scritto al suo interno un gruppo di "amici" che ricevono un saluto meno formale rispetto a nomi di sconosciuti. Un vero programma probabilmente salvebbero l'elenco da qualche parte, per leggerlo tutto in un colpo, quindi conservare quanto letto per poterlo scorrere se necessario.

import cmd

class HelloWorld(cmd.Cmd):
    """Semplice esempio di processore di comando."""

    FRIENDS = [ 'Alice', 'Adam', 'Barbara', 'Bob' ]

    def do_greet(self, person):
        "Saluta la persona"
        if person and person in self.FRIENDS:
            greeting = 'Ciao, %s!' % person
        elif person:
            greeting = "Salve, " + person
        else:
            greeting = 'Salve'
        print greeting

    def complete_greet(self, text, line, begidx, endidx):
        if not text:
            completions = self.FRIENDS[:]
        else:
            completions = [ f
                            for f in self.FRIENDS
                            if f.startswith(text)
                            ]
        return completions

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    HelloWorld().cmdloop()

Se c'è un testo in input, complete_greet() restituisce un elenco di amici che corrispondono. Altrimenti viene restituita l'intera lista di amici.

$ python cmd_arg_completion.py
(Cmd) greet
Adam     Alice    Barbara  Bob
(Cmd) greet A
Adam   Alice
(Cmd) greet Ad
Ciao, Adam!

Se il testo passato non è nell'elenco di amici viene fornita la formula di saluto formale

(Cmd)greet Joe
Salve, Joe

Riscrittura dei Metodi Base della Classe

Cmd comprende parecchi metodi che possono essere riscritti come agganci per compiere azioni o per alterare il comportamento della classe base. Questo esempio non è esaustivo, ma contiene molti dei metodi che sono utili nell'uso comune.

import cmd

class Illustrate(cmd.Cmd):
    "Illustra l'uso dei metodi base della classe."

    def cmdloop(self, intro=None):
        print 'cmdloop(%s)' % intro
        return cmd.Cmd.cmdloop(self, intro)

    def preloop(self):
        print 'preloop()'

    def postloop(self):
        print 'postloop()'

    def parseline(self, line):
        print 'parseline(%s) =>' % line,
        ret = cmd.Cmd.parseline(self, line)
        print ret
        return ret

    def onecmd(self, s):
        print 'onecmd(%s)' % s
        return cmd.Cmd.onecmd(self, s)

    def emptyline(self):
        print 'emptyline()'
        return cmd.Cmd.emptyline(self)

    def default(self, line):
        print 'default(%s)' % line
        return cmd.Cmd.default(self, line)

    def precmd(self, line):
        print 'precmd(%s)' % line
        return cmd.Cmd.precmd(self, line)

    def postcmd(self, stop, line):
        print 'postcmd(%s, %s)' % (stop, line)
        return cmd.Cmd.postcmd(self, stop, line)

    def do_greet(self, line):
        print 'Salve,', line

    def do_EOF(self, line):
        "Exit"
        return True

if __name__ == '__main__':
    Illustrate().cmdloop('Illustrazione dei metodi di cmd.Cmd')

cmdloop() è il ciclo principale di elaborazione dell'interprete. Si può riscrivere, ma in genere non è necessario, visto che sono disponibili gli agganci con preloop() e postloop().

Ogni iterazione attraverso cmdloop() chiama onecmd() per inviare il comando al suo processore. L'effettiva riga di input viene elaborata da parseline() per creare una tupla contenente il comando e la parte rimasta della riga.

Se la riga è vuota, viene chiamato emptyline(). L'implementazione predefinita esegue nuovamente il comando precedente. Se la riga contiene un comando, prima viene chiamato precmd(), quindi viene cercato il processore e chiamato. Se non si trova nulla, viene chiamato default().

Ecco una sessione di esempio nella quale sono state aggiunte delle istruzioni print:

$ python cmd_illustrate_methods.py
cmdloop(Illustra l'uso dei metodi base della classe.)
preloop()
Illustra l'uso dei metodi base della classe.
(Cmd) greet Bob
precmd(greet Bob)
onecmd(greet Bob)
parseline(greet Bob) => ('greet', 'Bob', 'greet Bob')
Salve, Bob
postcmd(None, greet Bob)
(Cmd) ^Dprecmd(EOF)
onecmd(EOF)
parseline(EOF) => ('EOF', '', 'EOF')
postcmd(True, EOF)
postloop()

Configurare Cmd Tramite Attributi

Oltre ai metodi sopra descritti, ci sono parecchi attributi per controllare gli interpreti di comando.

prompt può essere impostato come stringa da stamparsi ogni volta che l'utente richiede un nuovo comando.

intro è il messaggio di benvenuto stampato all'inizio del programma. cmdloop() ottiene un parametro per questo valore, oppure si può impostarlo direttamente nelle classe.

Quando si stampa l'aiuto, gli attributi doc header, misc header, undoc header e ruler vengono usati per formattare l'output.

Questa classe di esempio mostra un processore di comando che consente all'utente di controllare il prompt per la sessione interattiva.

class HelloWorld(cmd.Cmd):
    """Semplice esempio di processore di comando."""

    prompt = 'prompt: '
    intro = "Semplice esempio di processore di comando."

    doc_header = 'doc_header'
    misc_header = 'misc_header'
    undoc_header = 'undoc_header'

    ruler = '-'

    def do_prompt(self, line):
        "Cambia il prompt interattivo"
        self.prompt = line + ': '

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    HelloWorld().cmdloop()
$ python cmd_attributes.py
Semplice esempio di processore di comando.
prompt: prompt hello
hello: help

doc_header
----------
prompt

undoc_header
------------
EOF  help

hello:

Uscire dalla Shell

Per integrare l'elaborazione standard del comando, Cmd comprende due prefissi speciali di comando. Un punto interrogativo (?) equivale al comando built-in di aiuto, e può essere usato allo stesso modo. Un punto esclamativo (!) è collegato a do_shell(), ed è concepito per eseguire altri comandi al di fuori della shell, come in questo esempio.

import cmd
import os

class ShellEnabled(cmd.Cmd):

    last_output = ''

    def do_shell(self, line):
        "Esegue un comando di shell"
        print "esecuzione di un comando di shell:", line
        output = os.popen(line).read()
        print output
        self.last_output = output

    def do_echo(self, line):
        "Stampa l'input, sostituendo '$out' con l'output dell'ultimo comando di shell"
        # Ovviamente non robusto
        print line.replace('$out', self.last_output)

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    ShellEnabled().cmdloop()
$ python cmd_do_shell.py
(Cmd) ?

Documented commands (type help ):
========================================
echo  shell

Undocumented commands:
======================
EOF  help

(Cmd) ? shell
Esegue un comando di shell
(Cmd) ? echo
Stampa l'input, sostituendo '' con l'output dell'ultimo comando di shell
(Cmd) shell pwd
esecuzione di un comando di shell: pwd
/home/robby/pydev/pymotw-it/dumpscripts

(Cmd) !pwd
esecuzione di un comando di shell: pwd
/home/robby/pydev/pymotw-it/dumpscripts

(Cmd) echo 
/home/robby/pydev/pymotw-it/dumpscripts

(Cmd)

Input Alternativi

Sebbene la modalità predefinita per Cmd() sia l'interazione con l'utente attraverso la libreria readline è tuttavia possibile passare una serie di comandi verso lo standard input usando la redirezione standard della shell Unix.

pydev/pymotw-it/dumpscripts/
robby@robby-desktop:~/pydev/pymotw-it/dumpscripts$ echo help | python cmd_do_help.py
(Cmd)
Documented commands (type help ):
========================================
greet

Undocumented commands:
======================
EOF  help

(Cmd)

Se si preferisce che sia il proprio programma a leggere il file script direttamente, allora potrebbe servire qualche altra modifica. Visto che readline interagisce con il dispositivo termnal/tty piuttosto che con il flusso standard di input, si dovrebbe disabilitarlo se si sa che il proprio script sarà letto da un file. Inotre, per evitare la stampa di prompt supreflui, si può impostare il prompt ad una stringa vuota. Questo esempio mostra come aprire un file e passarlo come input ad una versione modificata dell'esempio HelloWorld:

import cmd

class HelloWorld(cmd.Cmd):
    """Semplice esempio di processore di comando."""

    # Disabilita l'uso del modulo rawinput module
    use_rawinput = False

    # Non mostra il prompt dopo ogni lettura di comando
    prompt = ''

    def do_greet(self, line):
        print "Salve,", line

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    import sys
    input = open(sys.argv[1], 'rt')
    try:
        HelloWorld(stdin=input).cmdloop()
    finally:
        input.close()

Con use_rawinput impostato a False e prompt impostato ad una stringa vuota, si può chiamare lo script su questo file di input:

greet
greet Alice e Bob

per produrre un output tipo:

$ python cmd_file.py cmd_file.txt
Salve,
Salve, Alice e Bob

Comandi da sys.argv

Si possono anche elaborare dei parametri da riga di comando di un programma come comandi per la propria classe di interprete, invece che leggere comandi da stdin o da un file. Per usare i parametri di riga di comando, occorre chiamare onecmd() direttamente, come nell'esempio seguente:

import cmd

class InteractiveOrCommandLine(cmd.Cmd):
    """Accetta comandi tramite il normale prompt interattivo o da riga di comando"""

    def do_greet(self, line):
        print 'Salve,', line

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    import sys
    if len(sys.argv) > 1:
        InteractiveOrCommandLine().onecmd(' '.join(sys.argv[1:]))
    else:
        InteractiveOrCommandLine().cmdloop()

Visto che onecmd() riceve una singola stringa come input, i parametri per il programma devono essere uniti prima di essere passati.

$ python cmd_argv.py greet Utente di Riga di Comando
Salve, Utente di Riga di Comando
$ python cmd_argv.py
(Cmd) greet Utente Interattivo
Salve, Utente Interattivo
(Cmd)

Vedere anche:

cmd
La documentazione della libreria standard per questo modulo.
cmd2
Rimpiazzo drop-in per cmd con funzionalità aggiuntive.
GNU readline
La libreria GNU Readline fornisce funzioni che consentono all'utente di modificare le righe di input mentre vengono digiate.
readline
Libreria per la modifica del prompt interattivo