Laborator 06 - Interfața în linia de comandă

Suport laborator

Demo

Redirectare

Redirectare stdout (standard output, în general mesaje afișate prin comenzi similare cu printf din C). De exemplu, într-un sistem în care avem multe procese, e greu sa urmărim direct în consolă output-ul comenzii ps:

student@midgard:~$ ps -f -u student > stdout.txt 
student@midgard:~$ cat stdout.txt

Putem redirecta doar mesajele de eroare către un fișier. Scenariu: Vrem să găsim toate fișierele cu extensia *.conf din directorul /etc.

student@midgard:~$ find /etc -name '*.conf'
(...)
find: `/etc/ppp/peers': Permission denied
(...)

Ne deranjează mesajele de eroare din cauza lipsei drepturilor de citire asupra unor fișiere sau directoare. Pentru aceasta vom folosi:

student@midgard:~$ find /etc -name '*.conf' 2> errors.txt
student@midgard:~$ cat errors.txt

Putem face combinații acum: redirectăm erorile într-un fișier și rezultatele în alt fișier:

student@midgard:~$ find /etc -name '*.conf' 2> errors.txt > stdout.txt

În cazul în care dorim să redirecționăm toate mesajele afișate de o comandă, la grămadă, folosim operatorul &>:

student@midgard:~$ find /etc -name '*.conf' &> find_output.txt

Reamintiți-vă de la procese procfs, montat în /proc. Pentru fiecare proces identificat printr-un PID, aveam directorul /proc/19827/fd, care conținea link-uri simbolice către fișierele deschise de acel proces. Pentru fiecare proces în acel director găseam 0, 1, 2. Acestea reprezintă respectiv:

  • 0 - intrarea standard (stdin). Exemplu: tastatura
  • 1 - ieșirea standard (stdout). Exemplu: mesaje afișate cu printf de un program C
  • 2 - ieșirea standard de eroare (stderr), unde se duc mesaje precum cele afișate de find mai sus.

Putem redirecta outputul și către device-uri virtuale, cum ar fi /dev/null. În general facem acest lucru deoarece vrem să executăm o anumită comandă (într-un program sau într-un script), fără a ne interesa outputul ei. Pentru exemplul cu find de mai sus, să zicem că dorim să ignorăm erorile:

student@midgard:~$ find /etc -name '*.conf' 2> /dev/null 

Înlănțuirea comenzilor

Este o metodă foarte bună prin care executăm mai multe comenzi simultan, în funcție, eventual, de anumite condiții.

  1. Înlănțuire simplă: ;: execută comenzile una după alta, indiferent de rezultatul comenzii anterioare:
     root@asgard:~# apt-get update ; apt-get upgrade ; apt-get dist-upgrade 

    În exemplul de mai sus am executat un update complet (de surse de unde sunt luate pachetele și de pachete) al unui sistem Ubuntu.

  2. Dacă vrem să înlănțuim două comenzi, dar s-o executăm pe a doua doar dacă prima s-a terminat cu succes, vom folosi operatorul &&:
     student@midgard:~$ grep www-data /etc/passwd > /dev/null && echo "Avem userul www-data" 
  3. Dacă vrem să înlănțuim două comenzi, dar s-o executăm pe a doua doar dacă prima s-a terminat cu eșec, vom folosi operatorul ||:
     student@midgard:~$ grep costel /etc/passwd > /dev/null || echo "Nu avem userul costel" 

În general, pentru a vedea ce rezultat a avut o comandă, inspectăm variabila $?:

student@midgard:~$ ls -l ~ > out.txt
student@midgard:~$ echo $?
0
student@midgard:~$ cat /etc/sudoers
student@midgard:~$ echo $?
1

Observăm că o comandă care s-a încheiat cu success returnează 0.

Atenție! Toți operatorii de redirectare din acest demo 2>, &>, > trunchiază conținutul fișierului, dacă acesta exista. Ca alternativă aveam append mode, care se poate aplica și aici, fără probleme, astfel: 2>>, &>>, >>.

Comunicarea interprocese, folosind pipe

Care e diferența dintre cele două comenzi de mai jos? Câte procese se execută pentru fiecare dintre cele două comenzi?

student@midgard:~$ cat /etc/passwd | cut -d':' -f1
student@midgard:~$ cut -d':' -f1 < /etc/passwd

Dorim să inspectăm jurnalul (log-ul) pachetelor instalate sau șterse în sistemul nostru Ubuntu. Dar ne interesează doar ultimele 40 de acțiuni. Pentru a putea obține și un output în care putem căuta și filtra text ca în pagina de manual vom folosi less:

student@midgard:~$ tail -n 40 /var/log/bootstrap.log | less

Dacă vrem să monitorizăm fișierel de tip log în timp real, folosim comanda

tail -f /path/to/logfile 

unde /path/to/logfile este calea către fișierul pe care vrem să-l monitorizăm în timp real.

Similar putem cere primele 40 de acțiuni jurnalizate în acest fișier:

student@midgard:~$ head -n 40 /var/log/bootstrap.log | less

Scenariu: suntem administratorii unui server. Dorim să folosim un one liner pentru a afișa ultimii 30 de utilizatori (doar username-ul lor, plus trebuie evitate duplicatele, ne interesează 30 de persoane) care s-au autentificat în sistemul nostru, să îi sortăm alfabetic:

root@testingsrv:~$ last -30 | cut -d ' ' -f1 | sort | uniq

Totuși există o problemă cu comanda de mai sus: last afișează pe ultima linie un mesaj de forma

wtmp begins Mon Nov  3 19:24:55 2014

Ne deranjează ultimele două linii (un spațiu gol și mesajul descris mai sus) și deci vrem să le eliminăm:

root@testingsrv:~$ last -30 | head -n -2 | cut -d ' ' -f1 | sort | uniq

Variabile în terminalul Bash

student@midgard:~$ MY_VAR="value"
student@midgard:~$ a=1
student@midgard:~$ echo $a
student@midgard:~$ echo $MY_VAR

Într-o variabilă putem salva și output-ul unei comenzi pentru a îl transmite ca parametru altei comenzi (similar cu un pipe). Spre exemplu, dorim să ștergem toate fișierele cu extensia *.tmp din directorul curent:

student@midgard:~$ rm -f $(find . -name '*.tmp')

Variabile speciale în terminalul Bash

Variabila Semnificație
$$ PID-ul procesului curent
$! PID-ul ultimului proces lansat in background
$? Valoarea de exit a ultimei comenzi
$_ Ultimul argument al ultimei comenzi (similar cu ALT+.

Putem folosi comanda echo și să îi transmitem parametru variabilele respective, pentru a vedea rezultatul lor.

Variabile de mediu

Variabilele de mediu sunt un set de variabile care afectează modul în care un program rulează. În general, un proces moștenește variabilele de mediu ale procesului părinte. Astfel, pentru procesul asociat rulării unei comenzi, sunt moștenite variabilele shell-ului.

Pentru a vedea variabliele de mediu definite, putem folosi comanda env fără nici un parametru.

student@uso:~$ env

Pentru a defini o variabilă de mediu folosim:

student@uso:~$ export VARIABLE=value

Dacă nu folosim export variablia nu va fi moștenită de procesele copii.

Variabliele de mediu se scriu în mod obișnuit cu litere mari. Exemple de variabile de mediu:

  • PATH - o listă de directoare folosite de shell pentru a găsi executabilul asociat comenzii. Directoarele sunt separate prin :.
  • HOME - directorul home al utilizatorului curent.
  • USER - utilizatorul curent
  • OLDPWD - calea directorului anterior
  • EDITOR - editorul implicit.

Globbing

  1. Dorim să afișăm toate fișierele cu extensia .conf din /etc, care încep cu a, b, c, d sau e:
     student@midgard:~$ ls -l /etc/[a-e]*.conf
  2. Dorim să afișăm fișierele (sau directoarele) cu extensiile .conf sau .d din /etc:
     student@midgard:~$ ls -ld /etc/*{.conf,.d}

    De ce am folosit parametrul -d la ls?

  3. Dorim să afișăm fișierele (sau directoarele) cu extensiile .conf sau .d din /etc care nu încep cu a, b, c:
     student@midgard:~$ ls -ld /etc/[^abc]*{.conf,.d}

Shell scripting

În loc să scriem one-linere putem automatiza execuția mai multor într-un fișier, numit script, care va fi interpretat de Bash linie cu linie. În general shell script-urile sunt fișiere care au extensia .sh și pe prima linie au așa-numitul shebang, o linie care începe cu #! prin care instruim terminalul ce interpretor sa folosească. Iată un exemplu simplu:

hello.sh
#!/bin/bash
 
echo "Hello, World!"

Preuspunând că fișierul hello.sh e salvat în /home/student acesta se execută ca un binar generat în urma compilării unui program C. Este foarte important de observat că trebuie să avem drepturi de execuție asupra lui:

student@midgard:~$ cd ~; ls -l hello.sh
-rw-rw-r-- 1 student student 34 nov  8 21:44 hello.sh
student@midgard:~$ ./hello.sh
bash: ./hello.sh: Permission denied
 
student@midgard:~$ chmod +x hello.sh; ls -l hello.sh
-rwxrwxr-x 1 student student 34 nov  8 21:44 hello.sh
student@midgard:~$ ./hello.sh
Hello, World!

Înlănțuirea de comenzi de upgrade poate fi transpusă într-un script, astfel:

upgrade_ubuntu.sh
#!/bin/bash
 
# This is a comment
echo "Upgrading Ubuntu!"
 
# Please run this script as root
apt-get update
apt-get upgrade
apt-get dist-upgrade
 
# Make some cleanup
echo "Cleaning dependencies not needed anymore"
apt-get autoremove
 
echo "Cleaning retrieved package files"
apt-get autoclean

La shebang în loc să specificăm calea absolută către interpretor, am putea interoga variabila de mediu, pentru a avea scriptul portabil (e posibil ca pe alte stații interpretorul Bash să nu fie localizat în /bin/bash. Să urmărim prima linie din exemplul de mai jos:

hello.sh
#!/usr/bin/env bash
 
echo "Hello, World!"

Exerciții

Pe parcursul exercițiilor, rulați părțile din exerciții pas cu pas. Scrieți o comandă, verificați că funcționează. Adăugați apoi o prelucrare cu operatorul | (pipe) și verificați că funcționează. Și tot așa, din aproape în aproape.

[1] Cine pe cine include (3p)

Dorim să afișăm fișierele din ierarhia /usr/include care includ header-ul features.h, împreună cu linia unde acest antet apare. Vom folosi comanda grep și opțiunile acesteia și vom construi un one liner.

Pentru început, folosiți grep pentru a căuta recursiv în ierarhia /usr/include fișierele care conțin șirul features.h. Opțiunea -r (de la recursive) este cea care permite căutarea recursivă într-o ierarhie.

Completați opțiunile comenzii grep de mai sus cu opțiunea care permitea afișarea numărului liniei din cadrul fiecărui fișier unde a fost găsit match-ul. Pentru a afla opțiunea grep care afișează numărul liniei pe care a fost găsit șirul, căutați în pagina de manual a comenzii grep șirul line-number.

În output-ul obținut mai sus sunt afișate trei elemente separate de caracterul două puncte (:, colon): numele fișierului, numărul liniei pe care a fost făcut match și conținutul liniei. Afisați doar numele fișierului și numărul liniei pe care a fost făcut match.

Folosiți comanda cut și extrageți doar coloanele de interes: coloana 1 și coloana 2.

[2] Mesaj de întâmpinare (3p)

Dorim să afișăm un mesaj de întâmpinare custom pentru momentul în care un utilizator deschide un shell. Mesajul vrem să fie

Welcome, <nume-utilizator>, to Pearson-Hardman-Specter. <nume-utilizator> just got Litt-up!

unde <nume-utilizator> este username-ul.

Pentru aceasta va trebui să editați, ca root (adică folosind sudo), fișierul /etc/bash.bashrc și să scrieți la sfârșitul fișierului comanda dorită. Comanda va fi rulată la orice deschidere de shell nou. Folosiți comanda sudo vim /etc/bash.bashrc pentru a edita ca root fișierul /etc/bash.bashrc.

Fișierul /etc/bash.bashrc este un fișier global de configurare a shell-ului. Fișierul este parcurs la fiecare deschidere de shell.

Pentru început afișați șirul Welcome la fiecare deschidere de shell. Adică scrieți la sfârșitul fișierului /etc/bash.bashrc comanda pentru afișarea șirului. Apoi deschideți un shell nou (sau un tab nou de shell pentru verificare).

Pentru a afișa un mesaj folosiți comanda echo urmată de șirul de afișat. Este recomandat ca șirul de afișat, transmis ca argument comenzii echo să fie pus între ghilimele (", quotes).

Pentru afișarea șirului dorit, care conține username-ul, folosiți variabila USER, variabilă deja definită în shell. Variabila USER conține username-ul. Pentru a afișa valoarea unei variabile folosim construcția de forma $USER. De exemplu, pentru a afișa numele de utilizator și un mesaj de salut folosim comanda

echo "Salut, $USER"

[3] Căutare de fișiere (2p)

De multe ori dorim să căutăm într-o ierarhie de fișiere și directoare, dar după anumite criterii. Comanda cea mai potrivită în acest caz este find. Folosiți find pentru a afișa toate fișierele din ierarhia /etc/ (adică toate fișierele din /etc/, din subdirectoarele acestuia, din subdirectoarele subdirectoarelor etc.). Hint: Folosiți opțiunea -type a comenzii find.

Extindeți comanda anterioară pentru a afișa toate fișierele din ierarhia /etc/ al căror nume începe cu litere între a și d. Hint: Folosiți opțiunea -name a comenzii find și construcția [a-d]* pentru a indica un șir care începe cu o literă între a și d urmată de orice altceva.

Extindeți comanda anterioară pentru a afișa toate fișierele din ierahia /etc/ care au permisiunile de forma rw-------, adică 600. Hint: Folosiți opțiunea -perm urmată de permisiunile căutate în format octal.

Pentru comanda finală, redirectați outputul în fișierul ~/file_list.txt și erorile în ~/errors.txt.

[4] Lucrul cu arhive tar (2p)

Luați exercițiul pe bucăți, faceți o parte apoi treceți la următoarea.

Descărcați arhiva de aici folosind una dintre comenzile wget sau curl.

Atât wget cât și curl primesc ca parametru un URL (link). URL-ul îl puteți obține folosind, în browser, click dreapta și selectând opțiunea Copy link location. Pentru a da paste în terminal folosiți click dreapta și selectând Paste, sau, mai direct, combinația de taste Shift+Insert. Dacă alegeți să folosiți curl, fiți atenți la faptul că, implicit, acesta afișează conținutul fișierului pe ecran, deci va trebui să-l redirectați voi într-un fișier practic.tar.gz.

  • Fără a o dezarhiva, listați conținutul arhivei practic.tar.gz. (Hint: Folosiți opțiunea -t a comenzii tar).
  • Dezarhivați arhiva practic.tar.gz. (Hint: Arhiva este în format .tar.gz. Pentru a afla comanda de dezarhivare căutați pe Google șirul unpack .tar.gz. După dezarhivare directorul obținut este practic/.)

[Bonus 0] Globbing (2 Karma)

Listați, folosind globbing, toate imaginile cu extensiile bmp, jpg, png, gif din directorul rezultat în urma dezarhivării.

Parcurgeți secțiunea Globbing din cadrul laboratorului.

Pentru a lista toate fișierele cu extensiile bmp, jpg, png și gif folosiți operatorii de globbing * (star) si {opt1,opt2,opt3} (acolade). Un exemplu simplu de utilizare (porniți de la acesta) este

ls practic/*/*.png

În comanda de mai sus listăm toate fișierele cu extensia png din toate subdirectoarele directorului practic.

Redirectați outputul într-un fișier numit images.dat.

Pentru redirectare într-un fișier folosiți operatorul >.

Ștergeți directorul dezarhivat folosind comanda

rm -fr practic/

[Bonus #1] Lucrul cu arhive tar - continuare (1 karma WoUSO)

Extrageți din arhiva practic.tar.gz doar fișierele listate în fișierul images.dat.

Pentru a afla opțiunile tar de extragere doar a anumitor fișiere dintr-o arhivă, căutați pe Google șirul tar extract specific files.

În cadrul comenzii porniți de la exemplul de mai jos care listează în format lung fișierele indicate în fișierul images.dat:

ls -l $(cat images.dat)

Comanda va da eroare pentru că directorul practic/ nu mai există, dar sintaxa este corectă și o puteți adapta pentru comanda tar. Adică să aveți o comandă de forma

tar ... $(cat images.dat)

Pentru verificarea dezarhivării doar a fișierelor de tip imagine folosiți comanda

tree practic/

[Bonus #2] Lucrul cu arhive tar - continuare (1 karma WoUSO)

Creați un script numit extract-images-only.sh care să conțină comenzile folosite la cele 3 exerciții precedente. Acordați permisiuni de execuție și rulați scriptul. Scriptul va trebui să:

  • dezarhiveze arhiva practic.tar.gz
  • listeze toate fișierele cu una dintre extensiile png, jpg, bmp sau gif în fișierul images.dat. Fișierul images.dat va trebui suprascris de fiecare dată.
  • șteargă directorul practic
  • dezarhiveze din arhiva practic.tar.gz doar fișierele aflate în fișierul images.dat
  • listeze conținutul directorului practic pe ecran, folosind comanda tree.

Parcurgeți secțiunea Shell Scripting din cadrul laboratorului.

Scriptul trebuie să înceapă cu shebang adică șirul #!/bin/bash.

Pentru acordarea permisiunilor de execuție folosiți comanda chmod.

[Bonus #3] Folosirea shell operators (1 karma WoUSO)

Uneori putem testa rezultatul execuției unei comenzi folosind una dintre construcțiile următoare:

student@uso:~$ comanda && echo "SUCCESS"

sau

student@uso:~$ comanda || echo "FAIL"

Pe baza conținutului celor două fișiere create la Exercițiul 3, file_list.txt și errors.txt, scrieți un one liner care afișează pe ecran textul SUCCESS, dacă nu s-a produs nicio eroare la folosirea comenzii find (fișierul errors.txt este gol) sau FAILED WITH <num_erori> ERRORS, unde <num_erori> este numărul de linii (erori) din fișierul errors.txt.

Considerăm că orice linie din fișierul errors.txt reprezintă o eroare separată. Pentru a calcula numărul de linii dintr-un fișier a.txt putem folosi comanda

student@uso:~$ wc -l < a.txt

Pentru a verifica dacă fișierul errors.txt are conținut (are dimensiunea mai mare ca 0), folosiți comanda

test -s errors.txt

Această comandă se întoarce cu succes dacă fișierul are conținut (are dimensiunea mai mare ca 0). Adică puteți să folosiți în continuarea comenzii de mai sus operatorii && și || pentru a afișa un mesaj de succes sau de insucces.

Ca să expandați numărul de linii din cadrul unui fișier folosiți o construcție de forma $(wc -l < a.txt).

[Bonus #4] User & Password generator (2 karma WoUSO)

Unui script îi putem da și argumente în linia de comandă. Urmăriți scriptul exemplu de mai jos:

args.sh
#!/bin/bash
 
echo "Numarul argumentelor este $#"
echo "Numele scriptului este $0"
echo "Primele 2 argumente sunt: $1 \\ $2"
student@midgard:~$ chmod +x args.sh
student@midgard:~$ ./args.sh 1 2 3 4 5
Numarul argumentelor este 5
Numele scriptului este ./args.sh
Primele 2 argumente sunt: 1 \ 2

Realizați un script numit simple_adduser.sh care adaugă în sistem utilizatorul U, îi configurează homedir-ul la /home/U și îi generează o parolă de N caractere, unde N este dat ca argument scriptului.

Scriptul va fi chemat astfel: ./simple_adduser.sh username 20

La final trebuie să afișați un string de forma: Utilizator:parola_generata

Indicații: Pentru adăugarea neinteractivă a utilizatorilor în sistem, folosiți comanda useradd

Parola generată o salvați într-o variabilă. Pentru un utilizator existent în sistem, putem configura o parolă neinteractiv folosind chpasswd astfel:

echo "user:newpassword" | chpasswd

[Bonus #5] Sleeping processes (1 karma WoUSO)

Folosiți un one-liner pentru a determina PID-ul și comanda tuturor proceselor din sistem aflate în starea sleeping. Indicație: man ps, /user-defined format

Folosiți un one-liner pentru a determina câte procese sunt în starea sleeping. Indicație: folosiți comanda utilizată anterior și dați outputul ca parametru (prin pipe sau ca variabilă) lui wc (word count) cu un anumit parametru.

[Bonus #6] Variabila $PATH (1 karma WoUSO)

Creați directorul /home/student/executables/. Plasați scriptul extract-images-only.sh, creat la Exercițiul 8 în acest director și redenumiți-l în extract-images-only (fără .sh). Configurați sistemul astfel încât să putem executa scriptul direct, fără a folosi calea către acesta (așa cum executăm ls, de exemplu). La final veți putea rula scriptul doar cu ajutorul comenzii

extract-images-only

Va trebui să actualizați valoarea variabilei de mediu PATH, variabilă ce conține căile către directoarele ce conțin executabile/scripturi care sunt rulate direct. Adică va trebui să adăugați în cadrul variabilei PATH directorul /home/student/executables/.

Pentru documentare despre cum puteți actualiza valoarea variabilei de mediu PATH urmăriți acest link.

[Bonus #7] Log inspection (3 karma WoUSO)

În fișierul /var/log/syslog se găsesc jurnalizate mai multe mesaje printre care și mesajele afișate de clientul de DHCP, numit dhclient. Acest fișier îl afișați folosind less.

Evitați să deschideți fișiere de jurnalizare folosind vim, din cauza faptului că logul poate fi foarte mare și vă poate bloca pentru secunde bune terminalul. Întotdeauna, inspectați logurile cu less, tail, grep | less etc.

După ce ați observat formatul datelor din fișierul /var/log/syslog, scrieți un one-liner care afișează pe ecran adresele IP care au fost furnizate de clientul de DHCP (adică acele mesaje care conțin șirul bound to).

uso-ac/laboratoare/laborator-06.txt · Last modified: 2017/11/08 08:38 by giorgiana.vlasceanu
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