Laborator 05 - Îmbunătățirea lucrului în linia de comandă

Îmbunătățirea lucrului în linia de comandă

Înainte să începeți laboratorul, dați comanda cd ~/uso-lab && git pull.

Principalul atu al utilizării aplicațiilor în linie de comandă, și nu în mediul grafic, este viteza cu care rezolvăm anumite sarcini. Această viteză este dată de posibilitatea de a combina utilitare în linia de comandă pentru a automatiza procese manuale, repetitive. Întotdeauna ne dorim să fim mai rapizi și să automatizăm cât mai mult din sarcinile noastre, deoarece cu cât ne terminăm treaba mai repede, cu atât avem mai mult timp liber la dispoziție.

De regulă, cu cât petrecem mai mult timp cu mâna pe tastatură și mai puțin pe mouse, cu atât suntem mai rapizi. Aliniate la această idee, aplicațiile ne pun la dispoziție scurtături pe care suntem încurajați să le folosim pentru a ne utiliza timpul mai eficient.

Scurtături în terminal

Tab completion

Funcția de tab completion este probabil una dintre cele mai utile funcții expuse de către terminal. Prin simpla apăsare a tastei Tab în timp ce scriem numele unei comenzi, al unei opțiuni a unei comenzi sau calea către un director sau fișier, terminalul va completa în mod automat textul. În cazul în care există mai multe opțiuni pentru auto-complete, prin dubla apăsare a tastei Tab ne va sugera opțiunile de auto-complete.

În imaginea de mai jos putem observa că pentru comanda cd D funcția de Tab completion a găsit mai multe opțiuni valide pentru auto-complete. În astfel de scenarii, cu mai multe opțiuni valide, apăsarea tastei Tab o singură dată nu produce niciun rezultat; trebuie să apăsăm tasta Tab de două ori consecutiv pentru a genera afișarea opțiunilor de auto-complete.

student@uso:~$ cd D
Desktop/   Documents/ Downloads/
student@uso:~$ cd D

Funcția de auto-complete este extrem de utilă și îmbunătățește semnificativ viteza cu care realizăm acțiuni în terminal.

Funcția este extrem de utilă atunci când lucrăm cu nume de fișiere, directoare și căi din sistem. În loc să scriem manual o cale către un nume foarte lung, lăsăm tasta Tab să facă asta pentru noi.

Atunci când avem o eroare în comandă (am scris greșit o anumită parte din numele comenzii sau al fișierului, fișierul nu există, etc.), tasta Tab nu produce nici un rezultat. Acesta este un alt motiv pentru care să folosim tasta Tab.

Folosiți funcția de Tab completion cât mai des cu putință1).

Shellul implementează funcția de a reține istoricul comenzilor pe care le-am executat. Pentru a vedea istoricul curent putem rula comanda history. Vom obține un rezultat asemănător cu cel de mai jos:

student@uso:~$ history
[...]
21  ls
22  cd sorting/
23  ls -l
24  ls
25  cd
26  ls ~/Desktop ~/Documents ~/Downloads
27  ls ~/Desktop
28  ls Desktop/todos.txt
29  cp Desktop/todos.txt cp
30  ls
31  rm cp
[...]

Ciclăm prin comenzile date anterior folosind combinația de taste Arrow Up sau Ctrl+p, respectiv Arrow Down sau Ctrl+n.

Exercițiu: Ciclați prin istoricul de comenzi folosind combinația de taste Ctrl+p, respectiv Ctrl+n.

Căutarea în istoricul comenzilor

Terminalul ne pune la dispoziție un mod mai inteligent de a căuta în istoricul comenzilor prin combinația de taste Ctrl+r, ordinea căutării fiind de la cea mai recentă comandă la cea mai veche. Funcția este cunoscută sub numele de history search.

Pentru a porni căutarea, apăsați combinația de taste Ctrl+r și începeți să scrieți o parte din textul comenzii pe care o căutați, de exemplu cd.

(reverse-i-search)`cd': cd workspace/hello

De aici, avem următoarele opțiuni:

  • Căutăm în continuare o comandă care conține șirul cd, folosind combinația de taste Ctrl+r
  • Rulăm comanda pe care am găsit-o, folosind combinația de taste Ctrl+o
  • Anulăm căutarea comenzii și revenim la starea inițială, folosind combinația de taste Ctrl+g

Căutarea este incrementală. Adică se rafinează pe măsură ce tastăm un caracter. Orice caracter apăsat rafinează căutarea.

Textul căutat se poate afla oriunde în interiorul comenzii; nu trebuie să fie primele litere din comandă. Pentru exemplul de mai sus, căutarea folosind textul work ar fi produs același rezultat

(reverse-i-search)`work': cd workspace/hello
Expandarea comenzilor anterioare

Terminalul ne pune la dispoziție și alte moduri prin care putem reutiliza comenzile din istoric.

Atunci când rulăm comanda history, ca în subsecțiunea improve_cli_history_nav , vedem că fiecare comandă din istoric este precedată de un număr:

23  ls -l

Acest număr funcționează ca un index pe care îl putem folosi pentru a executa comanda care îi corespunde:

student@uso:~$ !23
ls -l
total 60
drwxr-xr-x  2 student student 4096 aug  6  2018 Desktop
drwxr-xr-x  3 student student 4096 aug 20  2018 Documents
drwxr-xr-x  2 student student 4096 aug 11 19:35 Downloads
drwxr-xr-x  2 student student 4096 aug  6  2018 Music
drwxr-xr-x  2 student student 4096 aug 11 21:08 Pictures
drwxr-xr-x  2 student student 4096 aug  6  2018 Public
drwxr-xr-x  2 student student 4096 aug  6  2018 Templates
drwxr-xr-x  2 student student 4096 aug  6  2018 Videos
-rw-r--r--  1 student student 8980 aug  6  2018 examples.desktop
drwxr-xr-x 14 student student 4096 aug 20  2018 uso.git
-rw-r--r--  1 student student 4827 aug 21  2018 vm-actions-log.txt
drwxr-xr-x  3 student student 4096 aug 11 20:28 workspace

Observăm că !23 a fost înlocuit (expandat, în abuz de limbaj) cu comanda ls -l care corespundea indexului 23.

Un caz particular, foarte des folosit, este !!, care se va înlocui textual cu ultima comandă executată:

student@uso:~$ ls
Desktop    Downloads  Pictures  Templates  examples.desktop  vm-actions-log.txt
Documents  Music      Public    Videos     uso.git           workspace
student@uso:~$ !! -l workspace
ls -l workspace
total 4
drwxr-xr-x 2 student student 4096 aug 11 21:32 hello

În exemplul de mai sus observăm că !! a fost înlocuit cu ls în textul comenzii, pentru ca apoi să se execute comanda ls -l workspace. Sintaxa !! este echivalentă cu !-1.

Exercițiu: Rulați trei comenzi din istoricul vostru folosind atât înlocuirea numerică (!2), cât și înlocuirea ultimei comenzi (!!). Folosiți-vă de faptul că această înlocuire are loc înaintea executării comenzii pentru a adăuga argumente comenzilor, similar exemplului de mai sus.

Reutilizarea argumentelor comenzii anterioare

Terminalul ne oferă și o sintaxă prin care avem posibilitatea de a reutiliza argumentele comenzii anterioare în corpul comenzii curente. Acest lucru este util în reutilizarea argumentelor lungi sau complicate, pentru că evităm rescrierea lor. Astfel nu doar că suntem mai rapizi, dar evităm și apariția unor probleme din categoria typourilor.

Executăm următorul șir de comenzi:

student@uso:~$ touch a/very/long/path/that-you-dont-want-to-retype
student@uso:~$ ls -l !$
ls -l a/very/long/path/that-you-dont-want-to-retype

Observăm că șirul !$ din comanda ls -l !$ a fost înlocuit cu ultimul argument al comenzii, anterioare, touch.

Executăm următoarele comenzi:

student@uso:~$ ls ~/Desktop ~/Documents ~/Downloads
student@uso:~$ ls -l !^

Observăm că șirul !^ din comanda ls -l !^ a fost înlocuit cu primul argument al comenzii, anterioare, ~/Desktop.

Exerciții
  1. Afișați istoricul vostru de comenezi. Rulați a zecea comandă din istoric, folosind sintaxa !număr.
  2. Rulați comanda ls -lh. Acum rulați comanda anterioară, folosind sintaxa !!, cu argumentul ~/Downloads.
  3. Navigați către directorul ~/Downloads, folosit ca argument în exercițiul anterior, folosind sintaxa !$.
  4. Navigați către directorul ~/Downloads, folosit ca argument în exercițiul anterior, folosind sintaxa !^.

Ne găsim des în situația în care căutăm o comandă în istoric folosind funcția de history search, modificăm un argument al comenzii și apoi o executăm. Pentru navigarea în cadrul textului comenzii putem folosi Arrow Keys, iar pentru ștergeri putem folosi tasta Backspace sau Del.

Terminalul ne pune la dispoziție și o serie de scurtături cu ajutorul cărora putem face realiza aceeași acțiune mai rapid. Dacă vreți să vă impresionați prietenii, acesta este un mod simplu, dar eficient, de a o face.

Pentru a naviga în istoricul de comenzi putem folosi combinațiile de taste:

  • Ctrl+p - accesăm ultima comandă dată; prin apăsări succesive ciclăm prin istoricul de comenzi, de la ultima la prima comandă dată
  • Ctrl+n - accesăm comenzile în sens invers față de Ctrl+p, de la comanda actuală până la ultima comandă dată

Pentru a naviga în corpul textului putem folosi combinațiile de taste:

  • Ctrl+a - mută cursorul la începutul liniei
  • Ctrl+e - mută cursorul la sfârșitul liniei
  • Ctrl+f - mută cursorul cu un caracter înainte
  • Ctrl+b - mută cursorul cu un caracter înapoi
  • Alt+f - mută cursorul cu un cuvânt înainte
  • Alt+b - mută cursorul cu un cuvânt înapoi

