- Overview
- Text vs. Whatever
- Cuvinte duble în jurul argumentelor
- List Unique Species
- Soluție
- De ce să înregistrăm comenzile în istoric înainte de a le executa?
- Soluție
- Nelle’s Pipeline: Crearea unui script
- Variabile în scripturile shell
- Soluție
- Găsește cel mai lung fișier cu o anumită extensie
- Soluție
- Comprehensiunea de citire a scripturilor
- Soluții
- Debugging Scripts
- Soluție
- Puncturi cheie
Overview
Predare: 30 min
Exerciții: 15 minÎntrebări
Cum pot salva și reutiliza comenzile?
Obiective
Scrieți un script shell care execută o comandă sau o serie de comenzi pentru un set fix de fișiere.
Executați un script shell din linia de comandă.
Scrieți un script shell care operează asupra unui set de fișiere definit de utilizator în linia de comandă.
Creați pipe-line-uri care includ scripturi shell scrise de dumneavoastră și de alții.
În sfârșit suntem gata să vedem ce face din shell un mediu de programare atât de puternic.Vom lua comenzile pe care le repetăm frecvent și le vom salva în fișierepentru ca mai târziu să putem rula din nou toate aceste operații prin tastarea unei singure comenzi.Din motive istorice,o grămadă de comenzi salvate într-un fișier se numește de obicei script shell,dar nu vă înșelați:acestea sunt de fapt mici programe.
Să începem prin a ne întoarce la
molecules/și să creăm un nou fișier,middle.shcare va deveni scriptul nostru shell:$ cd molecules$ nano middle.shComanda
nano middle.shdeschide fișierulmiddle.shîn editorul de text ‘nano'(care rulează în shell).Dacă fișierul nu există, acesta va fi creat. putem folosi editorul de text pentru a edita direct fișierul – vom introduce pur și simplu următoarea linie:head -n 15 octane.pdb | tail -n 5Aceasta este o variație a pipe-ului pe care l-am construit mai devreme:selectează liniile 11-15 din fișierul
octane.pdb.Țineți minte, încă nu o executăm ca o comandă:punem comenzile într-un fișier.Apoi salvăm fișierul (
Ctrl-Oîn nano), și ieșim din editorul de text (Ctrl-Xîn nano).Verificați că directorulmoleculesconține acum un fișier numitmiddle.sh.După ce am salvat fișierul,putem cere shell-ului să execute comenzile pe care le conține.Shell-ul nostru se numește
bash, așa că executăm următoarea comandă:$ bash middle.shATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00Sigur, ieșirea script-ului nostru este exact ceea ce am obține dacă am rula direct acea conductă.
Text vs. Whatever
De obicei numim programe precum Microsoft Word sau LibreOffice Writer „texteditors”, dar trebuie să fim puțin mai atenți când vine vorba deprogramare. În mod implicit, Microsoft Word utilizează fișiere
.docxpentru a stoca nu numai text, ci și informații de formatare privind fonturile, titlurile și, în curând. Aceste informații suplimentare nu sunt stocate sub formă de caractere și nu înseamnă nimic pentru instrumente precumhead: acestea se așteaptă ca fișierele de intrare să nu conțină decât literele, cifrele și semnele de punctuație de pe o tastatură standard de calculator. Prin urmare, atunci când editați programe, trebuie fie să folosiți un editor de text în clar, fie să aveți grijă să salvați fișierele ca text în clar.Ce se întâmplă dacă vrem să selectăm linii dintr-un fișier arbitrar?Am putea edita
middle.shde fiecare dată pentru a schimba numele fișierului,dar acest lucru ar dura probabil mai mult decât să scriem din nou comanda în shell și să o executăm cu un nou nume de fișier.În schimb, haideți să editămmiddle.shși să o facem mai versatilă:$ nano middle.shAcum, în „nano”, înlocuiți textul
octane.pdbcu variabila specială numită:head -n 15 "" | tail -n 5În interiorul unui script shell,
înseamnă „primul nume de fișier (sau alt argument) de pe linia de comandă”.Acum putem rula scriptul nostru astfel:$ bash middle.sh octane.pdbATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00sau pe un alt fișier astfel:
$ bash middle.sh pentane.pdbATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00Cuvinte duble în jurul argumentelor
Pentru același motiv pentru care punem variabila de buclă între ghilimele duble, în cazul în care numele fișierului conține spații, înconjurăm
cu ghilimele duble.În prezent, trebuie să modificăm
middle.shde fiecare dată când dorim să ajustăm intervalul de linii care este returnat. Să rezolvăm acest lucru prin configurarea scriptului nostru pentru a utiliza în schimb trei argumente în linia de comandă. După primul argument din linia de comandă (), fiecare argument suplimentar pe care îl furnizăm va fi accesibil prin intermediul variabilelor speciale,,, care se referă la primul, al doilea, respectiv al treilea argument din linia de comandă.Cunoscând acest lucru, putem folosi argumente suplimentare pentru a defini intervalul de linii care vor fi transmise la
headși, respectiv,tail:$ nano middle.shhead -n "" "" | tail -n ""Acum putem rula:
$ bash middle.sh pentane.pdb 15 5ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00Schimbând argumentele la comanda noastră putem schimba comportamentul scriptului nostru:
$ bash middle.sh pentane.pdb 20 5ATOM 14 H 1 -1.259 1.420 0.112 1.00 0.00ATOM 15 H 1 -2.608 -0.407 1.130 1.00 0.00ATOM 16 H 1 -2.540 -1.303 -0.404 1.00 0.00ATOM 17 H 1 -3.393 0.254 -0.321 1.00 0.00TER 18 1Acest lucru funcționează, dar s-ar putea ca următoarea persoană care citește
middle.shsă aibă nevoie de un moment pentru a-și da seama ce face.Putem îmbunătăți scriptul nostru adăugând câteva comentarii în partea de sus:$ nano middle.sh# Select lines from the middle of a file.# Usage: bash middle.sh filename end_line num_lineshead -n "" "" | tail -n ""Un comentariu începe cu un caracter
#și merge până la sfârșitul liniei.Calculatorul ignoră comentariile,dar acestea sunt de neprețuit pentru a ajuta oamenii (inclusiv pe viitorul dumneavoastră) să înțeleagă și să folosească scripturile.Singurul avertisment este că de fiecare dată când modificați scriptul,ar trebui să verificați dacă comentariul este încă corect:o explicație care trimite cititorul într-o direcție greșită este mai rea decât niciuna.Ce se întâmplă dacă vrem să procesăm mai multe fișiere într-o singură conductă?De exemplu, dacă vrem să sortăm fișierele noastre
.pdbîn funcție de lungime, vom tasta:$ wc -l *.pdb | sort -npentru că
wc -lenumeră numărul de linii din fișiere(amintiți-vă căwcînseamnă „număr de cuvinte”, adăugarea opțiunii-lînseamnă în schimb „număr de linii”)iarsort -nsortează lucrurile numeric.Am putea pune acest lucru într-un fișier,dar atunci ar sorta doar o listă de.pdbfișiere din directorul curent.Dacă vrem să putem obține o listă ordonată a altor tipuri de fișiere,avem nevoie de o modalitate de a introduce toate aceste nume în script.Nu putem folosi,și așa mai departepentru că nu știm câte fișiere există.În schimb,folosim variabila specială$@,care înseamnă:’Toate argumentele liniei de comandă pentru scriptul shell’.De asemenea, ar trebui să punem$@în interiorul ghilimelelor dublepentru a gestiona cazul argumentelor care conțin spații("$@"este o sintaxă specială și este echivalentă cu""""…).Iată un exemplu:
$ nano sorted.sh# Sort files by their length.# Usage: bash sorted.sh one_or_more_filenameswc -l "$@" | sort -n$ bash sorted.sh *.pdb ../creatures/*.dat9 methane.pdb12 ethane.pdb15 propane.pdb20 cubane.pdb21 pentane.pdb30 octane.pdb163 ../creatures/basilisk.dat163 ../creatures/minotaur.dat163 ../creatures/unicorn.dat596 totalList Unique Species
Leah are câteva sute de fișiere de date, fiecare dintre ele fiind formatat astfel:
2013-11-05,deer,52013-11-05,rabbit,222013-11-05,raccoon,72013-11-06,rabbit,192013-11-06,deer,22013-11-06,fox,12013-11-07,rabbit,182013-11-07,bear,1Un exemplu de acest tip de fișier este dat în
data-shell/data/animal-counts/animals.txt.Se poate utiliza comanda
cut -d , -f 2 animals.txt | sort | uniqpentru a produce specia unică dinanimals.txt. Pentru a evita să fie nevoit să tasteze de fiecare dată această serie de comenzi, un om de știință poate alege să scrie în schimb un script shell.Scrieți un script shell numit
species.shcare primește orice număr de nume de fișiere ca argumente în linia de comandă și folosește o variantă a comenzii de mai sus pentru a tipări o listă a speciilor unice care apar în fiecare dintre aceste fișiere separat.Soluție
# Script to find unique species in csv files where species is the second data field# This script accepts any number of file names as command line arguments# Loop over all filesfor file in $@doecho "Unique species in $file:"# Extract species namescut -d , -f 2 $file | sort | uniqdoneSă presupunem că tocmai am executat o serie de comenzi care au făcut ceva util – de exemplu, care au creat un grafic pe care am dori să-l folosim într-o lucrare.Am dori să putem recrea graficul mai târziu, dacă avem nevoie, așa că dorim să salvăm comenzile într-un fișier.În loc să le tastăm din nou(și eventual să le greșim)putem face așa:
$ history | tail -n 5 > redo-figure-3.shFileul
redo-figure-3.shconține acum:297 bash goostats NENE01729B.txt stats-NENE01729B.txt298 bash goodiff stats-NENE01729B.txt /data/validated/01729.txt > 01729-differences.txt299 cut -d ',' -f 2-3 01729-differences.txt > 01729-time-series.txt300 ygraph --format scatter --color bw --borders none 01729-time-series.txt figure-3.png301 history | tail -n 5 > redo-figure-3.shDupă un moment de lucru într-un editor pentru a elimina numerele de serie de pe comenzi,și pentru a elimina linia finală în care am apelat comanda
history,avem o înregistrare complet exactă a modului în care am creat acea figură.De ce să înregistrăm comenzile în istoric înainte de a le executa?
Dacă executați comanda:
$ history | tail -n 5 > recent.shultima comandă din fișier este chiar comanda
history, adică,shell-ul a adăugathistoryla jurnalul de comenzi înainte de a o executa efectiv. De fapt, shell-ul adaugă întotdeauna comenzi în jurnal înainte de a le executa. De ce credeți că face acest lucru?Soluție
În cazul în care o comandă provoacă un blocaj sau un blocaj, ar putea fi util să știm care a fost acea comandă, pentru a investiga problema.Dacă comanda ar fi înregistrată numai după ce a fost executată, nu am avea o înregistrare a ultimei comenzi executate în cazul unui blocaj.
În practică, majoritatea oamenilor dezvoltă scripturi shell executând comenzi la promptul shell de câteva oripentru a se asigura că fac ceea ce trebuie, apoi salvându-le într-un fișier pentru reutilizare.Acest stil de lucru le permite oamenilor să recicleze ceea ce descoperă despre datele lor și despre fluxul lor de lucru cu un singur apel la
historyși un pic de editare pentru a curăța ieșirea și a o salva ca script shell.Nelle’s Pipeline: Crearea unui script
Supervizorul lui Nelle a insistat ca toate analizele ei să fie reproductibile. Cel mai simplu mod de a captura toți pașii este într-un script.
În primul rând ne întoarcem în directorul de date al lui Nelle:
$ cd ../north-pacific-gyre/2012-07-03/Ea rulează editorul și scrie următoarele:
# Calculate stats for data files.for datafile in "$@"do echo $datafile bash goostats $datafile stats-$datafiledoneSalvează acest lucru într-un fișier numit
do-stats.shpentru ca acum să poată reface prima etapă a analizei sale tastând:$ bash do-stats.sh NENE*.txtDe asemenea, poate face acest lucru:
$ bash do-stats.sh NENE*.txt | wc -lpentru ca rezultatul să fie doar numărul de fișiere procesate și nu numele fișierelor care au fost procesate.
Un lucru de reținut în legătură cu scriptul lui Nelle este că acesta lasă persoana care îl execută să decidă ce fișiere să proceseze.Ea l-ar fi putut scrie ca:
# Calculate stats for Site A and Site B data files.for datafile in NENE*.txtdo echo $datafile bash goostats $datafile stats-$datafiledoneAvantajul este că acesta selectează întotdeauna fișierele corecte:nu trebuie să își amintească să excludă fișierele „Z”.Dezavantajul este că selectează întotdeauna doar acele fișiere – nu îl poate rula pe toate fișierele (inclusiv pe cele „Z”),sau pe fișierele „G” sau „H” pe care le produc colegii ei din Antarctica,fără a modifica scriptul.Dacă ar vrea să fie mai aventuroasă,ar putea să modifice scriptul pentru a verifica dacă există argumente în linia de comandă și să folosească
NENE*.txtdacă nu este furnizat niciunul.Desigur,acest lucru introduce un alt compromis între flexibilitate și complexitate.Variabile în scripturile shell
În directorul
molecules, imaginați-vă că aveți un script shell numitscript.shcare conține următoarele comenzi:head -n tail -nÎn timp ce vă aflați în directorul
molecules, tastați următoarea comandă:bash script.sh '*.pdb' 1 1Care dintre următoarele ieșiri vă așteptați să vedeți?
- Toate liniile dintre prima și ultima linie a fiecărui fișier care se termină în
.pdbdin directorulmolecules- Prima și ultima linie a fiecărui fișier care se termină în
.pdbdin directorulmolecules. directorPrima și ultima linie a fiecărui fișier din directorul
molecules- O eroare din cauza ghilimelelor din jurul
*.pdbSoluție
Răspunsul corect este 2.
Variabilele speciale $1, $2 și $3 reprezintă argumentele din linia de comandă date scriptului, astfel încât comenzile executate sunt:
$ head -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb$ tail -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdbShell-ul nu extinde
'*.pdb'deoarece este închis între ghilimele.Ca atare, primul argument al scriptului este'*.pdb'care este extins în script prinheadșitail.Găsește cel mai lung fișier cu o anumită extensie
Scrieți un script shell numit
longest.shcare ia ca argumente numele unui director și o extensie a numelui de fișier și tipărește numele fișierului cu cele mai multe linii din acel director cu acea extensie. De exemplu:$ bash longest.sh /tmp/data pdba imprima numele fișierului
.pdbdin/tmp/datacare are cele mai multe linii.Soluție
# Shell script which takes two arguments:# 1. a directory name# 2. a file extension# and prints the name of the file in that directory# with the most lines which matches the file extension.wc -l /*. | sort -n | tail -n 2 | head -n 1Prima parte a conductei,
wc -l /*. | sort -n, numără liniile din fiecare fișier și le ordonează numeric (cele mai mari la sfârșit). În cazul în care există mai multe fișiere,wcemite, de asemenea, o linie finală de rezumat, care indică numărul total de linii din toate fișierele. Folosimtail-n 2 | head -n 1pentru a arunca această ultimă linie.Cu
wc -l /*. | sort -n | tail -n 1vom vedea linia de rezumat finală: putem construi conducta noastră pe bucăți pentru a fi siguri că înțelegem ieșirea.Comprehensiunea de citire a scripturilor
Pentru această întrebare, luați din nou în considerare directorul
data-shell/molecules.Acesta conține un număr de fișiere.pdbîn plus față de orice alte fișiere pe care le puteți fi creat.Explicați ce ar face fiecare dintre următoarele trei scripturi atunci când sunt rulate cabash script1.sh *.pdb,bash script2.sh *.pdbși, respectiv,bash script3.sh *.pdb.# Script 1echo *.*# Script 2for filename in do cat $filenamedone# Script 3echo [email protected]Soluții
În fiecare caz, shell-ul extinde wildcard-ul din
*.pdbînainte de a trece lista de nume de fișiere rezultată ca argumente pentru script.Scriptul 1 ar tipări o listă a tuturor fișierelor care conțin un punct în numele lor.Argumentele transmise scriptului nu sunt de fapt folosite nicăieri în script.
Scriptul 2 ar tipări conținutul primelor 3 fișiere cu extensia
.pdb.,șise referă la primul, al doilea și, respectiv, al treilea argument.Script 3 ar imprima toate argumentele scriptului (adică toate fișierele
.pdb),urmate de.pdb.$@se referă la toate argumentele date unui script shell.cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdbDebugging Scripts
Să presupunem că ați salvat următorul script într-un fișier numit
do-errors.shîn directorulnorth-pacific-gyre/2012-07-03al lui Nelle:# Calculate stats for data files.for datafile in "$@"do echo $datfile bash goostats $datafile stats-$datafiledoneCând îl rulați:
$ bash do-errors.sh NENE*.txtla ieșire este goală.Pentru a afla de ce, rulați din nou scriptul folosind opțiunea
-x:bash -x do-errors.sh NENE*.txtCe vă arată ieșirea?Ce linie este responsabilă de eroare?
Soluție
Opțiunea
-xface cabashsă ruleze în modul de depanare.Acest lucru tipărește fiecare comandă pe măsură ce este executată, ceea ce vă va ajuta să localizați erorile.În acest exemplu, putem vedea căechonu tipărește nimic. Am făcut o greșeală de tipar în numele variabilei de buclă, iar variabiladatfilenu există, prin urmare returnează un șir gol.Puncturi cheie
Salvați comenzile în fișiere (numite de obicei scripturi shell) pentru reutilizare.
bashexecută comenzile salvate într-un fișier.
$@se referă la toate argumentele din linia de comandă a unui script shell.
,, etc., se referă la primul argument de linie de comandă, al doilea argument de linie de comandă etc.Puneți variabilele între ghilimele dacă valorile ar putea avea spații în ele.
Lăsând utilizatorii să decidă ce fișiere să proceseze este mai flexibil și mai coerent cu comenzile Unix încorporate.