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
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: tastatura1
- ieșirea standard (stdout). Exemplu: mesaje afișate cu printf
de un program C2
- 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
Este o metodă foarte bună prin care executăm mai multe comenzi simultan, în funcție, eventual, de anumite condiții.
;
: 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.
&&
: student@midgard:~$ grep www-data /etc/passwd > /dev/null && echo "Avem userul www-data"
||
: student@midgard:~$ grep costel /etc/passwd > /dev/null || echo "Nu avem userul costel"
$?
:
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
.
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>>
, &>>
, >>
.
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
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
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')
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.
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
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 curentOLDPWD
- calea directorului anteriorEDITOR
- editorul implicit..conf
din /etc
, care încep cu a, b, c, d
sau e
: student@midgard:~$ ls -l /etc/[a-e]*.conf
.conf
sau .d
din /etc
: student@midgard:~$ ls -ld /etc/*{.conf,.d}
De ce am folosit parametrul -d
la ls
?
.conf
sau .d
din /etc
care nu încep cu a, b, c
: student@midgard:~$ ls -ld /etc/[^abc]*{.conf,.d}
Î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:
#!/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:
#!/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
/bin/bash
. Să urmărim prima linie din exemplul de mai jos:
#!/usr/bin/env bash echo "Hello, World!"
|
(pipe) și verificați că funcționează. Și tot așa, din aproape în aproape.
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.
cut
și extrageți doar coloanele de interes: coloana 1 și coloana 2.
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
.
/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).
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"
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
.
Descărcați arhiva de aici folosind una dintre comenzile wget
sau curl
.
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
.
practic.tar.gz
. (Hint: Folosiți opțiunea -t
a comenzii tar
).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/
.)
Listați, folosind globbing, toate imaginile cu extensiile bmp, jpg, png, gif
din directorul rezultat în urma dezarhivării.
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
.
>
.
Ștergeți directorul dezarhivat folosind comanda
rm -fr practic/
Extrageți din arhiva practic.tar.gz
doar fișierele listate în fișierul images.dat
.
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/
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ă:
practic.tar.gz
practic
practic.tar.gz
doar fișierele aflate în fișierul images.dat
practic
pe ecran, folosind comanda tree
.
Scriptul trebuie să înceapă cu shebang adică șirul #!/bin/bash
.
Pentru acordarea permisiunilor de execuție folosiți comanda chmod
.
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
.
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
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)
.
Unui script îi putem da și argumente în linia de comandă. Urmăriți scriptul exemplu de mai jos:
#!/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
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
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.
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
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.
Î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
.
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
).