Pentru a efectua ștergeri în corpul textului putem folosi combinațiile de taste:

  • Ctrl+k - șterge tot textul de la cursor până la sfârșitul liniei
  • Ctrl+u - șterge tot textul de la cursor până la începutul liniei
  • Alt+d - șterge tot textul de la cursor până la sfârșitul cuvântului

Textul șters este salvat într-un registru și poate fi folosit folosind combinația de taste Ctrl+y. Funcționalitatea este similară cu procesul de Cut (Ctrl+k, Ctrl+u sau Alt+d) și Paste (Ctrl+y).

Exerciții

Rulați comanda ls Documents/ Downloads/ Desktop/ Pictures/ Music/ înainte de a vă apuca de exerciții.

  1. Apăsați tasta Ctrl+p pentru a accesa comanda rulată anterior.
  2. Plasați-vă la începutul comenzii folosind combinația de taste Ctrl+a.
  3. Plasați-vă la sfârșitul comenzii folosind combinația de taste Ctrl+e.
  4. Mergeți, cuvânt cu cuvânt, la începutul comenzii folosind combinația de taste Alt+b.
  5. Mergeți, cuvânt cu cuvânt, la sfârșitul comenzii folosind combinația de taste Alt+f.
  6. Rulați comanda ls Docuents/ Downlads/ Dektop/ Pitures/ Muic/.
  7. Apăsați tasta Ctrl+p pentru a accesa comanda rulată anterior. Corectați typourile (greșelile de scriere) din comanda anterioară. Folosiți combinațiile de taste Ctrl+f, Ctrl+b pentru a deplasa cursorul în cadrul comenzii.
  8. Apăsați tasta Ctrl+p pentru a accesa comanda rulată anterior (comanda corectată). Avansați până la începutul cuvântului Desktop/. Ștergeți tot până la final folosind combinația de taste Ctrl+k. Acum anulați comanda curentă apăsând combinația de taste Ctrl+c. În acest moment, textul pe care l-ați șters folosind Ctrl+k (Desktop/ Pictures/ Music/) se află într-un buffer. O să rulați comanda ls pe textul din buffer. Scrieți comanda ls și apoi apăsați combinația de taste Ctrl+y. Textul a fost scris din buffer în continuarea comenzii ls (scrisă de voi).
  9. Rulați comanda ls Documents/ Downloads/ Desktop/ Pictures/ Music/. Apăsați tasta Ctrl+p pentru a accesa comanda rulată anterior (comanda corectată). Avansați până la începutul cuvântului Pictures/. Ștergeți cuvântul folosind combinația de taste Alt+d. Acum anulați comanda curentă apăsând combinația de taste Ctrl+c. În acest moment, textul pe care l-ați șters folosind Alt+d (Pictures) se află într-un buffer. O să rulați comanda ls pe textul din buffer. Scrieți comanda ls și apoi apăsați combinația de taste Ctrl+y. Textul a fost scris din buffer în continuarea comenzii ls (scrisă de voi).

Note de subsol

Inspectarea sistemului de fișiere

Cea mai importantă comandă

Așa cum spuneam mai devreme, marele avantaj al utilizării terminalului este că ne ajută să rezolvăm sarcini foarte rapid. Rezolvăm sarcini folosind utilitarele pe care le avem disponibile în linia de comandă, fie că acestea fac parte din sistemul nostru sau le-am instalat.

Cel mai important utilitar pe care îl avem la dispoziție este man. Utilitarul man ne deschide pagina de manual în care este documentat un alt utilitar pe care dorim să-l folosim.

student@uso:~$ man
What manual page do you want?

Putem consulta însăși pagina de manual a utilitarului man

student@uso:~$ man man

Vom fi întâmpinați de următorul program interactiv:

MAN(1)                        Manual pager utils                        MAN(1)
 
NAME
       man - an interface to the on-line reference manuals
 
SYNOPSIS
       man  [-C  file]  [-d]  [-D]  [--warnings[=warnings]]  [-R encoding] [-L
       locale] [-m system[,...]] [-M path] [-S list]  [-e  extension]  [-i|-I]
       [--regex|--wildcard]   [--names-only]  [-a]  [-u]  [--no-subpages]  [-P
       pager] [-r prompt] [-7] [-E encoding] [--no-hyphenation] [--no-justifi‐
       cation]  [-p  string]  [-t]  [-T[device]]  [-H[browser]] [-X[dpi]] [-Z]
       [[section] page[.section] ...] ...
       man -k [apropos options] regexp ...
       man -K [-w|-W] [-S list] [-i|-I] [--regex] [section] term ...
       man -f [whatis options] page ...
       man -l [-C file] [-d] [-D] [--warnings[=warnings]]  [-R  encoding]  [-L
       locale]  [-P  pager]  [-r  prompt]  [-7] [-E encoding] [-p string] [-t]
       [-T[device]] [-H[browser]] [-X[dpi]] [-Z] file ...
       man -w|-W [-C file] [-d] [-D] page ...
       man -c [-C file] [-d] [-D] page ...
       man [-?V]
 
 Manual page man(1) line 1 (press h for help or q to quit)

Observăm că ultima linie din terminal, Manual page man(1) line 1 (press h for help or q to quit), ne oferă mai multe informații:

  • Ne aflăm pe prima linie din prima pagină a manualului
  • Putem apăsa tasta h pentru a acesa meniul de ajutor
  • Putem apăsa tasta q pentru a ieși din manual

Navigăm cu câte o linie de terminal în joș și în sus folosind folosind tastele Ctrl+n și Ctrl+p. Putem folosi tastele Ctrl+f și Ctrl+b pentru a naviga, cu câte un ecran de terminal, în jos și în sus în pagină. Mai simplu, putem folosi tasta Enter pentru a naviga cu câte o linie în jos și tasta Space pentru a naviga cu câte un ecran în jos. Navigăm la începutul paginii folosind tasta g. Navigăm la sfârșit paginii folosind tasta G.

Putem folosi tastele j și k ca alternative pentru Arrow Down și Arrow Up. Astfel suntem mai rapizi pentru că nu ne mai mutăm mâna de pe tastele caractere.

Folosim man ca să vedem dacă un utilitar oferă o anumită funcționaltiate. Citim întreaga pagină de manual ca să vedem toate funcționalitățile sau căutăm o funcționalitate folosind cuvinte cheie. Pașii pentru căutarea unui cuvânt cheie sunt următorii:

  1. Pentru a porni funcția de căutare apăsăm tasta / în sesiunea interactivă din man.
  2. În continuare vom introduce textul pe care dorim să-l căutăm: poate să fie un cuvântul cheie pe care îl știm deja sau orice text care sperăm că ne duce la rezultatul dorit.
  3. Acum apăsăm tasta Enter. Vom fi duși la primul rezultat care se potrivește căutării, dacă acesta există.
  4. Dacă vrem să navigăm la următorul rezultat apăsăm tasta n. Dacă vrem să navigăm la un rezultat anterior apăsăm tasta N.

Căutarea2) are loc de la poziția curentă în pagină către sfârșitul paginii. Dacă am navigat deja în interiorul paginii, trebuie să avem în vedere că rezultatul de interes al căutării noastre se poate alfa undeva între începutul paginii și poziția noastră curentă3).

Interpretarea paginii de manual

La o primă vedere, textul paginii de manual poate fi intimidant; unele utilitare au mai multe opțiuni și argumente, unele opționale, altele nu. O să trecem prin sintaxă și o să vedem că lucrurile sunt mult mai simple decât par. Să analizăm pagina de manual a utilitarului ls; man ls:

LS(1)                            User Commands                           LS(1)
 
NAME
       ls - list directory contents
 
SYNOPSIS
       ls [OPTION]... [FILE]...
 
DESCRIPTION
       List  information  about  the FILEs (the current directory by default).
       Sort entries alphabetically if none of -cftuvSUX nor --sort  is  speci‐
       fied.
 
       Mandatory  arguments  to  long  options are mandatory for short options
       too.
 
       -a, --all
              do not ignore entries starting with .
 
       -A, --almost-all
              do not list implied . and ..
 
       --author
 Manual page ls(1) line 1 (press h for help or q to quit)
  • Prima secțiune care ne interesează este “DESCRIPTION”. Citim descrierea și ne dăm seama dacă utilitarul ne va ajuta în rezolvarea sarcinii pe care o avem. În cazul utilitarului ls, descrierea ne informează că acesta afișează informații despre fișierele din calea indicată, sau din directorul curent atunci când nu specificăm o cale.
  • Cea de-a doua secțiune care ne interesează este “SYNOPSIS”. Aceasta ne spune cum putem să rulăm utilitarul, ce opțiuni și argumente sunt opționale (pot lipsi) și ce opțiuni și argumente sunt obligatorii.
  <code bash>
  SYNOPSIS
         ls [OPTION]... [FILE]...
  </code>

Sintaxa [ ] ne spune că acea categorie este opțională. Astfel, pentru ls, deducem că atât opțiunile ([OPTION]...) cât și argumentele ([FILE]..., calea către fișiere sau directoare) sunt opționale. Cele trei puncte ... înseamnă mai multe din categoria precedentă: deci [OPTION]... înseamnă că nu suntem limitați la o singură opțiune, dar opțiunile pot să și lipsească în totalitate datorită [ ].

O comandă poate avea atât opțiuni, cât și argumente. Opțiunile îi spun unei comenzi cum să își modifice comportamentul, și de obicei sunt precedate de - (ex. -l, --verbose, etc.). Argumentele îi spun unei comenzi pe ce să acționeze.

În exemplul de mai jos:

student@uso:~$ ls -l Desktop/

