cmd - processori di riga di comando
Scopo: Crea processori di riga di comando.
Il modulo cmd contiene una classe pubblica: Cmd
, progettata per l'uso come classe base per shell interattive ed altri interpreti di comando. Nella modalità predefinita usa readline per la gestione del prompt interattivo, per la modifica della riga di comando e per il completamento del comando.
Elaborazione dei Comandi
Un interprete di comando creato con cmd 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 l'interprete della propria classe comprende un metodo chiamato do_foo()
, esso viene chiamato con "bar"
come unico argomento.
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" (saluta):
# cmd_simple.py
import cmd
class HelloWorld(cmd.Cmd):
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, e mostrare qualcuna delle caratteristiche di cui Cmd
è già dotato.
$ python3 cmd_simple.py (Cmd)
La prima cosa da notare è il prompt di comando, (Cmd)
. Il prompt può essere configurato attraverso l'attributo prompt
. Questo valore è dinamico, e se un gestore di comando cambia l'attributo, il nuovo valore è utilizzato per richiedere il comando successivo.
(Cmd) help Documented commands (type help <topic>): ======================================== help Undocumented commands: ====================== EOF greet
Il comando help
è costruito dentro Cmd
. Senza argomenti, mostra l'elenco dei comandi disponibili. Se nell'input si include il nome di un comando, 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 Salve
Se la propria classe non include uno specifico gestore per un comando, viene chiamato il metodo default()
con l'intero input della riga come argomento. 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$
Non viene stampata una nuova riga in uscita, quindi il risultato è un poco confuso.
Argomenti di cmd
Questo esempio comprende qualche miglioria per eliminare qualche fastidio ed aggiungere un aiuto per il comando greet
.
# cmd_arguments.py
import cmd
class HelloWorld(cmd.Cmd):
def do_greet(self, person):
"""greet [person]
Saluta la persona"""
if person:
print("Salve,", person)
else:
print('Salve')
def do_EOF(self, line):
return True
def postloop(self):
print()
if __name__ == '__main__':
HelloWorld().cmdloop()
La docstring aggiunta a do_greet()
diventa il testo di aiuto per il comando:
$ python3 cmd_arguments.py (Cmd) help Documented commands (type help <topic>): ======================================== greet help Undocumented commands: ====================== EOF (Cmd) help greet greet [person] Saluta la persona (Cmd)
Il risultato mostra un argomento opzionale per il comando greet
: person
. Sebbene l 'argomento sia opzionale per il comando c'è distinzione tra il comando ed il metodo di callback. Il metodo riceve sempre l' argomento, ma talvolta il valore è una stringa vuota. E' compito del gestore di comando determinare se un argomento 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 Salve, Alice (Cmd) greet Salve
A prescindere dal fatto che un argomento sia fornito dall'utente o meno, il valore passato al gestore di comando non comprende il comando stesso. Questo semplifica l'analisi al gestore di comando, specialmente 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 il codice dell'applicazione risulterebbe scarsamente formattato. Una soluzione migliore è implementare un gestore di aiuto per il comando greet
, chiamato help_greet()
. Il gestore di aiuto viene chiamato per produrre un testo di aiuto per il comando specificato.
# cmd_do_help.py
# Imposta gnureadline se readline è installato
try:
import gnureadline
import sys
sys.modules['readline'] = gnureadline
except ImportError:
pass
import cmd
class HelloWorld(cmd.Cmd):
def do_greet(self, person):
if person:
print("salve,", person)
else:
print('salve')
def help_greet(self):
print('\n'.join([
'greet [person]',
'Saluta la persona con il nome ricevuto',
]))
def do_EOF(self, line):
return True
if __name__ == '__main__':
HelloWorld().cmdloop()
In questo 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.
$ python3 cmd_do_help.py (Cmd) help greet greet [person] Saluta la persona con il nome ricevuto
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 il completamento del comando in base ai nomi dei comandi tramite metodi di gestione. L'utente attiva li completamento tramite la pressione del tasto tab al prompt. Se ci sono opzioni multiple di completamento, premendo tab due volte viene stampato un elenco di opzioni.
readline
non sono disponibili in tutte le piattaforme nella modalità predefinita. In questi casi il completamento via tab potrebbe non funzionare. Si faccia riferimento a readline per suggerimenti sull'installazione delle librerie necessarie se la propria installazione Python non ne dispone.$ python3 cmd_do_help.py (Cmd) <tab><tab> EOF greet help (Cmd) h<tab> (Cmd) help
Una volta che il comando viene riconosciuto, il completamento del argomento viene gestita dai metodi il cui nome inizia per complete_
. Questo consente ai gestori di completamento di assemblare un elenco di possibili completamenti usando criteri arbitrari (es. interrogare un database, cercare in un file o directory nel filesystem). 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 od anonimi. Un vero programma probabilmente salverebbero l'elenco da qualche parte, per leggerlo interamente la prima volta, quindi conservare quanto letto per poterlo scorrere se necessario.
# cmd_arg_completion.py
# Imposta gnureadline come readline se installato.
try:
import gnureadline
import sys
sys.modules['readline'] = gnureadline
except ImportError:
pass
import cmd
class HelloWorld(cmd.Cmd):
FRIENDS = ['Alice', 'Adam', 'Barbara', 'Bob']
def do_greet(self, person):
"Saluta la persona"
if person and person in self.FRIENDS:
greeting = 'Ciao, {}!'.format(person)
elif person:
greeting = 'Salve, {}'.format(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.
$ python3 cmd_arg_completion.py (Cmd) greet <tab><tab> Adam Alice Barbara Bob (Cmd) greet A<tab><tab> Adam Alice (Cmd) greet Ad<tab> 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.
# cmd_illustrate_methods.py
# Imposta gnureadline come readline se installato.
try:
import gnureadline
import sys
sys.modules['readline'] = gnureadline
except ImportError:
pass
import cmd
class Illustrate(cmd.Cmd):
"Illustra l'uso del metodo della classe base."
def cmdloop(self, intro=None):
print('cmdloop({})'.format(intro))
return cmd.Cmd.cmdloop(self, intro)
def preloop(self):
print('preloop()')
def postloop(self):
print('postloop()')
def parseline(self, line):
print('parseline({!r}) =>'.format(line), end='')
ret = cmd.Cmd.parseline(self, line)
print(ret)
return ret
def onecmd(self, s):
print('onecmd({})'.format(s))
return cmd.Cmd.onecmd(self, s)
def emptyline(self):
print('emptyline()')
return cmd.Cmd.emptyline(self)
def default(self, line):
print('default({})'.format(line))
return cmd.Cmd.default(self, line)
def precmd(self, line):
print('precmd({})'.format(line))
return cmd.Cmd.precmd(self, line)
def postcmd(self, stop, line):
print('postcmd({}, {})'.format(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ò sovrascrivere, 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 gestore. L'effettiva riga di input viene elaborata da parseline()
per creare una tupla contenente il comando e la parte rimanente 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 gestore e chiamato. Se non viene trovato, viene chiamato default()
. Infine viene invocato postcmd()
.
Ecco una sessione di esempio alla quale sono state aggiunte delle istruzioni print
:
$ python3 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 argomento 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 il risultato.
# cmd_attributes.py
import cmd
class HelloWorld(cmd.Cmd):
prompt = 'prompt: '
intro = "Esempio di semplice processore 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()
Questa classe di esempio mostra un gestore di comando che consente all'utente di controllare il prompt per la sessione interattiva.
$ python3 cmd_attributes.py Esempio di semplice processore comando. prompt: prompt salve salve: help doc_header ---------- help prompt undoc_header ------------ EOF salve:
Eseguire Comandi di Shell
Per integrare l'elaborazione standard del comando, Cmd
comprende due prefissi speciali di comando. Un punto interrogativo (?
) equivale al comando built-in help
, e può essere usato allo stesso modo. Un punto esclamativo (!
) è mappato a do_shell()
, ed è concepito per eseguire altri comandi al di fuori della shell, come in questo esempio.
# cmd_do_shell.py
import cmd
import subprocess
class ShellEnabled(cmd.Cmd):
last_output = ''
def do_shell(self, line):
"Esegue un comando shell"
print("esecuzione di comando shell:", line)
sub_cmd = subprocess.Popen(line,
shell=True,
stdout=subprocess.PIPE)
output = sub_cmd.communicate()[0].decode('utf-8')
print(output)
self.last_output = output
def do_echo(self, line):
"""Stampa l'input, sostituendo '$out' con
il risultato dell'ultimo comando shell
"""
# Ovviamente non robusto
print(line.replace('$out', self.last_output))
def do_EOF(self, line):
return True
if __name__ == '__main__':
ShellEnabled().cmdloop()
Questa implementazione del comando echo
sostituisce la stringa $out
nei suoi argomenti con il risultato dal comando shell precedente.
$ python3 cmd_do_shell.py (Cmd) ? Documented commands (type help <topic>): ======================================== echo help shell Undocumented commands: ====================== EOF (Cmd) ? shell Esegue un comando shell (Cmd) ? echo Stampa l'input, sostituendo '' con il risultato dell'ultimo comando shell (Cmd) shell pwd esecuzione di comando shell: pwd .../dumpscripts (Cmd) echo $out .../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.
$ echo help | python3 cmd_do_help.py (Cmd) Documented commands (type help <topic>): ======================================== greet help Undocumented commands: ====================== EOF (Cmd)
Se si preferisce che sia un programma a leggere il file script direttamente, potrebbe essere necessario qualche altro cambiamento. Visto che readline interagisce con il dispositivo termnal/tty piuttosto che con il flusso standard di input, si dovrebbe disabilitare quando lo script andrà a leggere da un file. Inoltre, per evitare la stampa di prompt superflui, 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
:
# cmd_file.py
import cmd
class HelloWorld(cmd.Cmd):
# Disabilita l'uso del modulo rawinput
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
with open(sys.argv[1], 'rt') as input:
HelloWorld(stdin=input).cmdloop()
Con use_rawinput
impostato a False e prompt
impostato ad una stringa vuota, si può chiamare lo script su questo file di input che contiene un comando su ogni riga.
# cmd_file.txt greet greet Alice e Bob
L'esecuzione dello script con il file sopra citato produce questo risultato:
$ python3 cmd_file.py cmd_file.txt Salve, Salve, Alice e Bob
Comandi da sys.argv
Anche gli argomenti da riga di comando per il programma possono essere elaborati come comandi per la classe dell'interprete, invece che leggere comandi dalla console o da un file. Per usare gli argomenti da riga di comando, occorre chiamare onecmd()
direttamente, come in questo esempio:
# cmd_argv.py
import cmd
class InteractiveOrCommandLine(cmd.Cmd):
"""Accetta comandi tramite il normale prompt
interattivo o sulla 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, gli argomenti per il programma devono essere uniti prima di essere passati.
$ python3 cmd_argv.py greet Utente da Riga di Comando Salve, Utente di Riga di Comando
$ python3 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 digitate.
- readline
- L'interfaccia della libreria standard di Python per readline
- subprocess
- Gestire altri processi ed i loro risultati