Scopo | Crea processori di riga di comando. |
Versione Python | 1.4 e successive, con aggiunte nella 2.3 |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo cmd
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.
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.
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.
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.
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) greetAdam 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
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()
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:
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)
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
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: