Table of Contents

O înțelegere mai bună a shellului

Execuția comenzilor

Încheierea execuției unei comenzi

Atunci când rulăm o comandă aceasta își poate încheia execuția în două moduri: cu succes sau cu eșec. Atunci când își încheie execuția, orice proces întoarce un cod de eroare, care este un număr:

Pentru a vedea codul cu care și-a încheiat execuția o comandă folosim sintaxa $?. Urmărim exemplul de mai jos:

student@uso:~$ ls Desktop/
todos.txt
student@uso:~$ echo $?
0
student@uso:~$ ls non-existent
ls: cannot access 'non-existent': No such file or directory
student@uso:~$ echo $?
2

Observăm că în cazul fișierului inexistet, comanda ls non-existent a întors valoarea 2, așa cum era specificat și în pagina de manual.

Înlănțuirea comenzilor în funcție de succes sau eșec

De multe ori vrem să executăm o succesiune de comenzi pentru a realiza o sarcină. De exemplu, atunci când vrem să instalăm o aplicație o rulăm trei comenzi:

Preferăm să înlănțuim cele trei comenzi într-una singură pentru că astfel putem să pornim tot acest proces, să plecăm de la calculator, iar când ne întoarcem avem tot sistemul pregătit.

Pentru a înlănțui comenzi în terminalul bash avem trei operatori disponibili:

Pentru a rezolva scenariul de la care am plecat inițial, putem rula:

sudo apt update && sudo apt install -y cowsay && cowsay "Howdy"

Comanda de mai sus va actualiza indexul pachetelor sursă, va instala pachetul cowsay și va rula comanda cowsay pentru a valida instalarea. O astfel de înlănțuire de comenzi este numită oneliner.

Exerciții

  1. Scrieți un oneliner cu ajutorul căruia creați directorul ~/uso-lab/labs/05-cli/support/make-folder și apoi copiați conținutul directorului ~/uso-lab/labs/05-cli/support/redir în el.
  2. Actualizați onelinerul anterior astfel încât după copiere să pornească compilarea proiectului folosind comanda make build.

Înlănțuirea comenzilor folosind operatorul | (pipe)

Așa cum am descoperit în secțiunile și capitolele anterioare, în mediul Linux avem multe utilitare care rezolvă o nevoie specifică: ls afișează informații despre fișiere, ps despre procese, grep filtrează, etc. Toate acestea au la bază filozofia mediului Linux: “do one thing and do it well”. Ca întodeauna, frumusețea stă în simplitate: avem o suită de unelte la dispoziție, fiecare capabilă să rezolve rapid o sarcină dată; pentru a rezolva o problemă mai complexă trebuie doar să îmbinăm uneltele.

Operatorul | (pipe) ne ajută să facem acest lucru. Atunci când folosim operatorul | preluăm rezultatul comenzii din stânga operatorului și îl oferim ca intrare comenzii aflate în dreapta operatorului.

Am folosit de mai multe ori operatorul | până acum:

Alte exemple de prelucrări de text

Ne amintim de fișierul /etc/passwd conține informații despre toți utilizatorii din sistem.

student@uso:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
(...)

În Linux există filtrul de text cut prin care putem extrage doar anumite informații dintr-un output. Să zicem că vrem să extragem doar numele utilizatorilor, fără informațiile legate de grupuri sau home directory.

student@uso:~/uso-lab$ cat /etc/passwd | cut -f1 -d":"
root
daemon
bin
(...)

Argumentul -f1 specifică faptul că vrem prima coloană, iar argumentul -d: specifică delimitatorul de coloane, în cazul nostru :.

  1. Pornind de la comanda de mai sus, afișați numele utilizatorilor sortați alfabetic. (Hint: man sort)
  2. Folosind utilitarul wc, obțineți numărul de utilizatori din sistem. (Hint: man wc)
  3. Să afișeze cele mai consumatoare de memorie 10 procese din sistem. (Hint: folosiți | și tail)