Avem utilitarul ls care primește opțiunea -l și argumentul Desktop/.

  • Ultima observație pe care o facem este că opțiunile unei comenzi pot avea o formă prescurtată, -a, sau o formă lungă, --all. Nu este obligatoriu ca o opțiune să expună ambele forme, deși majoritatea o fac. Opțiunile în formă prescurtată pot fi concatenate și precedate de un singur -, ca în exemplul de mai jos:
student@uso:~$ ls -la Desktop/
Exerciții

Deschideți pagina de manual a utilitarului ls.

  1. Căutați opțiunea -a. Rulați comanda ls -a.
  2. Căutați opțiunea -d. Rulați comanda ls -d.
  3. Căutați opțiunea -F. Rulați comanda ls -F.
  4. Căutați cuvântul cheie list. Treceți la următoarea apariție a cuvântului cheie până ajungeți la opțiunea -l.
  5. Mergeți la finalul paginii folosind tasta G. Căutați cuvântul cheie color până ajungeți la opțiunea --color (Hint: ?).

Utilizarea pachetului tldr

Utilitarul tldr (too long, didn't read) oferă o versiune simplificată a paginilor de manual. Acesta ne va arăta un rezumat al utilizării unei comenzi cu opțiunile cele mai des folosite în comunitate.

student@uso:~$ tldr ls
ls
List directory contents.
 
 - List files one per line:
   ls -1
 
 - List all files, including hidden files:
   ls -a
 
 - Long format list (permissions, ownership, size and modification date) of all files:
   ls -la
 
 - Long format list with size displayed using human readable units (KB, MB, GB):
   ls -lh
 
 - Long format list sorted by size (descending):
   ls -lS
 
 - Long format list of all files, sorted by modification date (oldest first):
   ls -ltr

Acesta trebuie tratat ca un cheatsheet accesibil din linie de comandă. tldr nu elimină utilizarea paginilor man, dar ne ajută să găsim rapid opțiunile uzuale. Acestea fiind spuse, vă recomandăm ca întotdeaună să citiți și să înțelegeți din paginile man ce efect au opțiunile unei comenzi înainte de a le folosi. Feriți-vă să rulați comenzi orbește, pentru că așa ați găsit pe StackOverflow, tldr, etc. Întotdeauna asigurați-vă că ați înțeles cum și de ce rulați comanda și abia apoi treceți la fapte.

Exerciții: Utilizarea pachetului tldr

Instalați pachetul tldr pe mașina voastră.

  1. Accesați pagina tldr a utilitarului ls.
  2. Accesați pagina tldr a utilitarului zip.
  3. Accesați pagina tldr a utilitarului tar.
  4. Accesați pagina tldr a utilitarului git.
  5. Accesați pagina tldr a utilitarului man.

Selectarea multiplor fișiere folosind globbing

Întotdeauna când deschidem un terminal o facem pentru că vrem să realizăm o sarcină: vrem să redenumim rapid ultimele poze făcute cu telefonul de la genericul DCIM1001 la ceva util Excursie Sinaia, Ian 2020, 1001, vrem să ne testăm proiectul și să urcăm modificările pe GitHub, etc.

Până acum am aplicat diferite comenzi fie pe fișiere individuale, fie pe întreg directorul. Foarte des vom avea nevoie de un mijloc prin care să putem selecta un număr variabil de fișiere care au un nume care corespunde unui tipar (pattern) comun.

Să revenim la scenariul prezentat anterior: vrem să selectăm pozele din excursia din Sinaia. În directorul în care avem pozele din excursie avem și alte poze de la alte evenimente. Știm că pozele din excursie încep toate cu numele DCIM și apoi sunt urmate de un număr. Ceea ce vrem să facem este să selectăm toate pozele al căror nume corespunde acestui tipar și să le mutăm într-un director separat. Pentru a face acest lucru, folosim globbing, ca în exemplul de mai jos:

student@uso:~/Pictures$ mv DCIM* excursie-Sinaia-2020/

Observăm argumentul pe care l-am dat comenzii mv, și anume DCIM*. Expresia DCIM* este un exemplu de globbing: adică o expresie care descrie un tipar prin folosirea unor caractere speciale, așa cum este caracterul *. În cazul de față, expresia DCIM* înseamnă orice fișier al cărui nume începe cu șirul de caractere DCIM.

Caracterul special *

În sintaxa globbing, caracterul * poate fi înlocuit cu orice caracter de oricâte ori, sau poate lipsi cu totul. În directorul nostru home (~), executăm următoarele comenzi:

student@uso:~$ ls
Desktop    Downloads  Pictures  Templates  examples.desktop  vm-actions-log.txt
Documents  Music      Public    Videos     uso.git           workspace
 
student@uso:~$ ls -d D*
Desktop  Documents  Downloads
 
student@uso:~$ ls -d Music*
Music

Observăm că în expresia D*, caracterul * înglobează toate caracterele care urmează literei D: “esktop”, “ocuments” și “ownloads”. Observăm că în cazul expresie Music*, * nu ține locul nici unui caracter.

Caracterul special ?

În sintaxa globbing, caracterul ? înlocuiește exact un caracter, oricare ar fi acela. În directorul nostru home (~), executăm următoarele comenzi:

student@uso:~$ ls -d Musi?
Music
 
student@uso:~$ ls -d Mus??
Music
 
student@uso:~$ ls -d Music?
ls: cannot access 'Music?': No such file or directory

Observăm că expresiile Musi? și Mus?? s-au înlocuit cu succes cu numele directorului Music, dar expresia Music? a generat o eroare deoarece nu există nici un fișier Music urmat de un caracter.

Sintaxa specială []

În sintaxa globbing, folosim sintaxa [] pentru a defini o listă de caractere care pot fi folosite în înlocuire. Această sintaxă înlocuiește exact un caracter din lista oferită. În directorul nostru home (~), executăm următoarele comenzi:

student@uso:~$ ls -d Mus[ijk]c
Music
 
student@uso:~$ ls -d Mus[abc]c
ls: cannot access 'Mus[abc]c': No such file or directory

În expresia Musi[ijk]c, i-am “spus” shellului că al patrulea caracter poate să fie oricare din lista [ijk]. În acest context, globbing a găsit cu succes numele fișierului Music. În expresia Musi[abc]c, i-am “spus” shellului că al patrulea caracter poate să fie oricare din lista [abc]. Deoarece nu avem niciun fișier numit Musac, Musbc sau Muscc, comanda ne-a afișat mesajul de eroare corespunzător.

Sintaxa [] nu ne limitează la a oferi enumarații de caractere, așa cum am făcut cu [ijk] sau [abc]. Sintaxa accepta și intervale, cum observăm în exemplul de mai jos:

student@uso:~$ ls -d Mus[A-Za-z0-9]c
Music

Citim expresia [A-Za-z0-9] în următorul mod: această expresie înlocuiește un caracter din intervalul A-Z sau din intervalul a-z sau din intervalul 0-9; cu alte cuvinte înlocuiește un caracter alfa-numeric4).

Tip

Folosind sintaxa [] putem rescrie mutarea pozelor a.î. să o facem mai precisă:

student@uso:~/Pictures$ mv DCIM[0-9][0-9][0-9][0-9].jpg excursie-Sinaia-2020/

Cu expresia de mai sus vom muta toate pozele din intervalul DCIM0000 - DCIM9999.

Sintaxa specială {}

În sintaxa globbing, folosim sintaxa {} pentru a defini o listă de cuvinte (grupuri de caractere) care pot fi folosite în înlocuire. Această sintaxă înlocuiește exact un cuvânt din lista oferită. În directorul vostru home (~), executați următoarele comenzi:

student@uso:~$ ls -d {Downloads,Music}
Downloads  Music
 
student@uso:~$ ls -d {Down,Mus}*
Downloads  Music

Citim expresia {Downloads,Music}: în locul acestei expresii poate să existe cuvântul Downloads sau cuvântul Music. Observăm că putem să combinăm orice elemente de globbing, așa cum am făcut în expresia {Down,Mus}*.

Folosirea ad-litteram a caracterelor speciale

Există cazuri când numele fișierelor conțin caractere speciale. Unele fișiere pot fi prefixate cu o categorie din care fac parte, ca în exemplul de mai jos:

student@uso:~$ ls Documents/uni
'[PC] Course 01.pdf'  '[USO] Course 01.pdf'  '[USO] Course 02.pdf'

În exemplul de mai sus, fișierele pdf de curs sunt prefixate cu numele materiei: [PC], [USO]. Vrem să îi spunem sintaxei de globbing că în acest caz, șirul [USO] nu trebuie tratat ca o expresie, ci ca un șir de caracter normale. Pentru a face acest lucru, încadrăm șirul între :

student@uso:~$ ls Documents/uni/"[USO]"*
'Documents/uni/[USO] Course 01.pdf'  'Documents/uni/[USO] Course 02.pdf'

Citim expresia "[USO]"*: orice fișier al cărui nume începe cu șirul de caractere [USO] și este urmat de orice caracter. Operația prin care eliminăm semnificația specială a unui caracter poartă numele de escaping; cu alte cuvinte, informal, spunem că am făcut escaping semnificației speciale a sintaxei []. Termenul vine de la cuvântul escape (a scăpa), și exprimă că scăpăm de semnificația specială a unui caracter / set de caractere.

Exerciții

Pentru exercițiile următoare vom folosi fișierele din directorul de suport ~/uso-lab/labs/05-cli/support/support-globbing.

  1. Creați un director numit pdfs. Mutați toate fișierele cu extensia .pdf din directorul ~/uso-lab/labs/05-cli/support/support-globbing în directorul pdfs.
  2. Creați un director numit Excursie Brasov, 2020-2021. Mutați fișierele DCIM din intervalul 1400 - 1700 în directorul creat.
  3. Creați un director numit cursuri/anul-I. Mutați toate fișierele care conțin cuvintele curs sau slide în directorul creat. Folosiți sintaxa *{curs,slide}*.

