student@uso:~$ cd /home/student/uso-lab student@uso:~$ git stash student@uso:~$ git pull student@uso:~$ git stash pop Updating d192801..bb8cbe7 Fast-forward labs/04-appdev/support/simple-make/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
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.
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
.
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:
cd
, folosind combinația de taste Ctrl+r
Ctrl+o
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
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:
h
pentru a acesa meniul de ajutorq
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:
/
în sesiunea interactivă din man
.Enter
. Vom fi duși la primul rezultat care se potrivește căutării, dacă acesta există.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).
~/uso-lab/labs/05-cli/support/support-globbing
.
Întotdeauna când deschidem un terminal o facem pentru că vrem să realizăm o sarcină: de exemplu, 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:~/.../05-cli/support/support-globbing$ 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
.
Î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.
Î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” shell-ului că al patrulea caracter poate să fie oricare din lista [ijk]
. În acest context, folosind globbing s-a găsit cu succes numele fișierului Music. În expresia Mus[abc]c
, i-am “spus” shell-ului 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:~/.../05-cli/support/support-globbing$ 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.
Î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}*
.
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.
Pentru exercițiile următoare vom folosi fișierele din directorul de suport ~/uso-lab/labs/05-cli/support/support-globbing
.
pdfs
. Mutați toate fișierele cu extensia .pdf
din directorul ~/uso-lab/labs/05-cli/support/support-globbing
în directorul pdfs
.Excursie Brasov, 2020-2021
. Mutați fișierele DCIM din intervalul 1400 - 1700 în directorul creat.cursuri/anul-I
. Mutați toate fișierele care conțin cuvintele curs sau slide în directorul creat. Folosiți sintaxa *{curs,slide}*
.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
. Ele au fost prezentate și în laboratorul 03: Lucrul cu fișiere (2)
. În continuare vedem câteva exemple de folosire ale utilitarului 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.
find
căutați fișierele care conțin șirul tmp
în nume. Hint: [starting point] este /
.find
căutați fișierele care conțin șirul bin
în nume. Hint: [starting point] este /
.Note de subsol
În secțiunea anterioară, 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:
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 ~/uso-lab/labs/05-cli/support/make-folder/hangman.c #include <stdio.h> #include <string.h> int main() { // Get word to guess char answer[128]; printf("Enter word to guess: "); [...]
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"
.
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ținuttail
- 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:
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
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 uso-lab/labs/05-cli/support/make-folder/hangman.c | grep gameover int gameover = 0; while (! gameover) { gameover = 1; gameover = 0;
În exemplul de mai sus, operatorul |
trimite textul afișat de comanda cat
către intrarea standard a comenzii grep
. Acesta comandă este un exemplu de oneliner. Vom discuta mai multe despre oneliners în secțiunea următoare.
Comanda următoare este echivalentă cu cea de mai sus:
student@uso:~$ grep gameover uso-lab/labs/05-cli/support/make-folder/hangman.c int gameover = 0; while (! gameover) { gameover = 1; gameover = 0;
Observăm modul de folosire: grep PATTERN cale/către/fișier
.
hangman.c
, pentru a vedea care sunt headerele incluse în acest fișiere (#include <...>
). Observați cât de multe rezultate irelevante ați găsit datorită faptului că am căutat doar caracterul l. Folosiți patternul “include” în încercarea de a restrânge căutarea.
Folosim opțiunea -n
pentru a afișa și numărul liniei care conține patternul căutat:
student@uso:~$ grep -n gameover uso-lab/labs/05-cli/support/make-folder/hangman.c 18: int gameover = 0; 19: while (! gameover) { 46: gameover = 1; 49: gameover = 0;
Implicit, grep caută în mod case-sensitive patternul, așa cum putem observa din exemplul de mai jos:
student@uso:~$ grep Gameover uso-lab/labs/05-cli/support/make-folder/hangman.c student@uso:~$
Pentru a efectua căutarea textului în mod case-insesnsitive, folosim opțiunea -i
, ca în exemplul de mai jos:
student@uso:~$ grep -i Gameover uso-lab/labs/05-cli/support/make-folder/hangman.c int gameover = 0; while (! gameover) { gameover = 1; gameover = 0;
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 gameover uso-lab/labs/05-cli/support/make-folder/hangman.c #include <stdio.h> #include <string.h> int main() { // Get word to guess char answer[128]; printf("Enter word to guess: "); fflush(stdout); scanf(" %s", answer); int N = strlen(answer); int mask[N]; for (int i=0; i < N; ++i) { mask[i] = 0; } [...]
Cu alte cuvinte, am afișat toate liniile din fișiere care nu conțin patternul gameover
.
Î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 gameover în fișierul uso-lab/labs/05-cli/support/make-folder/hangman.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 gameover uso-lab/ | less uso-lab/labs/05-cli/support/make-folder/hangman.c: int gameover = 0; uso-lab/labs/05-cli/support/make-folder/hangman.c: while (! gameover) { uso-lab/labs/05-cli/support/make-folder/hangman.c: gameover = 1; uso-lab/labs/05-cli/support/make-folder/hangman.c: gameover = 0; uso-lab/labs/04-appdev/support/simple-make/hangman.c: int gameover = 0; uso-lab/labs/04-appdev/support/simple-make/hangman.c: while (! gameover) { uso-lab/labs/04-appdev/support/simple-make/hangman.c: gameover = 1; uso-lab/labs/04-appdev/support/simple-make/hangman.c: gameover = 0;
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 uso-lab | less uso-lab/lectures/auto/demo/grades/script.wiki:3:Pentru acest scenariu, actualizați [[https://github.com/systems-cs-pub-ro/uso-lab|repository-ul public de USO]] și navigați în subdirectorul ''lectures/auto/grades/'': uso-lab/lectures/auto/demo/grades/script.wiki:6:student@uso:~$ cd uso-lab uso-lab/lectures/auto/demo/grades/script.wiki:7:student@uso:~/uso-lab$ git pull uso-lab/lectures/auto/demo/grades/script.wiki:8:student@uso:~/uso-lab$ cd lectures/auto/grades uso-lab/lectures/auto/demo/grades/script.wiki:9:student@uso:~/uso-lab/lectures/auto/grades$ ls uso-lab/lectures/auto/demo/grades/script.wiki:19:student@uso:~/uso-lab/lectures/auto/grades$ wc -l 2016-2017.csv uso-lab/lectures/auto/demo/grades/script.wiki:21:student@uso:~/uso-lab/lectures/auto/grades$ wc -l 2017-2018.csv uso-lab/lectures/auto/demo/grades/script.wiki:23:student@uso:~/uso-lab/lectures/auto/grades$ wc -l 2018-2019.csv uso-lab/lectures/auto/demo/grades/script.wiki:25:student@uso:~/uso-lab/lectures/auto/grades$ wc -l 2019-2020.csv uso-lab/lectures/auto/demo/grades/script.wiki:32:student@uso:~/uso-lab/lectures/auto/grades$ tail -n +2 2019-2020.csv | wc -l uso-lab/lectures/auto/demo/grades/script.wiki:34:student@uso:~/uso-lab/lectures/auto/grades$ echo -n "2019-2020: " ; tail -n +2 2019-2020.csv | wc -l uso-lab/lectures/auto/demo/grades/script.wiki:45:student@uso:~/uso-lab/lectures/auto/grades$ for i in *.csv; do echo -n "$(basename "$i" .csv): " ; tail -n +2 " :
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ă.
stdio.h
.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:
0
, procesul și-a încheiat execuția cu succes.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.
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:
apt update
apt install
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:
;
care 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.
&&
(ș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ă.
||
(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.
~/uso-lab/labs/05-cli/support/make-folder
și apoi copiați conținutul directorului ~~/uso-lab/labs/05-cli/support/redir
în el.make all
.
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:
student@uso:~$ ps -e | grep firefox 14912 pts/0 00:00:19 firefox
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
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 :
.
man sort
)wc
, obțineți numărul de utilizatori din sistem. (Hint: man wc
)|
și tail
)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. Acum, adăugăm câteva informații noi la ceea ce am învățat deja.
Putem folosi niște fișiere speciale pentru redirectarea outputulu sau erorilor sau din care să citim input. Detaliem mai jos.
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.
/dev/urandom
este un alt generator de octeți pe care-l putem folosi pentru extragerea de caractere. De exemplu, pentru a genera o parola de 30 de caractere alfanumerice.
/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 citimof
- output file - calea către fișierul unde scriembs
- block size - dimensiunea unui block citit din ifcount
- block count - numărul de block-uri cititerand-50mb
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.
Note de subsol
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ă.
-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”.
?
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.
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ă.