Redirectări

Majoritatea utilitarelor pe care le folosim afișează rezultatele operațiilor pe care le aplică la ieșirea standard, adică pe ecran. În continuare vom aprofunda ceea ce am discutat despre redirectări în capitolul Lucrul cu Fișiere. Anterior am mai menționat și termenul de intrare standard; în această secțiune ne vom clarifica ce înseamnă, ce rol îndeplinesc și cum ne folosim de aceste cunoștințe.

Orice proces folosește implicit trei fluxuri (streams) de date:

În linie de comandă, atât STDOUT cât și STDERR vor apărea pe ecran. Datorită faptului că informațiile sunt scrise în două fluxuri distincte, utilizatorul are posibilitatea de a separa rezultatele de erori. Utilizatorul face aceasta folosind redirectări.

Redirectarea ieșirilor standard

Cum spuneam mai sus, majoritatea programelor pe care le folosim vor afișa rezultatele pe ecran. Acest comportament este bun atunci când ne scriem onelinerul care ne extrage informațiile căutate, dar cel mai probabil o să vrem să salvăm rezultatul procesării într-un fișier.

Folosim operatorul > pentru a redirecta STDOUT sau STDERR într-un fișier. Pentru fiecare flux de date avem un număr, numit descriptor de fișier, asociat:

Pentru a redirecta ieșirea standard folosim sintaxa cmd 1> nume-fișier. Pentru a redirecta ieșirea standard a erorilor folosim sintaxa cmd 2> nume-fișier.

Atenție! În cazul în care fișierul destinație nu există, operatorul > îl va crea. Dacă fișierul destinație există, operatorul > va șterge conținutul acestuia.

Urmăm exemplul de mai jos:

student@uso:~$ ps -e -ouser,uid,pid,%mem,%cpu,rss,cmd --sort=-%mem | head -11
USER       UID   PID %MEM %CPU   RSS CMD
student   1000  7938 18.0  0.1 367952 /usr/bin/gnome-shell
student   1000  8437  8.4  0.0 171916 /usr/bin/gnome-software --gapplication-service
student   1000  7782  3.9  0.0 81312 /usr/lib/xorg/Xorg vt1 -displayfd 3 -auth /run/user/1000/gdm/Xauthority -background none -noreset -keeptty -verbose 3
root         0  1338  3.8  0.0 78880 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
student   1000  8307  3.1  0.0 64628 /usr/lib/evolution/evolution-calendar-factory
student   1000  8338  3.0  0.0 61860 /usr/lib/evolution/evolution-calendar-factory-subprocess --factory all --bus-name org.gnome.evolution.dataserver.Subprocess.Backend.Calendarx8307x2 --own-path /org/gnome/evolution/dataserver/Subprocess/Backend/Calendar/8307/2
root         0   336  2.6  0.0 53612 /lib/systemd/systemd-journald
student   1000  8274  2.3  0.0 48296 nautilus-desktop
root         0  1074  2.2  0.0 45460 /usr/bin/containerd
student   1000 12966  1.8  0.0 38216 /usr/lib/gnome-terminal/gnome-terminal-server
student@uso:~$ ps -e -ouser,uid,pid,%mem,%cpu,rss,cmd --sort=-%mem | head -11 1> top10-consumers
student@uso:~$ less top10-consumers

Am scris, prin încercări succesive, onelinerul care ne afișează primele zece procese care consumă cea mai multă memorie. Apoi am folosit sintaxa 1> top10-consumers pentru a redirecta rezultatul în fișierul top10-consumers.

Urmăm exemplul de mai jos pentru a redirecta erorile:

student@uso:~$ ls D* F* > out 2> errs
student@uso:~$ cat out
Desktop:
todos.txt
 
Documents:
snippets.git
uni
uso.tar
 
Downloads:
courses.tar
uso.tar
student@uso:~$ cat errs
ls: cannot access 'F*': No such file or directory