Căutarea unui fișier în sistem

De multe ori ne aflăm în situația în care căutăm un fișier pe disc: ex. doar ce am clonat un proiect de pe GitHub și vrem să inspectăm fișierul Makefile pentru a vedea cum compilăm și rulăm proiectul. Un alt exemplu poate fi că vrem să vedem cum arată fișierele de test existente în proiect; de multe ori, ințelegem mai bine proiectul doar prin simpla inspectare a testelor.

Există două utilitare care ne permit să căutăm în cadrul sistemului de fișiere: locate și find.

Utilitarul locate

Utilitarul locate folosește o bază de date pentru a căuta în fișierele de pe sistem. Inspectăm pagina de manual a utilitarului pentru a vedea cum îl putem folosi, folosind comanda man:

student@uso:~$ man locate
 
SYNOPSIS
       locate [OPTION]... PATTERN...

Observăm că locate primește ca argument un șir de caractere, PATTERN, care fac parte din numele fișierului pe care în căutăm, dar nu trebuie să-i dăm numele exact:

student@uso:~$ locate todos.txt
/home/student/Desktop/todos.txt
student@uso:~$ locate todos
/home/student/Desktop/todos.txt

Putem să folosim și sintaxa globbing pentru a descrie numele fișierului căutat:

student@uso:~$ locate "*.txt"
/home/student/vm-actions-log.txt
/home/student/.local/lib/python2.7/site-packages/Keras_Applications-1.0.8.dist-info/top_level.txt
/home/student/.local/lib/python2.7/site-packages/Keras_Preprocessing-1.1.2.dist-info/top_level.txt
/home/student/.local/lib/python2.7/site-packages/Markdown-3.1.1.dist-info/entry_points.txt
/home/student/.local/lib/python2.7/site-packages/Markdown-3.1.1.dist-info/top_level.txt
/home/student/.local/lib/python2.7/site-packages/Werkzeug-1.0.1.dist-info/top_level.txt

Căutările cu locate sunt foarte rapide. Acest lucru se datorează utilizării bazei de date pentru a indexa fișierele din sistem. Într-o configurație implicită (default), baza de date se reconstruiește periodic, o dată la 24h. Asta înseamnă că locate nu va găsi fișiere care au fost create după reconstrucția bazei de date. Dacă vrem să reconstruim baza de date, folosim comanda updatedb.

Hai să clonăm repository-ul TheAlgorithms/C. Acesta conține implementările diferitor algoritmi folosind limbajul de programare C.

student@uso:~$ cd workspace
student@uso:~/workspace$ git clone https://github.com/TheAlgorithms/C.git
student@uso:~/workspace$ cd C

Fiind vorba despre un repository care implementează algoritmi clasici, ne așteptăm să găsim și algoritmi de căutare, cum ar fi binary-search. Hai să căutăm după cuvântul cheie search.

student@uso:~/workspace$ locate search | grep workspace/C
student@uso:~/workspace$ 

Observăm că nu am găsit nici un rezultat. Cum spuneam mai devreme, trebuie să reconstruim baza de date pentru a căuta în fișierele nou create.

student@uso:~/workspace/C$ sudo updatedb
[sudo] password for student: 

Comanda updatedb trebuie executată în mod privilegiat, așa că folosim sudo. Parola utilizatorului student, pe mașina noastră virtuală, este student.

student@uso:~/workspace/C$ locate search | grep workspace/C
/home/student/workspace/C/searching
/home/student/workspace/C/data_structures/binary_trees/binary_search_tree.c
/home/student/workspace/C/searching/CMakeLists.txt
/home/student/workspace/C/searching/binary_search.c
/home/student/workspace/C/searching/fibonacci_search.c
/home/student/workspace/C/searching/interpolation_search.c
/home/student/workspace/C/searching/jump_search.c
/home/student/workspace/C/searching/linear_search.c
/home/student/workspace/C/searching/modified_binary_search.c
/home/student/workspace/C/searching/other_binary_search.c
/home/student/workspace/C/searching/pattern_search
/home/student/workspace/C/searching/ternary_search.c
/home/student/workspace/C/searching/pattern_search/CMakeLists.txt
/home/student/workspace/C/searching/pattern_search/boyer_moore_search.c
/home/student/workspace/C/searching/pattern_search/naive_search.c
/home/student/workspace/C/searching/pattern_search/rabin_karp_search.c
Exerciții
  1. Folosind locate căutați fișierele care conțin șirul bubble_sort în nume.
  2. Folosind locate căutați fișierele care conțin șirul quick_sort în nume.
  3. Folosind locate căutați fișierele care conțin șirul merge_sort în nume.
  4. Folosind locate căutați fișierele care conțin șirul sort în nume.

Utilitarul find

Utilitarul find îndeplinește același scop: căuta în fișierele de pe sistem. find este un utilitar mai complex decât locate. Acesta ne permite să căutăm fișiere după nume, permisiuni, tipul fișierelor, data ultimei modificări și multe altele. Inspectăm pagina de manual a utilitarului pentru a vedea cum îl putem folosi.

student@uso:~$ man find
 
SYNOPSIS
       find  [-H]  [-L]  [-P]  [-D  debugopts]  [-Olevel]  [starting-point...]
       [expression]

La o primă vedere, find poate părea complex și intimidant, dar lucrurile stau foarte simplu. Folosim find cu sintaxa find [starting-point] [expression], ca în exemplul de mai jos:

student@uso:~$ find . -name "*search*"
./C/searching
./C/searching/linear_search.c
./C/searching/other_binary_search.c
./C/searching/binary_search.c
./C/searching/modified_binary_search.c
./C/searching/jump_search.c
./C/searching/interpolation_search.c
./C/searching/fibonacci_search.c
./C/searching/ternary_search.c
./C/searching/pattern_searc h
./C/searching/pattern_search/naive_search.c
./C/searching/pattern_search/boyer_moore_search.c
./C/searching/pattern_search/rabin_karp_search.c
./C/data_structures/binary_trees/binary_search_tree.c

În exemplul de mai sus observă că am folosit ca starting-point . (căutarea pleacă din directorul curent), iar ca expression -name "*search*".

Utilitarul find folosește o expresie compusă pentru căutare. În exemplul anterior am folosit opțiunea -name PATTERN. Exact ca în cazul utilitarului locate, PATTERN poate folosi sintaxa globbing, așa cum am făcut în exemplul de mai sus "*search*".

Atunci când folosim sintaxa globbing, trebuie să fim atenți să încadrăm PATTERN între " (ghilimele), așa cum am făcut în exemplul de mai sus. Trebuie să facem asta pentru ca sintaxa globbing să fie interpretată de către utilitarul find și nu de către terminalul (bash) din care lansăm utilitarul.

Scenarii complexe de căutare

Utilitarul find are o lungă listă de opțiuni pe care le putem folosi în expresii de căutare. Una din opțiunile mai cunoscute este -type care ne oferă posibilitatea de a căuta după tipul unui fișier:

student@uso:~$ find workspace/C -type f
workspace/C/leetcode/src/226.c
workspace/C/leetcode/src/700.c
workspace/C/leetcode/src/278.c
[...]

În exemplul de mai sus i-am transmis utilitarului find că vrem să căutăm în directorul ~/workspace/C toate fișierele text (regular file) -type f.

Exercițiu: Accesați pagina de manual a utilitarului find (man find) și căutați opțiunea -type. Căutați în directorul workspace/C după fiecare tip de fișier pentru care oferă suport opțiunea -type.

Reminder: pentru a căuta în man folosim / pentru a intra în search mode și apoi introducem textul pe care îl căutam -type urmat de tasta Enter; pentru a ne duce la următorul rezultat al căutării folosim tasta n (next).

În cadrul unei căutări putem să combinăm opțiunile de căutare:

student@uso:~$ find workspace/C -type f -name "*search*"
workspace/C/searching/modified_binary_search.c
workspace/C/searching/ternary_search.c
workspace/C/searching/jump_search.c
workspace/C/searching/binary_search.c

În exemplul de mai sus căutăm toate fișierele text care conțin șirul search în nume.

Utilitarul find ne permite să executăm comenzi asupra rezultatelor căutării. Facem acest cu opțiunea -exec command {} ;. Atunci când folosim -exec, rezultatul căutării va înlocui șirul '{}' în textul comenzii; comanda de executat trebuie să se termine în caracterul ;.

Observăm exemplul de mai jos:

student@uso:~$ find workspace/C -type f -name "*search*" -exec ls -l {} \;
-rw-r--r-- 1 student student 3312 sep 17 19:20 workspace/C/searching/modified_binary_search.c
-rw-r--r-- 1 student student 1782 sep 17 19:20 workspace/C/searching/ternary_search.c
-rw-r--r-- 1 student student 1624 sep 17 19:20 workspace/C/searching/jump_search.c
-rw-r--r-- 1 student student 2799 sep 17 19:20 workspace/C/searching/binary_search.c
-rw-r--r-- 1 student student 867 sep 17 19:20 workspace/C/searching/other_binary_search.c

În exemplul de mai sus, argumetul opțiunii exec este ls -l {} \;. În cuvinte, pentru fiecare fișier text care conține șirul search vom afișa informații în format lung (ls -l {}). Observăm că -exec se încheie cu \;: este nevoie să escapăm caracterul ; pentru ca acesta să fie interpretat de către utilitarul find și nu de către terminalul în care rulăm, exact ca în cazul -name PATTERN.

În secțiunile ce urmează vom vedea cum ne folosim de opțiunea exec pentru a face recursiv search & replace în fișiere.

Exerciții
  1. Folosind find căutați fișierele care conțin șirul bubble_sort în nume.
  2. Folosind find căutați fișierele care conțin șirul quick_sort în nume.
  3. Folosind find căutați fișierele care conțin șirul merge_sort în nume.
  4. Folosind find căutați fișierele care conțin șirul sort în nume.

