Quando si tratta di sviluppo software, la maggior parte degli strumenti a nostra disposizione sono applicazioni a riga di comando. Vale anche la pena notare che molti degli strumenti usati sulla linea di comando sono abbastanza potenti in ciò che possono realizzare, dal banale al tedioso. Andando oltre, si possono combinare applicazioni a riga di comando per concatenare una sequenza di lavoro per ottenere un risultato desiderato. Imparare i comandi a riga di comando esistenti vi permette di aumentare la vostra produttività e capire meglio quali capacità sono a portata di mano e quali compiti potreste aver bisogno di implementare per conto vostro.
Quando progettate uno strumento a riga di comando, dovete davvero prestare attenzione a chi, o cosa, è il vostro pubblico target. Per esempio, se tutte le persone che useranno questo strumento usano lo stesso sistema operativo e lo stesso ambiente, allora avrete la massima flessibilità nel trarre vantaggio dall’intero ecosistema. Tuttavia se avete bisogno di implementare la vostra applicazione a riga di comando per lavorare su più sistemi operativi, ora avete ristretto gli strumenti che potete usare a ciò che è disponibile su tutti questi sistemi — o dovete implementare eccezioni per ogni sistema operativo nella vostra applicazione. (Questo può essere molto noioso da ricercare e testare.)
La soluzione più spesso usata per gestire diversi ambienti OS è quella di evitare qualsiasi strumento esterno specifico per OS quando possibile o delegare la responsabilità alle librerie che hanno già fatto il duro lavoro di implementazione delle caratteristiche per lavorare su più architetture. Più riesci a mantenere l’implementazione in un solo linguaggio di base senza dipendere da dipendenze esterne, più facile sarà mantenere il tuo progetto.
I componenti di un’applicazione a riga di comando in Ruby
Ci sono tre aree di interesse da affrontare quando si costruisce un’applicazione a riga di comando: l’input, l’output, e tutto ciò che sta in mezzo. Se state sviluppando un unico strumento a riga di comando che prende semplicemente un input o una fonte, lo elabora/formatta e sputa fuori le informazioni, allora il vostro lavoro è piuttosto semplice. Tuttavia, se stai sviluppando un’interfaccia utente con menu da navigare, le cose iniziano a diventare più complicate.
Elaborazione dell’input
Per iniziare ad elaborare l’input, abbiamo dei parametri che possono essere dati alla tua applicazione. Questi argomenti sono il modo più tipico di iniziare un comando, con dettagli su come si vuole che venga eseguito ed evitare la necessità di sistemi di menu. In questo esempio:
ruby -e "puts RUBY_VERSION"
Ruby è il programma a riga di comando da eseguire, -e
è chiamato flag, e "puts RUBY_VERSION"
è il valore dato per il flag. In questo caso, il flag -e
di Ruby significa eseguire quanto segue come codice Ruby. Quando eseguo quanto sopra sulla linea di comando, ottengo 2.4.0
stampato sullo standard output (che mostra semplicemente sulla riga successiva).
Argument input
Argument input, o parametri, sono tutti i bit extra di testo inseriti dopo un comando e separati da uno spazio. Quasi tutti i comandi permettono un argomento con una bandiera di aiuto. Un argomento bandiera ha un trattino o due davanti ad esso.
Le bandiere di aiuto standard sono -h
e --help
. Ai tempi di MSDOS, era (e potrebbe ancora essere) /?
. Non so se la tendenza per le bandiere in Windows ha continuato ad usare il forward-slash come marcatore di bandiera, ma gli script a riga di comando multipiattaforma in questi giorni usano i trattini.
In Ruby, l’input da una riga di comando è posto in due diverse variabili: ARGV e ARGF. ARGV gestisce i parametri di input come un array di stringhe; ARGF è per la gestione dei flussi di dati. Potete usare ARGV per i parametri direttamente, ma questo potrebbe essere più lavoro del necessario. Ci sono un paio di librerie Ruby costruite per lavorare con gli argomenti della linea di comando.
OptionParser
OptionParser ha il vantaggio di essere incluso in Ruby, quindi non è una dipendenza esterna. OptionParser ti dà un modo semplice per visualizzare le opzioni della linea di comando disponibili e per elaborare l’input in qualsiasi oggetto Ruby tu voglia. Ecco un estratto da uno dei miei strumenti a riga di comando dfm:
require 'optionparser'options = {}printers = Array.newOptionParser.new do |opts| opts.banner = "Usage: dfm \nDefaults: dfm -xd ." + File::SEPARATOR opts.on("-f", "--filters FILTERS", Array, "File extension filters") do |filters| options = filters end opts.on("-x", "--duplicates-hex", "Prints duplicate files by MD5 hexdigest") do |dh| printers << "dh" end opts.on("-d", "--duplicates-name", "Prints duplicate files by file name") do |dh| printers << "dn" end opts.on("-s", "--singles-hex", "Prints non-duplicate files by MD5 hexdigest") do |dh| printers << "sh" end opts.on("-n", "--singles-name", "Prints non-duplicate files by file name") do |dh| printers << "sn" endend.parse!
In questo esempio, ogni blocco opts.on
ha del codice da eseguire se la bandiera è stata passata sulla riga di comando. Le quattro opzioni in basso (all’interno del loro blocco) stanno semplicemente aggiungendo le informazioni della bandiera in un array da usare in seguito.
La prima ha Array
dato come uno dei parametri a on
, quindi l’input per questa bandiera sarà convertito in un array Ruby e memorizzato nel mio hash chiamato options
sotto la chiave filters
.
Il resto dei parametri dati al metodo on
sono i dettagli della bandiera e la descrizione. Sia il flag breve che quello lungo funzioneranno per eseguire il codice dato nel blocco seguente.
OptionParser ha anche i flag -h
e --help
incorporati di default, quindi non è necessario reinventare la ruota. Digita semplicemente dfm -h
per lo strumento a riga di comando di cui sopra, e ti darà una descrizione utile:
Usage: dfm Defaults: dfm -xd ./ -f, --filters FILTERS File extension filters -x, --duplicates-hex Prints duplicate files by MD5 hexdigest -d, --duplicates-name Prints duplicate files by file name -s, --singles-hex Prints non-duplicate files by MD5 hexdigest -n, --singles-name Prints non-duplicate files by file name
Slop
OptionParser è un po’ prolisso quando si tratta di scrivere le definizioni della riga di comando. La gemma Slop è progettata per permettervi di scrivere il vostro parsing a riga di comando con molto meno sforzo. Invece di farvi fornire i blocchi di codice nella definizione del flag, Slop crea semplicemente un oggetto che potete interrogare nella vostra applicazione per vedere se un flag è stato dato e quale valore(i) può essere stato fornito per esso.
# Excerpt from https://github.com/leejarvis/slopopts = Slop.parse do |o| o.string '-h', '--host', 'a hostname' o.integer '--port', 'custom port', default: 80 o.bool '-v', '--verbose', 'enable verbose mode' o.bool '-q', '--quiet', 'suppress output (quiet mode)' o.bool '-c', '--check-ssl-certificate', 'check SSL certificate for host' o.on '--version', 'print the version' do puts Slop::VERSION exit endendARGV #=> -v --host 192.168.0.1 --check-ssl-certificateopts #=> 192.168.0.1opts.verbose? #=> trueopts.quiet? #=> falseopts.check_ssl_certificate? #=> trueopts.to_hash #=> { host: "192.168.0.1", port: 80, verbose: true, quiet: false, check_ssl_certificate: true }
Questa semplicità può aiutare a semplificare il vostro codice base e la vostra suite di test, così come ad accelerare il vostro tempo di sviluppo.
Output (STDOUT)
Quando si scrive un semplice strumento a riga di comando, spesso si desidera che esso produca i risultati del lavoro svolto. Tenere a mente qual è l’obiettivo di questo strumento determinerà in gran parte il modo in cui vuoi che l’output appaia.
Potresti formattare i dati di output come una semplice stringa, lista, hash, array annidato, JSON, XML, o altra forma di dati da consumare. Se i vostri dati saranno trasmessi in streaming su una connessione di rete, allora vorrete impacchettare i dati in una stringa di dati ben compressa. Se sono destinati agli occhi di un utente, allora vorrete espanderli in un modo presentabile.
Molti strumenti a riga di comando Linux/Mac esistenti possono stampare dettagli in coppie o insiemi di valori. Le informazioni possono essere separate da due punti, spazi, tabulazioni e blocchi di indentazione. Quando non sei sicuro di come possa essere usato esattamente, scegli il modo più semplice e presentabile di presentare i dati.
Un esempio di un obiettivo che potresti dover considerare è uno strumento di test API. Molte API forniscono una risposta JSON e vi si può accedere con uno strumento a riga di comando come curl
. Se la larghezza di banda è una preoccupazione, allora usa il metodo to_json
di JSON, ma se è destinato al lavoro su una macchina locale, usa pretty_generate
.
x = {"hello" => "world", this: {"apple" => 4, tastes: "delicious"}}require 'json'puts x.to_json# {"hello":"world","this":{"apple":4,"tastes":"delicious"}}puts JSON.pretty_generate( x )# {# "hello": "world",# "this": {# "apple": 4,# "tastes": "delicious"# }# }
Puoi anche usare YAML per i dati.
require 'yaml'puts x.to_yaml# ---# hello: world# :this:# apple: 4# :tastes: delicious
Se vuoi avere un output più complesso e ben formattato, allora ti consiglio vivamente di usare la gemma awesome_print. Questo vi darà un controllo specifico della vostra presentazione sull’output.
Standard Error (STDERR)
Questo è un altro tipo di output che può verificarsi dalle applicazioni a riga di comando. Quando qualcosa è sbagliato o è andato storto, è consuetudine che lo strumento a riga di comando scriva sull’output noto come STDERR. Apparirà come output regolare, ma altri strumenti possono verificare che il comando non ha avuto successo.
STDERR.puts "Oops! You broke it!"
La pratica più comune è quella di utilizzare un Logger per scrivere dettagli specifici sugli errori. Potete indirizzarli all’output STDERR sulla linea di comando o eventualmente a un file di log.
Interfaccia utente (STDIN e STDOUT)
Scrivere un’interfaccia utente può essere una delle cose più gratificanti da realizzare. Ti permette di applicare un po’ di design artistico e fornire diversi modi per l’utente di interagire.
L’interfaccia utente minima è una visualizzazione di un testo con un prompt in attesa di input. Può apparire semplice come:
Who is your favorite super hero /Batman/Wonder-Woman ?
Il precedente indica la domanda, le opzioni e un’opzione predefinita nel caso in cui l’utente scelga di premere il tasto invio senza inserire nulla. In Ruby, questo sarebbe simile a:
favorite = "Superman"printf "Who is your favorite hero /Batman/Wonder Woman?"input = gets.chompfavorite = input unless input.empty?
Questo è un codice molto rozzo da usare per l’input e l’output, e se avete intenzione di scrivere un’interfaccia utente, vi consiglio vivamente di provare una gemma come highline o tty.
Con la gemma highline, potreste scrivere quanto sopra come:
require 'highline/import'favorite = ask("Who is your favorite hero Superman/Batman/Wonder Woman?") {|question| question.in = question.default = "Superman"}
Qui, highline mostrerà la domanda, mostrerà il default, e catturerà ogni opzione errata inserita, notificando all’utente che non ha selezionato una delle opzioni fornite.
Sia highline che tty hanno una vasta quantità di cose aggiuntive che potete fare per un menu e un’esperienza utente, con sottigliezze come aggiungere colori al vostro display. Ma ancora una volta devi prendere in considerazione chi è il tuo pubblico di riferimento.
Più di un’esperienza visiva fornisci, più hai bisogno di prestare attenzione alle capacità multipiattaforma di questi strumenti. Non tutte le linee di comando gestiscono gli stessi dati di presentazione allo stesso modo, il che crea una cattiva esperienza per l’utente.
Compatibilità multipiattaforma
La grande notizia è che Ruby ha molte delle soluzioni necessarie per gli strumenti su diversi sistemi operativi. Quando Ruby viene compilato per uno specifico sistema operativo, il file sorgente per RbConfig viene generato con valori assoluti nativi del sistema su cui è stato costruito, salvato in formato hash. È quindi la chiave per rilevare e utilizzare le caratteristiche del sistema operativo.
Per vedere questo file con il vostro editor di testo preferito, potete eseguire il seguente codice Ruby:
editor = "sublime" # your preferred editor hereexec "#{editor} #{RbConfig.method(:ruby).source_location}"
Questo vi mostrerà tutto in modo presentabile, che ritengo sia meglio che vedere l’hash tramite RbConfig::CONFIG
. Questo hash include cose utili come i comandi usati per gestire il filesystem, la versione di Ruby, l’architettura del sistema, dove risiede tutto ciò che è importante per Ruby, e altre cose del genere.
Per controllare il sistema operativo, puoi fare:
case RbConfig::CONFIGwhen /mingw32|mswin/ # Probably a Windows operating systemelse # Most likely a Unix compatible system like BSD/Mac/Linuxend
Per altri valori specifici del sistema operativo, puoi guardare il codice sorgente di Gem::Platform.
Ora, i comandi specifici del filesystem memorizzati qui non devono essere usati da questa risorsa. Ruby ha scritto le classi Dir, File e Pathname per questo.
Quando avete bisogno di sapere se un eseguibile esiste nel PATH del sistema, allora vorrete usare MakeMakefile.find_executable. Ruby supporta la costruzione di estensioni C e una delle belle caratteristiche che hanno aggiunto per questo è questa capacità di scoprire se esiste l’eseguibile da chiamare.
Ma questo genererà un file di log nella directory corrente del vostro sistema ogni volta che lo eseguite. Quindi, per evitare di scrivere questo file di log, dovrai fare quanto segue:
require 'mkmf'MakeMakefile::Logging.instance_variable_set(:@log, File.open(File::NULL, 'w'))executable = MakeMakefile.find_executable('clear') # or whatever executable you're looking for
Quando disegni un menu visivo a finestra su una linea di comando, è preferibile che il menu rimanga in una posizione fissa quando il display si aggiorna. Il modo più semplice per farlo è cancellare la linea di comando tra ogni scrittura del display completo.
Nell’esempio sopra, ho cercato il comando clear
. Su Windows, il comando della shell per cancellare la riga di comando è cls
, ma non sarete in grado di trovare un eseguibile per questo perché fa parte del codice interno di command.com
.
La buona notizia è che prendere l’output della stringa dal comando clear
di Linux produce questa sequenza di codice di escape: \e[3J\e[H\e[2J
. Io e altri lo abbiamo testato su Windows, Mac e Linux, e questo fa esattamente quello che vogliamo: pulire lo schermo per ridisegnarlo.
Questa stringa di escape ha tre diverse azioni che sta eseguendo:
CLEAR = (ERASE_SCOLLBACK = "\e[3J") + (CURSOR_HOME = "\e[H") + (ERASE_DISPLAY = "\e[2J")
Tuttavia, è preferibile usare una libreria a riga di comando piuttosto che scrivere i codici di escape da soli. Ma questo vale la pena di menzionarlo.
Testing STDIN/STDOUT/STDERR
Con qualsiasi cosa che scrive su qualcosa come STDOUT o STDERR, sarebbe più saggio creare una variabile interna nel vostro oggetto Ruby UI che può essere cambiata con un parametro opzionale a new
. Così quando il vostro programma viene eseguito normalmente, il valore sarà STDOUT
. Ma quando scrivi i test, passerai in StringIO.new
, che puoi facilmente testare.
Quando cerchi di leggere l’IO da un comando eseguito fuori da Ruby, i test sono un po’ più coinvolti. Avrai probabilmente bisogno di guardare in Open3.popen3 per gestire ogni flusso IO STDIN, STDOUT, e STDERR.
Utilizzando i comandi Bash
Bash è arrivato su Windows (WSL)! Questo significa che hai accesso ai comandi bash nella stragrande maggioranza dei sistemi operativi usati in tutto il mondo. Questo apre più possibilità per il tuo strumento a riga di comando.
È tipico “tubare” i comandi tra loro, inviando l’output di un comando come un flusso al successivo. Sapendo questo, potresti voler considerare l’aggiunta di un supporto per l’input in streaming, o pensare a come formattare meglio il tuo output per il consumo di altri strumenti a riga di comando.
Ecco un esempio di sostituzione regex con il comando sed
di Bash che riceve il suo input in pipe da echo:
echo hello | sed "s/ll/ck n/g"
Questo produce heck no
. Più si impara su Bash, meglio si è in grado di ottenere risultati su una linea di comando e meglio si è equipaggiati per scrivere migliori strumenti a riga di comando per conto proprio.
Sommario
C’è molto da imparare quando si tratta di scrivere i propri strumenti a riga di comando. Più situazioni devi tenere conto, più le cose diventano complesse.
Prendi il tempo per imparare gli strumenti che hai a portata di mano, e raccoglierai molte ricompense che non sapevi di perdere. Vi auguro tutto il meglio!
.