Observăm că am folosit sintaxa 2> errs pentru a redirecta erorile în fișierul errs. Observăm că pentru a redirecta ieșirea standard putem omite descriptorul de fișier, așa cum am făcut cu > out.

Atunci când rulăm o comandă, redirectăm erorile într-un fișier pentru că vrem să verificăm că totul s-a executat cu succes. De cele mai multe ori suntem în rumătorul scenariu:

  1. Urmează să executăm o comandă care durează mai mult timp și pentru care nu putem să ținem pasul, cu ochiul liber, cu fluxul de afișare a datelor pe ecran. Un exemplu este compilarea unui proiect mai mare.
  2. O să pornim procesul și o să redirectăm STDOUT și STDERR în două fișiere, de ex. out și err.
  3. În timpul cât rulează noi putem să facem altceva: ne ocupăm de altă sarcină, ne facem o cafea, etc.
  4. La finalul execuției inspectăm fișierele out și err pentru a vedea dacă au existat erori și le rezolvăm.

Acum înțelegem cum funcționează operatorul | (pipe). Acesta conectează fluxul de ieșire (STDOUT) al comenzii din stânga sa cu fluxul de intrare (STDIN) al comenzii din dreapta.

Redirectarea în mod append

Implicit, operatoru > șterge (trunchează) conținutul fișierului destinație. Dacă vrem să păstrăm conținutul fișierului și să adăugăm rezultatul redirectării în continuarea acestuia, folosim operatorul >>.

Rulați din nou exemplele de mai sus folosind operatorul >> în locul operatorului >. Folosiți less pentru a inspecta fișierele de ieșire și de erori.

Fișiere speciale

Pe sistemele Linux găsim un număr de fișiere speciale pe care le putem folosim în diferite scopuri:

Generatoarele de octeți sunt utile pentru a testa aplicațiile pe care le dezvoltăm. Majoritatea aplicațiilor pe care le vom scrie, ca și cele pe care le utilizăm, citesc și prelucrează informații. Testăm o aplicație pentru că vrem să verificăm că nu avem buguri. Pentru aceasta putem să folosim seturi de date de intrare cât mai variate și mai aleatoare, adică inputuri random. Folosim utilitarul dd pentru a genera un fișier de 100 MB cu octeți random, ca în exemplul de mai jos:

student@uso:~$ dd if=/dev/urandom of=rand-100mb count=100 bs=1M
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 1,11416 s, 94,1 MB/s
student@uso:~$ ls -lh rand-100mb
-rw-rw-r-- 1 student student 100M nov  8 17:49 rand-100mb

Am folosit următoarele opțiuni ale utilitarului dd:

Exercițiu: Creați un fișier numit rand-250mb folosind utilitarul dd.

Un caz uzual de utilizare a dd este suprascrierea unui disc cu informații aleatoare. Această metodă este utilizată ca o formă de securitate atunci când vrem să ștergem informații de pe un disc. Astfel suprascriem datele șterse pentru a preveni posibilitatea recuperării datelor de pe disc. Mai multe informații găsiți aici.

Exerciții

  1. Afișați primele zece procese sortate în funcție de memoria ocupată (Hint: RSS). Nu uitați să includeți antetul. Redirectați rezultatul în fișierul top10-rss-consumers. Modificați comanda astfel încât rezultatul redirectării să nu șteargă conținutul existent.
  2. Afișați ultimele zece procese sortate în funcție de utilizarea procesorului (Hint: CPU). Nu uitați să includeți antetul. Redirectați rezultatul în fișierul top10-cpu-consumers. Modificați comanda astfel încât rezultatul redirectării să nu șteargă conținutul existent.

Note de subsol

1)

Valoarea 0 nu înseamnă cifra 0. Valoarea 0 înseamnă caracterul (null) din tabelul ASCII. Caracterul 0 are valoarea 48 în tabelul ASCII.