Note de subsol

Inspectarea fișierelor

Inspectarea rapida a conținutului fișierelor

În secțiunea anterioară, improve_cli_inspect_fs, am văzut cum căutăm fișiere în sistem cu ajutorul utilitarelor locate și find. Căutăm un fișier cu un scop: vrem să găsim fișierul README pentru informații despre compilarea proiectului, vrem să ne amintim un detaliu de implementare din cod, etc.

De cele mai multe ori acțiunea noastră se poate grupa în una din următoarele două categorii:

  • Ne dorim să inspectăm rapid conținutul fișierelor pentru a ne da seama dacă am găsit informația căutată.
  • Ne dorim să afișăm pe ecran conținutul fișierelor pentru a extrage și prelucra informații din acestea.

Căutarea informației într-un fișier

Pentru a vedea rapid conținutul unui fișier folosim utlitarul less.

De fapt, comanda man folosește utilitarul less pentru a afișa paginile de manual. less este pagerul implicit în majoritatea distribuțiilor Linux.

Avem fișierul workspace/C/searching/binary_search.c. Vrem să ne facem rapid o idee despre cum arată implementarea algoritmului binary_search. Inspectăm conținutul fișierului workspace/C/searching/binary_search.c, folosind utilitarul less, ca în exemplul de mai jos:

student@uso:~$ less workspace/C/searching/binary_search.c
 
/**
 * @file
 * @brief Program to perform [binary
 * search](https://en.wikipedia.org/wiki/Binary_search_algorithm) of a target
 * value in a given *sorted* array.
 * @authors [James McDermott](https://github.com/theycallmemac) - recursive
 * algorithm
 * @authors [Krishna Vedala](https://github.com/kvedala) - iterative algorithm
 */
#include <assert.h>
#include <stdio.h>
 
/** Recursive implementation
 * \param[in] arr array to search
 * \param l left index of search range
 * \param r right index of search range
 * \param x target value to search for
 * \returns location of x assuming array arr[l..r] is present
 * \returns -1 otherwise
 */
