This is an old revision of the document!
IFS
, while read
, for
, if
, expresii regulate, metacaractere, globbing, grep
, prelucrare de date, automatizarePentru rularea demo-urilor de mai jos folosim mașina virtuală USO Demo. Mașina virtuală (în format OVA) poate fi importată în VirtualBox. Comenzile le vom rula în cadrul mașinii virtuale.
Mașina virtuală deține două interfețe de rețea:
eth0
pentru accesul la Internet (interfață de tipul NAT)eth1
pentru comunicarea cu sistemul fizic (gazdă, host) (interfață de tipul Host-only Adapter)Pentru a rula demo-ul avem două opțiuni:
eth1
a mașinii virtuale și ne conectăm prin SSH, de pe sistemul fizic, folosind comandassh student@<adresa-IP-vm-eth1>
unde <adresa-IP-vm-eth1>
este adresa IP a interfeței eth1
din cadrul mașinii virtuale.
Pentru conectarea la mașina virtuală folosim numele de utilizator student
cu parola student
. Contul student
are permsiuni de sudo
. Folosind comanda
sudo su -
obținem permisiuni privilegiate (de root
) în shell.
eth1
atunci folosim comanda
sudo dhclient eth1
pentru a obține o adresă IP.
Pentru parcurgerea demo-urilor, folosim arhiva aferentă. Descărcăm arhiva în mașina virtuală (sau în orice alt mediu Linux) folosind comanda
student@uso-demo:~$ wget http://elf.cs.pub.ro/uso/res/cursuri/curs-10/curs-10-demo.zip [...]
și apoi dezarhivăm arhiva
student@uso-demo:~$ unzip curs-10-demo.zip [...]
și accesăm directorul rezultat în urma dezarhivării
student@uso-demo:~$ cd curs-10-demo/ student@uso-demo:~/curs-10-demo$ ls user-results.csv
Acum putem parcurge secțiunile cu demo-uri de mai jos. Vom folosi resursele din directorul rezultat în urma dezarhivării.
Pentru splitting simplu de date tabelare putem folosi utilitarul cut
. Acesta permite precizarea unui delimitator și a unui câmp sau a mai multor câmpuri (coloane) care să fie selectate din tabel.
Din fișierul user-results.csv
(format CSV – Comma Separated Values) dorim să selectăm doar numele grupurilor. Pentru aceasta selectăm doar a doua coloană și folosim separatorul ,
(virgulă) folosind comanda
student@uso-demo:~/curs-11-demo$ cut -d ',' -f 2 < user-results.csv Liceul Teoretic Ștefan Odobleja Colegiul Național Ion Maiorescu Colegiul Tehnic Toma Socolescu Colegiul Național Nichita Stănescu Liceul Teoretic Benjamin Franklin Colegiul Național I.L. Caragiale Colegiul Național Zinca Golescu [...]
Dacă dorim să “unicizăm” rezultatele și să afișăm doar numele liceelor putem conecta comanda de mai sus la o comandă sort
:
student@uso-demo:~/curs-11-demo$ cut -d ',' -f 2 < user-results.csv | sort -u Colegiul Economic Virgil Madgearu Colegiul Național Alexandru Odobescu Colegiul Național Barbu Știrbei Colegiul Național Cantemir Vodă Colegiul Național Carol I Colegiul Național Gheorghe Lazăr [...]
În urma rulării comenzii de mai sus ne sunt afișate doar numele liceelor.
Dacă ne interesează să afișăm doar identificatorii utilizatorilor și punctajele obținute, atunci folosim comanda
student@uso-demo:~/curs-11-demo$ cut -d ',' -f 1,4 < user-results.csv | head ionut.asimionesei,0 laura.matei,66 alin.dascalu,285 dragos.konnerth,42 alexandru.corneanu,247 alexandru.tittes,154 [...]
cut
are două opțiuni frecvent folosite:
-d
care precizează delimitatorul de câmpuri (field delimiter sau field separator)-f
care precizează ce câmpuri/coloane dorim să extragem
Utilitarul cut
are dezavantajul că face doar splitting și extrage câmpuri/coloane. Nu putem condiționa extragerea unor câmpuri. De exemplu, dacă dorim extragerea conturilor care au punctaj mai mare ca 500, nu vom putea folosi cut
. Putem însă folosi construcția while read
într-un script shell.
Pentru a extrage conturile care au punctaj mai mare ca 500, vom folosi scriptul extract-points-500
de mai jos:
#!/bin/bash IFS=',' while read uid school date points; do if test "$points" -ge 500; then echo "$uid" fi done < user-results.csv
Rulăm scriptul folosind comanda
student@uso-demo:~/curs-11-demo$ ./extract-points-500 mihaela.croitoru andreea.cismas elvis.titirca mihaela.serbana anjie.teodorescu [...]
În scriptul extract-points-500
am folosit construcția while read
pentru a face split la cele patru coloane din fișierul user-results.csv
. Separatorul (delimitatorul) l-am definit cu ajutorul variabilei IFS
(Input Field Separator) pe care am inițializat-o la ,
(virgulă). După split am folosit construcția if
pentru a afișa doar conturile utilizatorilor cu punctaj peste 500
.
Dacă dorim să afișăm și punctajul obținut (nu doar contul) atunci trebuie doar să modificăm linia de afișare (care folosește comanda echo
). Rezultatul va fi scriptul actualizat și cu rularea de mai jos:
student@uso-demo:~/curs-11-demo$ cat extract-points-500 #!/bin/bash IFS=',' while read uid school date points; do if test "$points" -ge 500; then echo "$uid,$points" fi done < user-results.csv student@uso-demo:~/curs-11-demo$ ./extract-points-500 mihaela.croitoru,516 andreea.cismas,803 elvis.titirca,501 mihaela.serbana,526 anjie.teodorescu,666 georgiana.ciobanica,1047 [...]
Dacă în output-ul de mai sus dorim să avem sortare în ordine descrescătoare a punctajului, înlănțuim o comandă sort
care să sorteze numeric, descrescător după a doua coloană
student@uso-demo:~/curs-11-demo$ ./extract-points-500 | sort -t ',' -k 2,2rn radu.dumitru5227,21433 mihaela.catai,13623 stefania.oprea,9547 alexandra.calinescu,5266 george.ungureanu,3846 dragos.totu,2040 monica.cirisanu,1815
În comanda de mai sus, comanda sort
sortează output-ul scriptului folosind ca separator virgulă (construcția -t ','
după a două coloană (construcția -k 2,2
) descrescător numeric (construcția rn
).
while read
este folosită pentru a putea face prelucrări pe fiecare linie procesată, nu doar splitting, așa cum face comanda cut
.
Construcția while read
este utilă pentru realizarea de splitting și de prelucări minimale. Prelucrările pe care le poate face țin de facilitățile pe care le oferă shell-ul.
Pentru prelucrări mai avansate recomandăm folosirea utilitarului awk
. Utilitarul awk
are în spate un limbaj propriu, asemănător limbajului C, și are suport de expresii regulate. Este un utilitar puternic util pentru prelucrarea datelor în format text.
Pentru a obține același efect cu al scriptului extract-points-500
putem folosi oneliner-ul de mai jos:
student@uso-demo:~/curs-11-demo$ awk -F ',' '{ if ($4 >= 500) print $1;}' < user-results.csv mihaela.croitoru andreea.cismas elvis.titirca mihaela.serbana anjie.teodorescu georgiana.ciobanica [...]
Pentru a afișa și punctajul folosim one liner-ul:
student@uso-demo:~/curs-11-demo$ awk -F ',' '{ if ($4 >= 500) print $1 "," $4;}' < user-results.csv mihaela.croitoru,516 andreea.cismas,803 elvis.titirca,501 mihaela.serbana,526 anjie.teodorescu,666 georgiana.ciobanica,104 [...]
Observăm că pentru awk
separatorul este dat de opțiunea -F
, iar sintaxa este similară cu cea a limbajului C. Un câmp/coloană este indicat de construcția $N
unde N
este indexul câmpului; în cazul nostru am folosit $1
și $4
pentru cont și punctaj, respectiv.
După cum am precizat, awk
are suport de expresii regulate. Dacă, de exemplu, din output-ul de mai sus dorim să extragem doar liniile care au un nume de cont al cărui nume de familie începe cu litera c
, vom folosi construcția:
student@uso-demo:~/curs-11-demo$ awk -F ',' '$1 ~ /[^\.]+\.c/ { if ($4 >= 500) print $1 "," $4;}' < user-results.csv mihaela.croitoru,516 andreea.cismas,803 georgiana.ciobanica,1047 ion.camasa,502 mihaela.catai,13623 alexandra.cismaru,860 [...]
În one liner-ul de mai sus, am selectat doar acele linii pentru care primul câmp ($1
) face match pe expresia regulată [^\.]+\.c
(adică numele de familie începe cu litera c
).
/[^\.]+\.c/
.
Același rezultat ca mai sus putea fi realizat și cu ajutorul comenzii grep
legată de la comanda awk
, ca mai jos:
student@uso-demo:~/curs-11-demo$ awk -F ',' '{ if ($4 >= 500) print $1 "," $4;}' < user-results.csv | grep '^[^\.]\+\.c' mihaela.croitoru,516 andreea.cismas,803 georgiana.ciobanica,1047 ion.camasa,502 mihaela.catai,13623 alexandra.cismaru,860 [...]
Rezultatul este același și poate părea mai simplu să folosim grep
. Doar că awk
permite match cu expresie regulată pe un câmp specific primit la intrare; se poate forța acest lucru și cu grep
dar devine mai puțin clar.
În momentul în care un script awk
devine mai complicat, poate fi plasat într-un script shell, așa cum este în fișierul extract-points-500-awk
:
#!/bin/bash awk -F ',' ' $1 ~ /[^\.]+\.c/ { if ($4 >= 500) print $1 "," $4; } ' < user-results.csv
Rulăm scriptul folosind comanda
student@uso-demo:~/curs-11-demo$ ./extract-points-500-awk mihaela.croitoru,516 andreea.cismas,803 georgiana.ciobanica,1047 [...]
Scriptul extract-points-500-awk
face același lucru ca one liner-ul anterior doar că este mai lizibil.
Pentru prelucrări complexe sau pentru integrarea prelucrărilor cu alte componente ale unei aplicații, inclusiv awk
poate fi insuficient. În acest caz programatorul va apela la un limbaj specific precum Python, Perl, Ruby, Lua, Java, JavaScript sau altul.
În fișierul extract-points-500.py
de mai jos avem implementarea în Python a aceleiași funcționalități ca mai sus pentru awk
#!/usr/bin/env python import sys import re def main(): for line in open("user-results.csv", "rt"): line = line.rstrip("\n") uid, school, date, points = line.split(",") if re.match("[^\.]+\.c", uid): if int(points) >= 500: print "%s,%s" % (uid,points) if __name__ == "__main__": sys.exit(main())
În cadrul scriptului citim linie cu linie conținutul fișierului user-results.csv
și apoi este splitted și se extrag liniile pentru care numele contului are un nume de familie care începe cu litere c
și punctajul este mai mare ca 500
.
Rularea scriptului Python conduce la același rezultat ca în cazul folosirii awk
:
student@uso-demo:~/curs-11-demo$ ./extract-points-500.py mihaela.croitoru,516 andreea.cismas,803 georgiana.ciobanica,1047 ion.camasa,502 mihaela.catai,13623 alexandra.cismaru,860 [...]
Python oferă o flexibilitate superioară awk
cu dezavantajul unei complexități mai mari a codului. Pentru acțiuni rapide, integrabile cu shell scripting, awk
este o bună alegere (sau while read
); pentru acțiuni mai complexe, un limbaj de programare dedicat, precum Python poate fi o soluție.
De exemplu, putem augmenta scriptul anterior pentru a afișa doar informații despre acele conturi care s-au autentificat prima oară în luna aprilie. Adică al treilea câmp este din luna aprilie. Pentru aceasta folosim scriptul extract-points-500-date.py
din director:
#!/usr/bin/env python import sys import re from datetime import datetime def main(): for line in open("user-results.csv", "rt"): line = line.rstrip("\n") uid, school, date, points = line.split(",") date_compare = datetime.strptime("2015-04-01 00:00:00", "%Y-%m-%d %H:%M:%S") try: date_in_format = datetime.strptime(date, "%Y-%m-%d %H:%M:%S") except: continue if re.match("[^\.]+\.c", uid): if int(points) >= 500: if date_in_format > date_compare: print "%s,%s,%s" % (uid,date,points) if __name__ == "__main__": sys.exit(main())
În scriptul de mai sus am comparat data (al treilea câmp) cu data de 1 aprilie 2015 și am afișat acele câmpuri pentru care câmpul dată era mai târziu de 1 aprilie 2015. Acest lucru ar fi putut fi realizat în awk
dar ar fi complicat scriptul.
Pentru rularea scriptului, folosim construcția
student@uso-demo:~/curs-11-demo$ ./extract-points-500-date.py mihaela.catai,2015-04-16 12:29:26,13623 andra.cristiev,2015-04-22 13:08:57,865
cut
sau while read
sau awk
sau Python ține atât de datele și complexitatea problemei cât și de experiența utilizatorului/dezvoltatorului. În general, recomandăm “use the best tool for the best job”; dacă un utilitar simplu poate face ce aveți nevoie, folosiți-l pe acela, chiar dacă un utilitar mai complex are mai multe caracteristici; dacă acele caracteristici nu sunt utile, nu are sens să-l folosiți.
Mai sus am folosit cu awk
, grep
și Python expresii regulate. Adesea, în linia de comandă veți folosi expresii regulate folosind comanda grep
. Dar și în alte situații, fie în linie de comandă, fie în scripting, fie în limbaje de programare, expresiile regulate sunt utile.
În particular, utilitarul sed
folosește expresii regulate. Unul dintre cazurile frecvente de utilizare a sed
este pe post de editor neinteractiv, care să înlocuiască (substituie) elemente de pe o linie cu alte elemente.
Dacă dorim, de exemplu, să înlocuim toate prenumele din numele de cont nu șirul aaa
vom folosi un one liner precum cel de mai jos:
student@uso-demo:~/curs-11-demo$ sed 's/^[^\.]\+/aaa/g' < user-results.csv aaa.asimionesei,Liceul Teoretic Ștefan Odobleja,2015-03-31 16:18:01,0 aaa.matei,Colegiul Național Ion Maiorescu,2015-03-17 11:04:49,66 aaa.dascalu,Colegiul Tehnic Toma Socolescu,None,285 aaa.konnerth,Colegiul Național Nichita Stănescu,None,42 aaa.corneanu,Liceul Teoretic Benjamin Franklin,2015-03-18 10:26:21,247 [...]
La fel ca în cazul awk
expresia regulată se plasează între slash-uri. Expresia regulată folosită ^[^\.]\+
face match pe începutul de linie și pe șiruri de cel puțin o literă care nu conțin punct (.
, dot). Adică exact pe prenumele din numele de cont.
Dacă dorim să eliminăm prenumele (împreună cu punctul) din numele de cont vom folosi o construcție precum
student@uso-demo:~/curs-11-demo$ sed 's/^[^\.]\+\.//g' < user-results.csv asimionesei,Liceul Teoretic Ștefan Odobleja,2015-03-31 16:18:01,0 matei,Colegiul Național Ion Maiorescu,2015-03-17 11:04:49,66 dascalu,Colegiul Tehnic Toma Socolescu,None,285 konnerth,Colegiul Național Nichita Stănescu,None,42 corneanu,Liceul Teoretic Benjamin Franklin,2015-03-18 10:26:21,247 [...]
În one liner-ul de mai sus am lăsat ca șir care substituie șirul vid (adică două slash-uri consecutive). Adică înlocuim partea pe care face match expresia regulată (adică prenumele din cont și caracterul punct) cu nimic, adică o ștergem.
O situație poate fi să fie înlocuită construcția prenume.nume
cu nume.prenume
în numele contului. Pentru aceasta folosim construcția
student@uso-demo:~/curs-11-demo$ sed 's/^\([^\.]\+\)\.\([^\,]\+\),/\2.\1,/g' < user-results.csv asimionesei.ionut,Liceul Teoretic Ștefan Odobleja,2015-03-31 16:18:01,0 matei.laura,Colegiul Național Ion Maiorescu,2015-03-17 11:04:49,66 dascalu.alin,Colegiul Tehnic Toma Socolescu,None,285 konnerth.dragos,Colegiul Național Nichita Stănescu,None,42 corneanu.alexandru,Liceul Teoretic Benjamin Franklin,2015-03-18 10:26:21,247 [...]
Expresia regulată folosită mai sus (^\([^\.]\+\)\.\([^\,]\+\),
) o decodificăm în următoarele componente:
^
: început de linie\([^\.]\+\)
: face match pe prenume, adică șirul format din cel puțin un caracter diferit de punct; reține acest șir într-o variabilă (variabila este referită prin construcția \1
)\.
: face match pe caracterul punct (.
, dot) care separă prenumele de nume în cont\([^\,]\+\)
: face match pe prenume, adică șirul format din cel puțin un caracter diferit de virgulă; reține acest șir într-o variabilă (variabila este referită prin construcția \2
),
: face match pe caracterul virgulă (,
, comma) care urmează după cont
Partea de înlocuit este \2.\1,
adică numele de cont este înlocuit cu nume, urmat de punct, urmat de prenume, urmat de virgulă, așa cum ne-am dorit.
awk
, sed
este un utilitar adecvat pentru one linere, acțiuni rapide și încorporare în shell scripting. Pentru scenarii de utilizare mai complexe, recomandăm folosirea unor limbaje dedicate precum Python, Perl, Ruby, Java, JavaScript.
Folosirea shell scripting și a one line-erelor are, de principiu, 3 scenarii de utilizare:
cut
, while read
, tr
, grep
, sort
, awk
, sed
) se prelucrează date în format textPartea de automatizare este utilă atunci când vrem să executăm o acțiune în mod repetat. Un caz de utilizare este când vrem să prelucrăm în același mod mai multe fișiere.
De exemplu, având în vedere conținutul subdirectorului horde/
, vrem să creăm copii de lucru ale fișierelor de configurare de distribuție. Fișierele de distribuție au extensia .dist
(de exemplu conf.php.dist
); o copie de lucru este un fișier fără extensia .dist
(de exemplu: conf.php
).
Pentru a crea copie de lucru putem folosi următorul one liner, construit pas cu pas:
student@uso-demo:~/curs-11-demo$ find horde/ -name '*.dist' horde/ingo/config/hooks.php.dist horde/ingo/config/prefs.php.dist [...] student@uso-demo:~/curs-11-demo$ for f in $(find horde/ -name '*.dist'); do echo "$f"; done horde/ingo/config/hooks.php.dist horde/ingo/config/prefs.php.dist [...] student@uso-demo:~/curs-11-demo$ for f in $(find horde/ -name '*.dist'); do echo "${f/.dist/}"; done horde/ingo/config/hooks.php horde/ingo/config/prefs.php [...] student@uso-demo:~/curs-11-demo$ for f in $(find horde/ -name '*.dist'); do cp "$f" "${f/.dist/}"; done
În ultima comandă avem one liner-ul care creează câte o copie a fișierului de distribuție într-un fișier de lucru. Construcția ${f/.dist/}
este folosită pentru a înlocui în valoarea variabilei f
șirul .dist
cu nimic, adică șterge acel șir, rezultând numele fișierului fără extensia .dist
.
Pentru verificare folosim comanda find
pentru a valida existența fișierelor de lucru:
student@uso-demo:~/curs-11-demo$ find horde/ -name '*.php' horde/ingo/config/prefs.php horde/ingo/config/backends.php [...]
One liner-ul de mai sus este util o singură dată, pentru acele fișiere și de acceea nu are sens să îl trecem într-un script pe care să îl rulăm periodic, la nevoia, ca automatizare.
Un exemplu de script folosit pentru automatizare este scriptul publish-slides
folosit pentru publicarea slide-urilor de cursuri de USO. Nu veți putea rula scriptul în absența fișierelor de suport, dar este reprezentantiv pentru ceea ce înseamnă automatizarea unei sarcini repetitive: publicarea slide-urilor cursului de USO intern în cadrul echipei (în Dropbox) și studenților.
#!/bin/bash dropbox_folder=~/Downloads/Dropbox/school/uso-shared remote_end=uso@elf.cs.pub.ro:res/current/cursuri if test $# -eq 1; then id=$(printf "%02g" $1) pushd curs-$id/ > /dev/null 2>&1 make all cp *.pdf "$dropbox_folder"/curs-$id/ scp *handout*.pdf "$remote_end"/curs-$id/ popd > /dev/null 2>&1 exit 0 fi for id in $(seq -f "%02g" 0 13); do pushd curs-$id/ > /dev/null 2>&1 make all cp *.pdf "$dropbox_folder"/curs-$id/ scp *handout*.pdf "$remote_end"/curs-$id/ popd > /dev/null 2>&1 done
Scriptul compilează (folosind make
) slide-urile cursului primit ca argument, sau toate slide-urile în absența argumentelor. Folosește cp
pentru a copia slide-urile intern echipei, în Dropbox, și folosește scp
pentru a publica slide-urile către studenți.