This is an old revision of the document!


Curs 11 - Prelucrarea datelor

  • Cuvinte cheie: date, prelucrare, means, ends, parsare, prezentare, date tabelare, separator de câmpuri, cut, awk, IFS, while read, expresii regulate, metacaractere, grep, shell scripting, for, if, rezultate numerice, grafice
  • Suport de curs
    • Suport (Introducere în sisteme de operare)
      • Capitolul 12 – Shell scripting
        • Secțiunile 12.4, 12.5, 12.6, 12.9
      • Puteți descărca fișierul PDF aferent de aici.
      • Capitolul 1 – Introduction to Regular Expressions
      • Capitolul 2 – Basic Regular Expression Skills

Demo-uri

Pentru 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:

  1. Folosim direct consola mașinii virtuale.
  2. Aflăm adresa IP de pe interfața eth1 a mașinii virtuale și ne conectăm prin SSH, de pe sistemul fizic, folosind comanda
    ssh 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.

Dacă dorim să ne conectăm pe SSH iar mașina virtuală nu are adresă IP configurată pe interfața eth1 atunci folosim comanda

sudo dhclient eth1

pentru a obține o adresă IP.

Dacă optăm pentru rularea prin SSH iar sistemul fizic rulează Windows, putem folosi Putty pe post de client SSH pe sistemul fizic.

Comenzile folosite sunt de uz general. Actualizând adresele IP cu adrese potrivite, putem rula cu succes comenzile pe orice sistem sau mașină virtuală Linux.

Obținere arhivă

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-11/curs-11-demo.zip
[...]

și apoi dezarhivăm arhiva

student@uso-demo:~$ unzip curs-11-demo.zip 
[...]

și accesăm directorul rezultat în urma dezarhivării

student@uso-demo:~$ cd curs-11-demo/
student@uso-demo:~/curs-11-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.

Splitting folosind cut

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
[...]

Utilitarul cut are două opțiuni frecvent folosite:

  • opțiunea -d care precizează delimitatorul de câmpuri (field delimiter sau field separator)
  • opțiunea -f care precizează ce câmpuri/coloane dorim să extragem

Splitting și prelucrare folosind while read

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:

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"
    fi
done < user-results.csv

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).

Construcția while read este folosită pentru a putea face prelucrări pe fiecare linie procesată, nu doar splitting, așa cum face comanda cut.

Splitting și prelucrare folosind awk

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).

În awk, sed și alte utilitare similare expresiile regulate se plasează între slash-uri; mai sus a fost vorba de /[^\.]+\.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:

student@uso-demo:~/curs-11-demo$ cat extract-points-500-awk 
#!/bin/bash

awk -F ',' '
$1 ~ /[^\.]+\.c/ {
    if ($4 >= 500)
        print $1 "," $4;
    }
' < user-results.csv

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.

În general, dacă utilizatorul dorește să obțină un efect rapid și cu șanse mici de repetare, va folosi un one liner. Altfel, va folosi un script shell pe care îl poate actualiza șî rula în mod repetat.

Splitting și prelucrare folosind Python

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

extract-points-500.py
#!/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:

extract-points-500-date.py
#!/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

Decizia de folosire 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.

Folosire expresii regulate în sed

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.

Automatizare folosind shell scripting

\begin{frame}[fragile]{Exemplu de script shell}
  \begin{block}{Publicare slide-uri de curs USO}
    \begin{alltt}
#!/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 \end{alltt}
  \end{block}
\end{frame}

Prelucrarea datelor cu shell scripting

uso/cursuri/curs-11.1451843957.txt.gz · Last modified: 2016/01/03 19:59 by razvan.deaconescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0