int binarysearch1(const int *arr, int l, int r, int x)
{
    if (r >= l)
    {
        int mid = l + (r - l) / 2;
 
        // If element is present at middle
        if (arr[mid] == x)
            return mid;
: 

Observăm că acum avem o sesiune interactivă în interiorul căreia putem explora fișierul.

În cadrul unei sesiuni less putem folosi aceeleași taste ca în cadrul sesiunii interactive man pentru navigarea în pagină:

  • Ctrl+n/Ctrl+p sau j/k pentru a naviga, cu câte o linie, în jos, respectiv în sus; recomandăm utlizarea tastelor j/k pentru a fi mai eficienți
  • Ctrl+f/Ctrl+b pentru a naviga, cu câte o pagină de terminal, în jos, respectiv în sus
  • Search (/, ?, n, N)
  • Go up (g), go down (G)
  • Help (h) pentru a afla mai multe despre cum putem folosi mai bine sesiunea interactivă
  • Quit (q) pentru a ieși din sesiunea interactivă

Toate aceste informații se găsesc în pagina de manual a utilitarului less: man less.

În sesiunea interactivă căutăm după cuvântul cheie search. Pentru a porni căutarea apăsăm tasta /, introducem textul căutat (search) și apăsăm tasta Enter. Apăsăm tasta n pentru a merge la următoarea apariție a textului căutat; apăsăm n până când ajungem la implementarea funcției binarysearch2.

Exerciții
  1. Analizați, folosind less, algoritmul de căutare din fișierul workspace/C/searching/linear_search.c. Ce implementare este mai eficientă: binary_search sau linear_search?
  2. Analizați, folosind less, algoritmul de sortare quick_sort. Folosiți utilitarul find pentru a găsi fișierul sursă care conține implementarea.
  3. Analizați, folosind less, algoritmul de sortare merge_sort. Folosiți utilitarul find pentru a găsi fișierul sursă care conține implementarea.
  4. Căutați pe Google detalii despre cei doi algoritmi de sortare și încercați să vă răspundeți la întrebarea: Când folosim merge_sort și când folosim quick_sort?

Prelucrarea informației dintr-un fișier

Pentru a afișa pe ecran conținutul unui fișier folosim utlitarul cat. Rulăm comanda de mai jos, pentru a exemplifica:

student@uso:~$ cat workspace/C/searching/binary_search.c
/**
 * @file
 * @brief Program to perform [binary
 * search](https://en.wikipedia.org/wiki/Binary_search_algorithm) of a target
 * value in a given *sorted* array.
 * @authors [James McDermott](https://github.com/theycallmemac) - recursive
 * algorithm
 * @authors [Krishna Vedala](https://github.com/kvedala) - iterative algorithm
 */
#include <assert.h>
#include <stdio.h>
 
[...]

Observăm că pentru un fișier cu un număr mare de linii, așa cum este binary_search.c, afișarea întregului conținut pe ecran devine un impediment în a putea înțelege și urmări conținutul. De aceea vă încurajăm să folosiți less în loc de cat pentru a inspecta un fișier: vă este mult mai ușor să vă plimbați în interiorul fișierului și puteți folosi funcția search pentru a căuta în fișier. De asemeni, folosind less vă păstrați consola curată și puteți urmări mai ușor ce comenzi ați dat anterior și care au fost rezultatele acestora.

Folosim comanda cat în combinație cu alte comenzi pentru a extrage sau filtra conținutul anumitor fișiere. Comanda cat primește ca argumente calea către unul sau mai multe fișiere și afișează pe ecran conținutul concatenat al acestora.

Un exemplu uzual este faptul că vrem să extragem informațiile despre starea memoriei sistemului din fișierul /proc/meminfo. Pentru aceasta rulăm comanda de mai jos:

student@uso:~$ cat /proc/meminfo | grep "Mem"
MemTotal:        2041248 kB
MemFree:          236092 kB
MemAvailable:     874420 kB

În exemplul de mai sus folosim cat pentru a oferi ca intrare conținutul fișierului /proc/meminfo utilitarului grep; cu utilitarul grep filtrăm conținutul după textul "Mem". Cu alte cuvinte, outputul comenzii cat /proc/meminfo, adică conținutul fișierului /proc/meminfo este textul pe care utilitarul grep îl prelucrează.

Exercițiu: Plecând de la exemplul de mai sus, extrageți din fișierul /proc/cpuinfo dimensiunea memoriei cache a procesorului vostru; filtrați conținutul după textul "cache".

Afișarea parțială a unui fișier

Am văzut că utilitarul cat afișează întreg conținutul unui fișier. Există scenarii în care suntem interesați doar de începutul sau sfârșitul unui conținut. Pentru aceste cazuri putem folosi utilitarele:

  • head - afișează primele 10 linii din conținut
  • tail - afișează ultimele 10 linii din conținut

Valoarea 10 este valoarea implicită a ambelor utilitare, dar putem specifica un alt număr de linii.

Așa cum am observat în capitolul despre procese, putem folosi utilitarul ps pentru a vedea care sunt procesele din sistem și ce resurse consumă acestea. Memoria sistemului este una dintre cele mai importante resurse; dacă sistemul nostru rămâne fără memorie disponibilă, tot sistemul este afectat: sistemul se va “mișca” mai greu, procesele se vor “mișca” mai greu sau pot chiar să își întrerupă activitatea. Știind acest lucru, suntem interesați să vedem care sunt primele zece procese care consumă cea mai multă memorie.

Folosim utilitarul ps pentru a afișa toate procesele din sistem:

student@uso:~$ ps -e -ouser,uid,pid,%mem,%cpu,rss,cmd --sort=%mem
 
USER       UID   PID %MEM %CPU   RSS CMD
root         0     2  0.0  0.0     0 [kthreadd]
 
[...]
 
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
student   1000  8307  3.1  0.0 64628 /usr/lib/evolution/evolution-calendar-factory
root         0  1338  3.8  0.0 78880 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
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
student   1000  8437  8.4  0.0 171916 /usr/bin/gnome-software --gapplication-service
student   1000  7938 18.0  0.1 368304 /usr/bin/gnome-shell

Am folosit opțiunea -ouser,uid,pid,%mem,%cpu,rss,cmd pentru a selecta coloanele pe care să le afișeze ps.

Am folosit opțiunea --sort cu argumentul %mem pentru a sorta procesele după procentul de memorie folosită.

Folosiți comanda ps -e -ouser,uid,pid,%mem,%cpu,rss,cmd --sort=%mem | less pentru a vizualiza rezultatul comenzii ps într-o sesiune interactivă less.

Observăm că avem procesele sortate crescător după coloana %MEM. Folosim utilitarul tail pentru a extrage din rezultatul ps cele mai consumatoare zece procese:

student@uso:~$ ps -e -ouser,uid,pid,%mem,%cpu,rss,cmd --sort=%mem | tail
student   1000 12966  1.8  0.0 38216 /usr/lib/gnome-terminal/gnome-terminal-server
root         0  1074  2.2  0.0 45460 /usr/bin/containerd
student   1000  8274  2.3  0.0 48296 nautilus-desktop
root         0   336  2.6  0.0 53612 /lib/systemd/systemd-journald
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
student   1000  8307  3.1  0.0 64628 /usr/lib/evolution/evolution-calendar-factory
root         0  1338  3.8  0.0 78880 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
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
student   1000  8437  8.4  0.0 171916 /usr/bin/gnome-software --gapplication-service
student   1000  7938 18.0  0.1 368248 /usr/bin/gnome-shell

În acest moment am găsit răspunsul căutat, dar avem două mici neajunsuri:

  • Ne lipsește antetul, așa că nu știm ce informații avem pe coloane
  • Procesele sunt sortate crescător, a.î. cel mai consumator este ultimul; vrem să fie sortate descrescător

Rezolvăm cele două probleme prin intermediul opțiunii --sort: dacă punem un - (minus) în fața argumentului după care sortăm, o să sortăm descrescător. Rulăm comanda:

student@uso:~$ ps -e -ouser,uid,pid,%mem,%cpu,rss,cmd --sort=-%mem | less
USER       UID   PID %MEM %CPU   RSS CMD
student   1000  7938 18.0  0.1 368248 /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
 
[...]

Observăm că acum avem formatul dorit. Ne mai rămâne să extragem primele 11 linii din rezultatul comenzii de mai sus; 11 deoarece prima este linia antetului iar următoarele zece sunt procesele de interes. Pentru aceasta utilizăm comanda head cu opțiunea -11 ca în 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
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.
  2. Afișați ultimele zece procese sortate în funcție de utilizarea procesorului (Hint: CPU). Nu uitați să includeți antetul.

Căutarea în fișiere

Așa cum am văzut până în acest punct din carte, majoritatea comenzilor Linux afișează o gamă largă de informații pe care apoi utilizatorul (adică noi) le filtrează pentru a extrage ceea ce îl intresează. La începutul acestei secțiuni, dar și de-a lungul cărții, am folosit utilitarul grep ca să filtrăm rezultatul unei comenzi.

Comanda grep este una dintre cele mai folosite în linie de comandă. Sintaxa de folosire a grep este următoarea:

SYNOPSIS
       grep [OPTIONS] PATTERN [FILE...]

grep caută PATTERN în lista de fișiere primită ca argument și afișează liniile care conțin PATTERN-ul căutat. Atunci când nu primește nici un fișier, citește text de la tastatură (intrarea standard) și afișează liniile care conțin PATTERN-ul căutat.

Până acum noi am utilizat grep după modelul de mai jos:

student@uso:~$ cat workspace/C/searching/binary_search.c | grep search
 * search](https://en.wikipedia.org/wiki/Binary_search_algorithm) of a target
 * \param[in] arr array to search
 * \param l left index of search range
 * \param r right index of search range
 * \param x target value to search for
int binarysearch1(const int *arr, int l, int r, int x)
 
[...]

În exemplul de mai sus, operatorul | trimite textul afișat de comanda cat către intrarea standard a comenzii grep. Vom discuta mai multe despre acesta în secțiunea improve_cli_improve_shell_oneliners.

Comanda următoare este echivalentă cu cea de mai sus:

student@uso:~$ grep search workspace/C/searching/binary_search.c
 * search](https://en.wikipedia.org/wiki/Binary_search_algorithm) of a target
 * \param[in] arr array to search
 * \param l left index of search range
 * \param r right index of search range
 * \param x target value to search for
int binarysearch1(const int *arr, int l, int r, int x)
[...]

Observăm modul de folosire: grep PATTERN cale/către/fișier.

Exerciții

  1. Căutați patternul “l” în fișierul binary_search.c, pentru a vedea unde este folosit parametrul left. Observați cât de multe rezultate irelevante ați găsit datorită faptului că am căutat doar caracterul l. Aici există o lecție de învățat. Numele variabilelor sunt foarte improtante: nu fac doar codul mai ușor de înțeles, dar ajută și căutarea. Folosiți patternul “param l” în încercarea de a restrânge căutarea.
  2. Căutați patternul “arr” în fișierul binary_search.c.
  3. Căutați patternul “binarysearch1” în fișierul binary_search.c pentru a vedea cum este apelată funcția de căutare.

Opțiuni uzuale ale grep

Afișarea numărului liniei care conține patternul

Folosim opțiunea -n pentru a afișa și numărul liniei care conține patternul căutat:

student@uso:~$ grep -n search workspace/C/searching/binary_search.c
4: * search](https://en.wikipedia.org/wiki/Binary_search_algorithm) of a target
14: * \param[in] arr array to search
15: * \param l left index of search range
16: * \param r right index of search range
17: * \param x target value to search for
21:int binarysearch1(const int *arr, int l, int r, int x)
[...]
Căutarea case-insensitive

Implicit, grep caută în mod case-sensitive patternul, așa cum putem observa din exemplul de mai jos:

student@uso:~$ grep Search workspace/C/searching/binary_search.c

Pentru a efectua căutarea textului în mod case-insesnsitive, folosim opțiunea -i, ca în exemplul de mai jos:

student@uso:~$ grep -i Search workspace/C/searching/binary_search.c
 * search](https://en.wikipedia.org/wiki/Binary_search_algorithm) of a target
 * \param[in] arr array to search
 * \param l left index of search range
 * \param r right index of search range
 * \param x target value to search for
int binarysearch1(const int *arr, int l, int r, int x)
[...]
Excluderea unui pattern

Pentru a afișa toate liniile, mai puțin pe cele care conțin pattern, folosim opțiunea -v, ca în exemplul de mai jos:

student@uso:~$ grep -v search workspace/C/searching/binary_search.c | less
/**
 * @file
 * @brief Program to perform [binary
 * value in a given *sorted* array.
 * @authors [James McDermott](https://github.com/theycallmemac) - recursive
 * algorithm
 * @authors [Krishna Vedala](https://github.com/kvedala) - iterative algorithm
 */
#include <assert.h>
#include <stdio.h>
[...]
Căutarea recursivă a unui pattern

În căutările noastre de până acum, ca și în exemplele de mai sus, am presupus că știm în ce fișiere se găsește informația căutată de noi. Acest lucru este adevărat pentru fișiere din sistem cu informații bine cunoscute, cum ar fi /proc/meminfo, dar atunci când lucrăm cu un proiect nou nu vom ști în ce fișiere să căutăm informația dorită. De exemplu, în cazul proiectului cu algoritmi implementați în C, noi am făcut presupunerea că vom găsi linii care conțin patternul search în fișierul workspace/C/searching/binary_search.c.

Atunci când nu știm în ce fișiere se află informația căutată putem să-i spunem lui grep să caute recursiv prin toată ierarhia de fișiere dintr-un anumit director. Pentru a efectua o căutare recursivă folosim opțiunea -r, ca în exemplul de mai jos:

student@uso:~$ grep -r search workspace/C/ | less
 
workspace/C/leetcode/src/700.c:struct TreeNode *searchBST(struct TreeNode *root, int val)
workspace/C/leetcode/src/700.c:        return searchBST(root->left, val);
workspace/C/leetcode/src/700.c:        return searchBST(root->right, val);
workspace/C/leetcode/src/35.c:int searchInsert(int *nums, int numsSize, int target)
workspace/C/leetcode/src/35.c:int searchInsert(int *nums, int numsSize, int target)
workspace/C/leetcode/src/35.c:        return searchInsert(nums, numsSize - 1, target);
workspace/C/leetcode/src/704.c:int search(int *nums, int numsSize, int target)
workspace/C/leetcode/src/704.c:/* Another solution: Using bsearch() */
workspace/C/leetcode/src/704.c:int search(int *nums, int numsSize, int target)
workspace/C/leetcode/src/704.c:    int *ret = bsearch(&target, nums, numsSize, sizeof(int), cmpint);
workspace/C/leetcode/README.md:|35|[Search Insert Position](https://leetcode.com/problems/search-insert-position/) | [C](./src/35.c)|Easy|
workspace/C/leetcode/README.md:|108|[Convert Sorted Array to Binary Search Tree](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/) | [C](./src/108.c)|Easy|
workspace/C/leetcode/README.md:|109|[Convert Sorted List to Binary Search Tree](https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/) | [C](./src/109.c)|Medium|
workspace/C/leetcode/README.md:|173|[Binary Search Tree Iterator](https://leetcode.com/problems/binary-search-tree-iterator/) | [C](./src/173.c)|Medium|
workspace/C/leetcode/README.md:|700|[Search in a Binary Search Tree](https://leetcode.com/problems/search-in-a-binary-search-tree/) | [C](./src/700.c)|Easy|
workspace/C/leetcode/README.md:|701|[Insert into a Binary Search Tree](https://leetcode.com/problems/insert-into-a-binary-search-tree/) | [C](./src/701.c)|Medium|
workspace/C/leetcode/README.md:|704|[Binary Search](https://leetcode.com/problems/binary-search/) | [C](./src/704.c)|Easy|
workspace/C/DIRECTORY.md:    * [Binary Search Tree](https://github.com/TheAlgorithms/C/blob/master/data_structures/binary_trees/binary_search_tree.c)
workspace/C/DIRECTORY.md:  * [Binary Search](https://github.com/TheAlgorithms/C/blob/master/searching/binary_search.c)
workspace/C/DIRECTORY.md:  * [Fibonacci Search](https://github.com/TheAlgorithms/C/blob/master/searching/fibonacci_search.c)
Best practice

De cele mai multe ori vom folosi opțiunile -n, -i și -r în aceelași timp. În cazul nostru de până acum, aceasta se traduce în:

student@uso:~$ grep -nri search workspace/C/ | less
 
workspace/C/leetcode/src/700.c:10:struct TreeNode *searchBST(struct TreeNode *root, int val)
workspace/C/leetcode/src/700.c:21:        return searchBST(root->left, val);
workspace/C/leetcode/src/700.c:25:        return searchBST(root->right, val);
workspace/C/leetcode/src/35.c:1:int searchInsert(int *nums, int numsSize, int target)
workspace/C/leetcode/src/35.c:18:int searchInsert(int *nums, int numsSize, int target)
workspace/C/leetcode/src/35.c:27:        return searchInsert(nums, numsSize - 1, target);
workspace/C/leetcode/src/704.c:1:int search(int *nums, int numsSize, int target)
workspace/C/leetcode/src/704.c:23:/* Another solution: Using bsearch() */
workspace/C/leetcode/src/704.c:26:int search(int *nums, int numsSize, int target)
workspace/C/leetcode/src/704.c:28:    int *ret = bsearch(&target, nums, numsSize, sizeof(int), cmpint);
workspace/C/leetcode/README.md:26:|35|[Search Insert Position](https://leetcode.com/problems/search-insert-position/) | [C](./src/35.c)|Easy|
workspace/C/leetcode/README.md:35:|108|[Convert Sorted Array to Binary Search Tree](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/) | [C](./src/108.c)|Easy|
workspace/C/leetcode/README.md:36:|109|[Convert Sorted List to Binary Search Tree](https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/) | [C](./src/109.c)|Medium|
workspace/C/leetcode/README.md:47:|173|[Binary Search Tree Iterator](https://leetcode.com/problems/binary-search-tree-iterator/) | [C](./src/173.c)|Medium|
workspace/C/leetcode/README.md:78:|700|[Search in a Binary Search Tree](https://leetcode.com/problems/search-in-a-binary-search-tree/) | [C](./src/700.c)|Easy|
workspace/C/leetcode/README.md:79:|701|[Insert into a Binary Search Tree](https://leetcode.com/problems/insert-into-a-binary-search-tree/) | [C](./src/701.c)|Medium|
workspace/C/leetcode/README.md:80:|704|[Binary Search](https://leetcode.com/problems/binary-search/) | [C](./src/704.c)|Easy|
workspace/C/.github/pull_request_template.md:20:- [ ] Search previous suggestions before making a new one, as yours may be a duplicate.
workspace/C/DIRECTORY.md:31:    * [Binary Search Tree](https://github.com/TheAlgorithms/C/blob/master/data_structures/binary_trees/binary_search_tree.c)
workspace/C/DIRECTORY.md:338:## Searching
:

Astfel avem o căutare cât mai cuprinzătoare și putem folosi funcția de căutare în sesiunea interactivă less pentru a găsi linia și fișierul care ne interesează.

Căutarea unui cuvânt

Din rezultatele căutărilor de mai sus observăm că grep caută patternul dat ca un subșir. Acest lucru se vede foarte ușor în rezultatul anterior:

student@uso:~$ grep -nri search workspace/C/ | less
 
workspace/C/leetcode/src/700.c:10:struct TreeNode *searchBST(struct TreeNode *root, int val)

Observăm că patternul search se regăsește în șirul *searchBST. Dacă dorim să căutăm cuvântul search folosim opțiunea -w (word) pentru a-i transmite utilitarului că patternul trebuie tratat ca un cuvânt, ca în exemplul de mai jos:

student@uso:~$ grep -nri -w "search" workspace/C/ | less
 
workspace/C/leetcode/src/704.c:1:int search(int *nums, int numsSize, int target)
workspace/C/leetcode/src/704.c:26:int search(int *nums, int numsSize, int target)
workspace/C/leetcode/README.md:26:|35|[Search Insert Position](https://leetcode.com/problems/search-insert-position/) | [C](./src/35.c)|Easy|
[...]

Observăm că acum rezultatele conțin doar cuvântul search.

Exerciții
  1. Găsiți toate fișierele care includ headerul stdio.h.
  2. Găsiți toate aparițiile patternului binarySearch.
  3. Găsiți toate aparițiile patternului quickSort.

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:

  • Dacă numărul întors are valoarea 0, procesul și-a încheiat execuția cu succes.
  • Dacă numărul întors are orice altă valoare, procesul și-a încheiat execuția cu eroare, iar codul întors poate fi folosit pentru a afla mai multe informații despre eroarea pe care a întors-o procesul. În pagina man a utilitarului ls este specificat:
    Exit status:
           0      if OK,
     
           1      if minor problems (e.g., cannot access subdirectory),
     
           2      if serious trouble (e.g., cannot access command-line argument).

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:

  • O să actualizăm indexul surselor de pachete folosind apt update
  • O să instalăm pachetul care conține aplicația folosind apt install
  • O să rulăm aplicația pentru a valida că instalarea a fost cu succes.

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:

  • Operatorul ; - este folosit pentru separarea comenzilor Urmăm exemplul de mai jos:

    student@uso:~$ mkdir demo; cd demo; touch Hello; ls
    Hello

    În exemplul de mai sus am creat directorul demo, am navigat în interiorul său, am creat fișierul Hello și am afișat conținutul directorului. Am făcut toate acestea înlănțuind comenzile mkdir, cd, touch și ls cu ajutorul operatorului ;.

    Operatorul ; este folosit pentru separarea comenzilor, dar nu ține cont dacă comenzile anterioare au fost executate cu succes sau nu. Urmăm exemplul de mai jos:

    student@uso:~$ mkdir operators/demo; cd operators/demo
    mkdir: cannot create directory ‘operators/demo’: No such file or directory
    -bash: cd: operators/demo: No such file or directory

    În exemplul de mai sus, comanda mkdir a eșuat deoarece nu a găsit directorul operators în care să creeze directorul demo. Cu toate acestea, operatorul ; doar separă comenzile între ele, așa că și comanda cd operators/demo a fost executată, și și aceasta a eșuat deoarece nu există calea operators/demo.

    Folosim operatorul ; pentru a înlănțui comenzi care sunt independente unele de altele, și deci execuția lor nu depinde de succesul unei comenzi precedente.

  • Operatorul binar && (și logic) - execută a doua comandă doar dacă precedenta s-a executat cu succes. Exemplul anterior devine:

    student@uso:~$ mkdir operators/demo && cd operators/demo
    mkdir: cannot create directory ‘operators/demo’: No such file or directory

    Observăm că din moment ce comanda mkdir a eșuat, comanda cd nu a mai fost executată.

  • Operatorul binar || (sau logic) - execută a doua comandă doar dacă prima s-a terminat cu eșec. Urmărim exemplul de mai jos:

    student@uso:~$ (ls -d operators || mkdir operators) && ls -d operators
    ls: cannot access 'operators': No such file or directory
    operators
    student@uso:~$ (ls -d operators || mkdir operators) && ls -d operators
    operators
    operators

    În exemplul de mai sus, prima comandă ls a eșuat, așa că a fost executată comanda mkdir și apoi a fost executată ultima comandă ls. La cea de-a doua rulare, a fost executată cu succes prima comandă ls, așa că comanda mkdir nu a mai fost executată, și apoi a fost executată ultima comandă ls.

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:

  • Am afișat informații despre procesele din sistem și am filtrat după numele unui proces:
    student@uso:~$ ps -e | grep firefox
    14912 pts/0    00:00:19 firefox
  • Am extras primele zece procese care consumă cel mai mare procent de memorie:
    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

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:

  • STDIN - fluxul de intrare standard, referit și ca “citit de la tastatură”. Spunem că un program care citește date de intrare din linie de comandă, deci așteaptă de la utilizator, citește de la intrarea standard; de aici și denumirea “citit de la tastatură”. Complementul citirii de la tastatură este citirea datelor dintr-un fișier.
  • STDOUT - fluxul de ieșire standard, referit și ca “afișare pe ecran”. Spunem că un program afișează datele de ieșire pe ecran, adică scrie rezultatele procesărilor efectuate la ieșirea standard. Complementul afișării pe ecran este scrierea rezultatelor într-un fișier.
  • STDERR - fluxul de ieșire standard al erorilor. Un program corect scris o să scrie erorile în fluxul de ieșire al erorilor. Acest lucru permite filtrarea erorilor.

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

  • STDIN are asociat descriptorul de fișier 0
  • STDOUT are asociat descriptorul de fișier 1
  • STDERR are asociat descriptorul de fișier 2

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:

  • Fișierul /dev/null este un fișier care ignoră orice este scris în el. Este echivalentul unei găuri negre în sistemul nostru. Cu ajutorul său, putem rescrie exemplul de mai sus în modul următor:

    student@uso:~$ firefox &> /dev/null &
    [1] 10349
    student@uso:~$ firefox > /dev/null 2>&1 &
    [2] 10595

    Acum orice va genera firefox va fi scris în /dev/null, care va consuma textul primit fără a ocupa spațiu pe disc.

  • Fișierul /dev/zero este un generator de octeți. Acesta generează atâția octeți cu valoarea zero (0)5) cât îi sunt ceruți. Urmăm exemplul:

    student@uso:~$ cat /dev/zero | xxd
    00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    [...]
    ^C

    Deoarece citim din generator, comanda cat va afișa o infinitate de octeți cu valoarea zero. Utilitarul xxd afișează în hexazecimal textul primit la STDIN. Trecem rezultatul lui cat prin xxd deoarece valoarea 0 nu este un caracter printabil. Cu alte cuvinte nu este un caracter obișnuit, ca cele de pe tastatură, deoarece nu are un echivalent grafic. Folosim Ctrl+c pentru a opri execția.

    Exercițiu: Rulați comanda cat /dev/zero pentru a înțelege nevoia utilitarului xxd din exemplul de mai sus.

    • Fișierul /dev/urandom este un alt generator de octeți.

    </blockquote></HTML>

    Acesta generează atâția octeți cu valoare random cât îi sunt ceruți.

    Exercițiu: Rulați comenzile din exemplul anterior, dar acum citiți din /dev/urandom.

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:

  • if - input file - calea către fișierul de unde citim
  • of - output file - calea către fișierul unde scriem
  • bs - block size - dimensiunea unui block citit din if
  • count - block count - numărul de block-uri citite

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

Sumar: Îmbunătățirea lucrului în linia de comandă

Sumar: Scurtături în terminal

  • Funcția de auto-complete este extrem de utilă și îmbunătățește în mod dramatic viteza cu care realizăm acțiuni în terminal.

    • Funcția de auto-complete nu ne face doar mai rapizi, dar putem folosi tasta Tab și pt a confirma că comanda este validă.

  • Consultăm istoricul comenzilor folosind comanda history.

    • Navigăm prin istoricul comenzilor folosind Arrow Keys, Ctrl+r, expandarea comenzilor anterioare sau chiar expandarea argumentelor comenzii anterioare.

    • Navigăm în interiorul unei comenzi (Ctrl+a, Ctrl+e, Alt+f, Alt+b), putem efectua modificări (Ctrl+k, Ctrl+u, Alt+d) și putem insera textul șters (Ctrl+y).

Sumar: Inspectarea sistemului de fișiere

Inspectarea paginilor de manual

  • Navigarea prin paginile manualului:
    • Ctrl+n/Ctrl+p sau j/k pentru a naviga, cu câte o linie, în jos, respectiv în sus; recomandăm utlizarea tastelor j/k pentru a fi mai eficienți
    • Search (/, ?, n, N)
    • Go up (g), go down (G)
    • Help (h) pentru a afla mai multe despre cum putem folosi mai bine sesiunea interactivă
    • Quit (q) pentru a ieși din sesiunea interactivă
  • Prezentarea secțiunilor din manual: man printf vs man 3 printf.
  • Prezentarea pachetului tldr - poate fi util pentru cazurile uzuale, dar nu trebuie să ne fie frică să căutăm în man pentru detalii

Selectarea multiplor fișiere folosind globbing

Folosim globbing pentru a selecta mai multe fișiere al căror nume corespunde unui tipar:

  • Caracterul * poate fi înlocuit cu orice caracter de oricâte ori, sau poate lipsi cu totul.
  • Caracterul ? înlocuiește exact un caracter, oricare ar fi acela.
  • Folosim sintaxa [] pentru a defini o listă de caractere care pot fi folosite în înlocuire.
  • Folosim sintaxa {} pentru a defini o listă de cuvinte (grupuri de caractere) care pot fi folosite în înlocuire.
  • Scăpăm de semnificația specială a unei expresii încadrând-o între " (ghilimele).

Căutarea unui fișier în sistem

Utilitarul locate

Folosim utilitarul locate pentru a căuta un fișier în întreg sistemul de fișiere.

Are avantajul că este foarte rapid, deoarece folosește o bază de date pentru a indexa fișierele.

Are două dezavantaje:

  1. Baza de date trebuie reconstruită periodic. Dacă vrem să reconstruim manual baza de date, avem nevoie de drepturi privilegiate pentru a rula comanda updatedb.

  2. Utilitarul caută în tot sistemul de fișiere: nu putem să specificăm un punct de start pentru căutare. Este necesar să filtrăm rezultatul căutării cu punctul de start dorit, așa cum am făcut în exemplul de mai sus: | grep workspace/C.

Utilitarul find

Folosim find pentru a căuta după criterii mai complexe decât numele fișierului, cum ar fi tipul fișierului, data ultimei modificări, etc.

De cele mai multe ori vom folosi find în conjuncție cu opțiunea -exec pentru a rula o comandă asupra fișierelor găsite.

Utilitarul find este mai lent decât locate, dar nu necesită o bază de date care trebuie actualizată periodic. locate este probabil suficient pentru majoritatea cazurilor când suntem interesați de căutarea unui fișier. j

Sumar: Inspectarea fișierelor

Inspectarea rapida a conținutului fișierelor

Pentru a vedea rapid conținutul unui fișier folosim utlitarul less. În cadrul unei sesiuni less putem folosi aceeleași taste ca în cadrul sesiunii interactive man pentru navigarea în pagină:

  • Ctrl+n/Ctrl+p sau j/k pentru a naviga, cu câte o linie, în jos, respectiv în sus; recomandăm utlizarea tastelor j/k pentru a fi mai eficienți
  • Ctrl+f/Ctrl+b pentru a naviga, cu câte o pagină de terminal, în jos, respectiv în sus
  • Search (/, ?, n, N)
  • Go up (g), go down (G)
  • Help (h) pentru a afla mai multe despre cum putem folosi mai bine sesiunea interactivă
  • Quit (q) pentru a ieși din sesiunea interactivă

Pentru a afișa pe ecran conținutul unui fișier folosim utlitarul cat.

Pentru a afișa parțial conținutul unui fișier sau a extrage rezultatul unei comenzi folosim utilitarele head și tail.

Căutarea în fișiere

Folosim comanda grep pentru a căuta un pattern într-un fișier sau în rezultatul unei comenzi (cum ar fi cat). Modul de folosire este grep PATTERN cale/către/fișier sau cmd | grep PATTERN.

Opțiuni uzuale ale grep

Folosim opțiunea -n pentru a afișa și numărul liniei care conține patternul căutat.

Implicit, grep caută în mod case-sensitive patternul. Folosim opțiunea -i pentru a căuta patternul în mod case-insensitive.

Pentru a afișa toate liniile, mai puțin pe cele care conțin pattern, folosim opțiunea -v.

Pentru a efectua o căutare recursivă folosim opțiunea -r.

De cele mai multe ori vom folosi opțiunile -n, -i și -r în aceelași timp. Astfel avem o căutare cât mai cuprinzătoare și putem folosi funcția de căutare în sesiunea interactivă less pentru a găsi linia și fișierul care ne interesează.

Extra: Dacă dorim să căutăm cuvântul search folosim opțiunea -w (word) pentru a trata patternul ca un cuvânt, ca în exemplul următor: grep -nri -w "search" workspace/C/ | less.

Compararea fișierelor

Comparăm două fișiere, octet cu octet, folosind utilitarul cmp.

Comparăm textual două fișiere folosind utilitarul diff.

Sumar: O înțelegere mai bună a shellului

Configurarea shellului bash

Fișierul de configurare al shellului BASH este ~/.bashrc. Atunci când un utilizator pornește un shell bash, conținutul fișierului ~/.bashrc este citit și sunt aplicate configurările specifice utilizatorului.

Valorile variabilelor HISTSIZE și HISTFILESIZE limitează numărul maxim de comenzi, respectiv linii, din fișierul ~/.bash_history.

Un alias este un nume (placeholder) care înlocuiește un șir de caractere. Pentru o organizare mai bună, este recomandat ca utilizatorul să-și definească aliasurile în fișierul ~/.bash_aliases.

Execuția comenzilor

Înlănțuirea comenzilor

Atunci când își încheie execuția, orice proces întoarce un cod de eroare, care este un număr: valoarea 0 semnifică că acesta și-a încheiat execuția cu succes, iar orice ală valoare indică o eroare.

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

  • Operatorul ; - este folosit pentru separarea comenzilor, indiferent de cum s-au executat acestea.
  • Operatorul binar && (și logic) - execută a doua comandă doar dacă precedenta s-a executat cu succes.
  • Operatorul binar || (sau logic) - execută a doua comandă doar dacă prima s-a terminat cu eșec.

Atunci când folosim operatorul | preluăm rezultatul comenzii din stânga operatorului și îl oferim ca intrare comenzii aflate în dreapta operatorului. Operatorul | ne permite să prelucrăm datele de interes, trecându-le prin mai multe utilitare, fiecare cu un scop bine definit.

Redirectări

Folosim operatorul > pentru a redirecta STDOUT sau STDERR într-un fișier. Pentru a redirecta ieșirea standard folosim sintaxa cmd > nume-fișier. Pentru a redirecta ieșirea standard a erorilor folosim sintaxa cmd 2> nume-fișier.

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

Folosim sintaxa &> cale/către/nume-fișier pentru a redirecta atât STDERR, cât și STDOUT, în fișierul primit ca argument.

Filtre de text

Înainte de laborator

Comandă Descriere scurtă
head comandă folosită să afișăm primele linii dintr-un output
tail comandă folosită să afișăm ultimele linii dintr-un output
find comandă cu care putem găsi un fișier într-o ierarhie de directoare
tr filtru de text, mai multe detalii aici
cut comandă utilă pentru a separa secțiuni ale unui output, pentru detalii aici
sort sortează un output
grep caută apariția unui cuvânt/expresii regulate într-un text
uniq elimină duplicatele unui output
wc utilitar care numără liniile, caracterele, cuvintele
touch creează un fișier
file inspectează tipul unui fișier

Fișiere speciale

  • Fișierul /dev/null este un fișier care ignoră orice este scris în el.
  • Fișierul /dev/zero este un generator de octeți. Acesta generează atâția octeți cu valoarea zero (0) cât îi sunt ceruți.
  • Fișierul /dev/urandom este un alt generator de octeți. Acesta generează atâția octeți cu valoare random cât îi sunt ceruți.

Cuprins

1) Putem să ne găsim în situația în care ecranul terminalului nostru este plin cu rezultatele comenzilor rulate anterior sau cu opțiuni afișate de către auto-complete. Putem să curățăm ecranul folosind comanda clear. O alternativă mai rapidă este să folosim combinația de taste Ctrl+l. Aceasta va produce același rezultat (va curăța ecranul) și are avantajul că poate fi folosită în timp ce scriem deja o comandă.
2) Căutarea este case-sensitive. Putem să schimbăm acest comportament prin introducerea opțiunii -I în sesiunea interactivă, înainte de a porni căutarea. Dacă doriți să aflați mai multe despre opțiunile pe care le putem introduce apăsați tasta h într-o sesiune interactivă și căutați textul “OPTIONS”.
3) Putem folosi tasta ? pentru a porni o căutare de la poziția curentă către începutul paginii. Alternativ, putem naviga la începutul paginii prin apăsarea unei singure taste (g) și apoi pornim căutarea / de acolo.
4) Folosim forma A-Za-z pentru a preciza orice caracter din alfabetul englez, indiferent dacă este majusculă sau nu. Nu putem folosi forma A-z datorită reprezentării caracterelor în tabelul ASCII. Caracterele A-Z sunt reprezentate în intervalul 65-90, iar caracterele a-z în intervalul 97-122 în tabelul ascii. Dacă am folosi forma A-z, i-am indica expresiei globbing să includă și caracterele din intervalul 91-96 din tabelul ascii în expresia noastră.
5)

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

uso/laboratoare/laborator-05.txt · Last modified: 2020/11/10 21:26 by liza_elena.babu
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