Când vine vorba de dezvoltarea de software, majoritatea instrumentelor pe care le avem la dispoziție sunt aplicații de linie de comandă. De asemenea, este bine de remarcat faptul că multe dintre instrumentele utilizate în linia de comandă sunt destul de puternice în ceea ce pot realiza, de la cele mai banale la cele mai plictisitoare. Mergând mai departe, puteți combina aplicațiile în linie de comandă pentru a înlănțui o secvență de lucru în vederea obținerii unui rezultat dorit. Învățarea comenzilor existente în linia de comandă vă permite să vă creșteți productivitatea și să înțelegeți mai bine ce capacități aveți la îndemână și ce sarcini ar putea fi necesar să implementați pe cont propriu.
Când proiectați un instrument în linie de comandă, trebuie să acordați cu adevărat atenție la cine, sau ce, este publicul țintă. De exemplu, dacă toate persoanele care vor utiliza acest instrument folosesc același sistem de operare și același mediu, atunci aveți cea mai mare flexibilitate în a profita de întregul ecosistem. Cu toate acestea, dacă trebuie să implementați aplicația dvs. în linie de comandă pentru a funcționa pe mai multe sisteme de operare, acum ați restricționat instrumentele pe care le puteți utiliza la ceea ce este disponibil pe toate aceste sisteme – sau trebuie să implementați în aplicația dvs. excepții pentru fiecare sistem de operare. (Acest lucru poate fi foarte anevoios de cercetat și de testat.)
Soluția cel mai des utilizată pentru a gestiona medii OS diverse este de a evita orice instrumente externe specifice sistemului de operare atunci când este posibil sau de a delega responsabilitatea către biblioteci care au făcut deja munca grea de a implementa caracteristici pentru a funcționa pentru mai multe arhitecturi. Cu cât mai mult puteți reduce implementarea la un singur limbaj de bază, fără a depinde de dependențe externe, cu atât mai ușor va fi să vă mențineți proiectul.
Componentele unei aplicații de linie de comandă în Ruby
Există trei zone de interes care trebuie abordate atunci când se construiește o aplicație de linie de comandă: intrarea, ieșirea și tot ceea ce se află între ele. Dacă dezvoltați un instrument unic de linie de comandă care preia pur și simplu o intrare sau o sursă și o procesează/formatează și scuipă informațiile, atunci munca dumneavoastră este destul de simplă. Cu toate acestea, dacă dezvoltați o interfață utilizator cu meniuri de navigat, lucrurile încep să devină mai complicate.
Procesarea intrărilor
Pentru a începe procesarea intrărilor, avem parametri care pot fi dați aplicației dumneavoastră. Aceste argumente reprezintă cea mai tipică modalitate de a începe o comandă, cu detalii despre modul în care doriți să se execute și să evitați necesitatea sistemelor de meniuri. În acest exemplu:
ruby -e "puts RUBY_VERSION"
Ruby este programul de linie de comandă care se execută, -e
se numește steguleț, iar "puts RUBY_VERSION"
este valoarea dată pentru steguleț. În acest caz, stegulețul -e
de la Ruby înseamnă că se execută următoarele ca și cod Ruby. Când execut cele de mai sus pe linia de comandă, primesc înapoi 2.4.0
tipărit pe ieșirea standard (care apare pur și simplu pe linia următoare).
Argument de intrare
Argumentul de intrare, sau parametrii, sunt toate bucățile suplimentare de text introduse după o comandă și separate printr-un spațiu. Aproape toate comenzile permit un argument de tip help flag. Un argument steguleț are o liniuță sau două în fața lui.
Stegulețele standard de ajutor sunt -h
, și --help
. Pe vremea MSDOS, era (și poate fi încă) /?
. Nu știu dacă tendința pentru stegulețe în Windows a continuat să folosească bară oblică ca marcator de stegulețe, dar scripturile de linie de comandă cross-platform din zilele noastre folosesc liniuțe.
În Ruby, datele de intrare de la o linie de comandă sunt plasate în două variabile diferite: ARGV și ARGF. ARGV gestionează parametrii de intrare ca o matrice de șiruri de caractere; ARGF este destinată gestionării fluxurilor de date. Puteți utiliza ARGV pentru parametri direct, dar acest lucru poate fi mai mult de lucru decât este necesar. Există câteva biblioteci Ruby construite pentru lucrul cu argumentele din linia de comandă.
OptionParser
OptionParser are avantajul de a fi inclus cu Ruby, deci nu este o dependență externă. OptionParser vă oferă o modalitate ușoară atât de a afișa ce opțiuni de linie de comandă sunt disponibile, cât și de a procesa datele de intrare în orice obiect Ruby doriți. Iată un extras dintr-unul dintre instrumentele mele de linie de comandă 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!
În acest exemplu, fiecare bloc opts.on
are cod de executat dacă steagul a fost trecut în linia de comandă. Ultimele patru opțiuni de jos sunt (în cadrul blocului lor) pur și simplu anexează informațiile despre steguleț într-un array pentru ca eu să le folosesc mai târziu.
Primul are Array
dat ca unul dintre parametrii pentru on
, astfel încât intrarea pentru acest steguleț va fi convertită într-un array Ruby și va fi stocată în hash-ul meu numit options
sub cheia filters
.
Celălalt parametru dat metodei on
sunt detaliile stegulețului și descrierea. Atât stegulețul scurt, cât și cel lung vor funcționa pentru a executa codul dat în următorul bloc.
OptionParser are, de asemenea, stegulețele -h
și --help
încorporate în mod implicit, așa că nu trebuie să reinventați roata. Pur și simplu tastați dfm -h
pentru instrumentul din linia de comandă de mai sus, iar acesta emite frumos o descriere utilă:
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 este un pic cam verbos atunci când vine vorba de scrierea definițiilor din linia de comandă. Gemul Slop este conceput pentru a vă permite să vă scrieți propria analiză a liniei de comandă cu mult mai puțin efort. În loc să trebuiască să furnizați blocurile de cod din definiția steagului, Slop creează pur și simplu un obiect pe care îl puteți interoga în aplicația dvs. pentru a vedea dacă a fost dat un steag și ce valoare (valori) este posibil să fi fost furnizate pentru acesta.
# 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 }
Această simplitate poate contribui la simplificarea bazei de cod și a suitei de teste, precum și la accelerarea timpului de dezvoltare.
Output (STDOUT)
Când scrieți un instrument simplu cu linie de comandă, deseori doriți ca acesta să emită rezultatele muncii efectuate. Ținând cont de ținta acestui instrument, veți determina în mare măsură modul în care doriți să arate ieșirea.
Ați putea formata datele de ieșire ca un simplu șir de caractere, o listă, un hash, o matrice imbricata, JSON, XML sau o altă formă de date care să fie consumate. Dacă datele dvs. vor fi transmise în flux prin intermediul unei conexiuni de rețea, atunci veți dori să împachetați datele într-un șir strâns de date. Dacă sunt destinate să fie văzute de ochii unui utilizator, atunci veți dori să le extindeți într-un mod prezentabil.
Multe instrumente existente în linie de comandă Linux/Mac pot imprima detalii în perechi sau seturi de valori. Informațiile pot fi separate prin două puncte, spații, tabulări și blocuri de indentare. Atunci când nu sunteți sigur de modul exact în care pot fi utilizate, optați pentru cel mai simplu și mai prezentabil mod de prezentare a datelor.
Un exemplu de țintă pe care ar putea fi necesar să o luați în considerare este un instrument de testare API. Multe API-uri oferă un răspuns JSON și pot fi accesate cu un instrument de linie de comandă precum curl
. Dacă lățimea de bandă este o preocupare, atunci folosiți metoda to_json
de la JSON, dar dacă este destinată lucrului pe o mașină locală, folosiți 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"# }# }
În mod similar, puteți folosi YAML pentru date.
require 'yaml'puts x.to_yaml# ---# hello: world# :this:# apple: 4# :tastes: delicious
Dacă doriți să aveți o ieșire mai complexă, frumos formatată, atunci vă recomand să folosiți gemul awesome_print. Aceasta vă va oferi un control specific al prezentării pe ieșire.
Error standard (STDERR)
Este un alt tip de ieșire care poate apărea din aplicațiile de linie de comandă. Atunci când ceva nu este în regulă sau a mers prost, se obișnuiește ca instrumentul de linie de comandă să scrie la ieșirea cunoscută sub numele de STDERR. Aceasta va apărea ca o ieșire obișnuită, dar alte instrumente pot verifica faptul că comanda nu a avut succes.
STDERR.puts "Oops! You broke it!"
Practica mai obișnuită este de a utiliza un Logger pentru a scrie detalii specifice despre erori. Puteți direcționa acest lucru către ieșirea STDERR pe linia de comandă sau, eventual, către un fișier jurnal.
Interfață utilizator (STDIN și STDOUT)
Scrierea unei interfețe utilizator poate fi unul dintre cele mai satisfăcătoare lucruri de realizat. Ea vă permite să aplicați un design artistic și să oferiți diferite moduri de interacțiune pentru utilizator.
Interfața utilizator minimă este o afișare a unui text cu un prompt care așteaptă să fie introdus. Poate arăta la fel de simplu ca:
Who is your favorite super hero /Batman/Wonder-Woman ?
Întrebarea de mai sus indică întrebarea, opțiunile și o opțiune implicită în cazul în care utilizatorul alege să apese tasta enter fără a introduce nimic. În Ruby simplu, acest lucru ar arăta ca:
favorite = "Superman"printf "Who is your favorite hero /Batman/Wonder Woman?"input = gets.chompfavorite = input unless input.empty?
Este un cod foarte rudimentar pentru a fi folosit pentru intrare și ieșire, iar dacă aveți de gând să scrieți o interfață de utilizator, vă recomand să încercați o bijuterie precum highline sau tty.
Cu gemul highline, ați putea scrie cele de mai sus ca:
require 'highline/import'favorite = ask("Who is your favorite hero Superman/Batman/Wonder Woman?") {|question| question.in = question.default = "Superman"}
Aici, highline va afișa întrebarea, va afișa opțiunea implicită și va prinde orice opțiune incorectă introdusă, notificând utilizatorul că nu a selectat una dintre opțiunile furnizate.
Atât highline, cât și tty au o cantitate imensă de lucruri suplimentare pe care le puteți face pentru un meniu și o experiență de utilizare, cu lucruri drăguțe, cum ar fi adăugarea de culori la afișaj. Dar, din nou, trebuie să luați în considerare care este publicul țintă.
Cu cât oferiți o experiență vizuală mai mare, cu atât mai mult trebuie să acordați atenție capacităților cross-platform ale acestor instrumente. Nu toate liniile de comandă tratează aceleași date de prezentare în același mod, ceea ce creează o experiență proastă pentru utilizator.
Compatibilitate între platforme
Veștile bune sunt că Ruby are multe dintre soluțiile necesare pentru instrumente pe diferite sisteme de operare. Atunci când Ruby este compilat pentru un anumit sistem de operare, fișierul sursă pentru RbConfig este generat cu valori absolute native pentru sistemul pe care a fost construit, salvate în format hash. Prin urmare, este cheia pentru detectarea și utilizarea caracteristicilor sistemului de operare.
Pentru a vedea acest fișier cu editorul de text preferat, puteți rula următorul cod Ruby:
editor = "sublime" # your preferred editor hereexec "#{editor} #{RbConfig.method(:ruby).source_location}"
Aceasta vă va arăta totul într-o manieră prezentabilă, ceea ce consider că este mai bine decât să vedeți hash-ul prin RbConfig::CONFIG
. Acest hash include lucruri utile, cum ar fi comenzile folosite pentru manipularea sistemului de fișiere, versiunea Ruby, arhitectura sistemului, unde se află tot ce este important pentru Ruby și alte lucruri de acest gen.
Pentru a verifica sistemul de operare, puteți face:
case RbConfig::CONFIGwhen /mingw32|mswin/ # Probably a Windows operating systemelse # Most likely a Unix compatible system like BSD/Mac/Linuxend
Pentru alte valori specifice sistemului de operare, vă puteți uita în codul sursă pentru Gem::Platform.
Acum, comenzile specifice sistemului de fișiere stocate aici nu sunt menite să fie folosite din această resursă. Ruby are clasele Dir, File și Pathname scrise pentru asta.
Când aveți nevoie să știți dacă un executabil există în PATH-ul sistemului, atunci veți dori să folosiți MakeMakefile.find_executable. Ruby suportă construirea de extensii C și una dintre caracteristicile drăguțe pe care le-au adăugat pentru asta este această abilitate de a afla dacă există executabilul pe care să îl apelați.
Dar acest lucru va genera un fișier jurnal în directorul curent al sistemului de fiecare dată când îl rulați. Deci, pentru a evita scrierea acestui fișier jurnal, va trebui să faceți următoarele:
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
Când desenați un meniu vizual cu fereastră pe o linie de comandă, este de preferat ca meniul să rămână într-o poziție fixă atunci când afișajul se actualizează. Cel mai simplu mod de a face acest lucru este să ștergeți linia de comandă între fiecare scriere a afișajului complet.
În exemplul de mai sus, am căutat comanda clear
. Pe Windows, comanda shell pentru ștergerea liniei de comandă este cls
, dar nu veți putea găsi un executabil pentru aceasta, deoarece face parte din codul intern al command.com
.
Veste bună este că prinderea șirului de ieșire de la comanda clear
de pe Linux produce această secvență de cod de evacuare: \e[3J\e[H\e[2J
. Eu și alții am testat acest lucru pe Windows, Mac și Linux, iar acest lucru face exact ceea ce ne dorim: curățarea ecranului pentru a fi redesenat pe el.
Acest șir de escape are trei acțiuni diferite pe care le execută:
CLEAR = (ERASE_SCOLLBACK = "\e[3J") + (CURSOR_HOME = "\e[H") + (ERASE_DISPLAY = "\e[2J")
Cu toate acestea, este de preferat să folosiți o bibliotecă de linie de comandă decât să scrieți singuri codurile de escape. Dar acesta merită menționat.
Testarea STDIN/STDOUT/STDERR
Cu orice lucru care scrie pe ceva de genul STDOUT sau STDERR, ar fi cel mai înțelept să creați o variabilă internă în obiectul Ruby UI care poate fi modificată cu un parametru opțional la new
. Astfel, atunci când programul dvs. rulează în mod normal, valoarea va fi STDOUT
. Dar când scrieți teste, veți trece StringIO.new
, pe care o puteți testa cu ușurință.
Când încercați să citiți IO de la o comandă executată în afara lui Ruby, testarea este un pic mai complicată. Probabil că va trebui să vă uitați la Open3.popen3 pentru a gestiona fiecare flux IO STDIN, STDOUT și STDERR.
Utilizarea comenzilor Bash
Bash a ajuns în Windows (WSL)! Acest lucru înseamnă că aveți acces la comenzile Bash în marea majoritate a sistemelor de operare utilizate la nivel mondial. Acest lucru deschide mai multe posibilități pentru propriul dvs. instrument de linie de comandă.
Este tipic să „pipezi” comenzile între ele, trimițând ieșirea unei comenzi ca un flux către următoarea. Știind acest lucru, este posibil să doriți să luați în considerare adăugarea suportului de intrare în flux sau să vă gândiți cum să vă formatați mai bine ieșirea pentru consumul altor instrumente de linie de comandă.
Iată un exemplu de substituție regex cu comanda sed
a lui Bash care primește intrarea sa canalizată de la echo:
echo hello | sed "s/ll/ck n/g"
Aceasta scoate heck no
. Cu cât învățați mai multe despre Bash, cu atât veți fi mai bine pregătiți pentru a realiza lucruri în linia de comandă și cu atât veți fi mai bine echipați pentru a scrie propriile instrumente de linie de comandă mai bune.
Rezumat
Există multe de învățat atunci când vine vorba de scrierea propriilor instrumente de linie de comandă. Cu cât trebuie să țineți cont de mai multe situații, cu atât lucrurile devin mai complexe.
Dă-ți timp să înveți instrumentele pe care le ai la îndemână și vei culege multe recompense pe care nu știai că le pierzi. Vă doresc toate cele bune!
.