a) Pentru a scrie, pe două linii, titlul jocului StarCraft 2 se pot folosi două comenzi echo
:
student@uso:~$ echo "StarCraft II" student@uso:~$ echo "Wings of Liberty"
b) Comenzile anterioare se pot rescrie pe un singur rând. Operatorul ;
este folosit pentru a secvenția două comenzi.
student@uso:~$ echo "StarCraft II"; echo "Wings of Liberty"
Puteți observa cum shell-ul afișează un nou prompt doar după ce a executat ambele instrucțiuni.
;
. Orice comenzi pot fi secvențiate în acest fel, nu doar echo
. Comenzile dintr-o secvență pot fi diferite, nu trebuie să fie toate de același fel.
a) Rulați comanda de mai jos pentru a descărca și dezarhiva fișierele de suport ale laboratorului de astăzi.
Obsevați că fiecare linie se termină cu un caracter \
. Acesta va face ca linia următoarea să fie considerată ca făcând parte din comanda curentă.
student@uso:~$ wget http://ocw.cs.pub.ro/courses/_media/uso/laboratoare/lab07.tar.gz;\ echo "Am luat arhiva."; \ mkdir lab07; \ tar zxvf lab07.tar.gz; \ echo "Am dezarhivat arhiva"; \ echo "Continutul arhivei:"; \ ls -l lab07
\
) la finalul liniei semnifică faptul că linia se continuă pe rândul următor.
Promptul oferit după trecerea liniei pe rândul următor este >
.
Dacă apăsați săgeată sus pentru a relua comanda anterioară, veți vedea că toate comenzile au fost alăturate pentru a fi pe o singură linie.
a) De cele mai multe ori se întâmplă ca, după ce se creează o comandă compusă pentru un task, să trebuiască să fie rezolvat acel task încă o dată. Pentru a nu rescrie toată comanda, se poate folosi un fișier în care se salvează toate comenzile componente.
Se pot copia, într-un fișier numit sc.sh
, următoarele două linii:
echo "StarCraft II" echo "Wings of Liberty"
Câteva observații:
sh
desemenează, tradițional, un shell script. Folosirea extensiei nu este obligatorie (comparativ cu exe
, care este obligatoriu pe Windows).
b) Pentru a lansa script-ul, se poate folosi comanda de mai jos având ca director curent directorul unde este salvat fișierul sc.sh
:
student@uso:~$ bash sc.sh
Linia de mai sus pornește un nou proces bash care nu va citi comenzile de la tastatură, ci din fișierul sc.sh
. La finalul fișierului, shell-ul își va încheia execuția – putem spune că există un exit
implicit.
O altă modalitate de a rula un script este folosind source
.
student@uso:~$ source sc.sh
Alternativ, se poate folosi .
ca sinonim pentru source
– pentru a scrie mai puțin.
student@uso:~$ . sc.sh
Diferențele între cele două moduri de execuție (source
și bash
) pot fi văzute într-un exercițiu ulterior.
a) O altă modalitate de a rula un script este aceea de a îl face executabil și de a îl rula ca un executabil oarecare.
Se pot adăuga drepturi de execuție scriptului sc.sh
și apoi scriptul se poate rula cu:
student@uso:~$ ./sc.sh
chmod
.
a) Se pot scrie fișiere script și în alte limbaje, nu doar în bash. Python, Perl, Ruby sunt câteva limbaje frecvent folosite.
Pentru a putea diferenția între limbajele din cadrul scripturilor va trebui invocat interpretorul corespunzător. De exemplu, pentru un script Python va trebui să rulăm python script
.
Dacă vrem să rulăm orice script sub forma ./script
, prima linie a fișierului va avea un format special: va conține șirul #!
urmat de executabilul ce trebuie invocat pentru a rula scriptul. În acest caz, prima linie se numește linia shebang (sau sha-bang).
Pentru bash
, linia ar arăta:
#! /bin/bash
Adăugați linia shebang scriptului sc.sh
și rulați-l cu ./sc.sh
.
.
și source
, la fel nu este nici o diferență între bash script.sh
și ./script.sh
dacă script.sh
începe cu #! /bin/bash
.
Un proces își poate termina execuția cu succes (status 0) sau cu eroare (status diferit de 0). Într-un terminal statusul cu care s-a încheiat ultimul proces lansat se poate vedea cu ajutorul comenzii
echo $?
true și false sunt două programe care întotdeauna se termină cu succes, respectiv cu eroare:
student@uso:~$ true student@uso:~$ echo $? 0 student@uso:~$ false student@uso:~$ echo $? 1
student@uso:~$ true && echo "Success" Success student@uso:~$ false && echo "Success" student@uso:~$ true || echo "Fail" student@uso:~$ false || echo "Fail" Fail
bash
este, de fapt, un limbaj de programare; doar că până acum l-am folosit interactiv. Ca orice limbaj de programare, bash
are suport de variabile.
Pentru a defini o variabilă folosim sintaxa NUME_VARIABILA=VALOARE
.
=
. Este o restricție sintactică bash
.
Exemplu de utilizare a variabielor:
student@uso:~$ sc_version=2 student@uso:~$ sc_title="Wings of Liberty" student@uso:~$ echo StarCraft $sc_version StarCraft 2 student@uso:~$ echo $sc_title Wings of Liberty
$
.
Ce se întâmplă dacă vrem să folosim o variabilă nedefinită?
$ echo $undefined_var
Bash nu consideră o eroare faptul că o variabilă nu este definită. O va înlocui cu șirul vid.
Vreți să afișați un tabel cu eroii StarCraft 2. Pentru a fi mai simplu de modificat, scrieți un shell script numit heroes.sh
. Definiți, în script, câte o variabilă pentru fiecare facțiune.
#
marchează începutul unui comentariu. Comentariul se întinde până la finalul liniei.
$ cat heroes.sh terran="Terran Jim Raynor" zerg="Zerg Kerrigan" protoss="Protoss Artanis" # Acum afisam cele trei variabile echo $terran echo $zerg echo $protoss $ bash heroes.sh Terran Jim Raynor Zerg Kerrigan Protoss Artanis
Observați că spațiile în plus nu au niciun efect, drept urmare cuvintele nu sunt aliniate. Pentru a repara acest lucru trebuie să încadrați numele variabilelor între ghilimele:
$ cat heroes.sh terran="Terran Jim Raynor" zerg="Zerg Kerrigan" protoss="Protoss Artanis" # Acum afisam cele trei variabile echo "$terran" echo "$zerg" echo "$protoss" $ bash heroes.sh Terran Jim Raynor Zerg Kerrigan Protoss Artanis
echo
afișează fiecare parametru al său, separat printr-un singur spațiu. Puteți vedea în imaginea de mai jos etapele evaluării comenzii echo
în cele două cazuri.
Până acum ați învățat să rulați un script, folosind construcția bash
sau source
, în urma căreia scriptul se execută, efectuând anumite acțiuni. Dacă îl rulați încă o dată, aceleași acțiuni s-ar executa identic.
De multe ori am folosit comenzi căror le-am trimis anumiți parametrii specifici comenzii. De exemplu ps -e, sau cat FIŞIER. Asemănător putem rula scriptul nostru adăugând parametrii după numele script-ului:
student@uso:~$ ./script_bash parametru_1 parametru_2
Pentru a putea avea acces la parametrii transmiși unui script, bash ne pune la dispoziție următoarele variabile speciale:
Variabila | Semnificație | Echivalent C |
---|---|---|
$# | Numărul de parametri transmiși scriptului | argc |
$@ | Parametrii efectivi transmiși scriptului | argv |
$0 | Numele scriptului | argv[0] |
$1 | Primul argument | argv[1] |
$2 | Al doilea argument | argv[2] |
… | … | … |
Alte variabile speciale întâlnite de-a lungul laboratoarelor:
Variabila | Semnificație | Echivalent C |
---|---|---|
$$ | PID-ul procesului curent | - |
$! | PID-ul ultimului proces lansat in background | - |
$? | Valoarea de exit a ultimei comenzi | valoarea cu care se întoarce main |
Descărcați arhiva de laborator. Dezarhivați-o folosind tar.
tar xf lab07.tar.gz
Vizualizați conținutul scriptului special_vars.sh
din arhiva de laborator și apoi rulați-l astfel:
student@uso:~$ ./special_vars.sh 1 2 3 4 a
Identificați valorile parametrilor trimiși script-ului și a celorlalte variabile speciale (cele din Introducere).
Rulați următoarele comenzi.
student@uso:~$ race=Zerg student@uso:~$ bash rush.sh Rush!! student@uso:~$ source rush.sh Zerg Rush!!
Observați că în momentul folosirii source
variabilele sunt definite și în interiorul scriptului.
Rulați comenzile următoare și identificați câte procese se crează în momentul folosirii bash
și câte în cazul source
student@uso:~$ echo $$; bash special_vars.sh; echo $$ student@uso:~$ echo $$; source special_vars.sh; echo $$
Variabilele definite într-un shell bash sunt locale acelui shell. Rulând scriptul rush.sh
cu comanda bash
, se crează un alt shell bash ce execută comenzile din script.
source
nu pornește proces nou, execută comenzile din script ca și cum ar fi fost introduse de la tastatură.
.bashrc
este sourced la fiecare lansare a unui proces bash
. De aceea, e bine să salvați în el configurările proprii pentru bash
: alias-uri, definiții de variabile, valoare umask
, etc.
Un fișier similar este .vimrc
sourced de fiecare dată când se lansează o nouă instanță de vim
.
Studiați ce se întâmplă dacă încercați să listați conținutul unui director care nu există. Ce valoare de ieșire are ls
în acest caz?
student@uso:~$ ls warcraft ls: cannot access warcraft: No such file or directory student@uso:~$ echo $? 2
Scrieți un one-liner care creează directorul warcraft
dacă acesta nu există deja. Folosiți-vă de faptul că ls
va întoarce non-zero dacă directorul nu există. Ce operator veți folosi: &&
sau ||
?
student@uso:~$ ls warcraft || mkdir warcraft
Până acum știți să afișați conținutul unui fișier utilizând cat
. Uneori, dorim să vizualizăm doar începutul sau finalul fișierului sau poate dorim să vizualizăm fișierul de la început spre final. Următorul tabel listează comenzile folosite în aceste cazuri.
Comanda | Efect |
---|---|
cat | Afișează tot conținutul fișierului în ordine |
tac | Afișează tot conținutul fișierului inversat |
head | Afișează doar primele linii din fișier |
tail | Afișează doar ultimele linii din fișier |
Dacă nu primesc nici un argument, aceste comenzi citesc de la standard input ceea ce face posibilă folosirea lor prin înlănțuire cu pipe-uri.
Comenzile head
și tail
implicit afișează primele, respectiv ultimele 10 linii. Pentru a afișa un număr arbitrar de linii, folosim:
head -n k # primele k linii tail -n k # ultimele k linii # de asemenea head -n -k # toate liniile, mai putin ultimele k tail -n +k # toate liniile, mai putin primele (k-1)
Încercați să explicați ce face următoarea comandă. Dați o formulare mai simplă pentru ea.
tac special_vars.sh | head | tail -n +3 | head -n -1 | tac | tail -n 1
head
și tail
pentru a vedea cum selectați range-ul de afișat.
grep
(pentru a selecta numai anumite linii) și cut
(pentru a selecta numai anumite secțiuni de pe fiecare linie).
a) Afișați în terminal conținutul fișierului /var/log/syslog
. După cum observați, fișierul conține un număr mare de linii.
student@uso:~$ cat /var/log/syslog
b) grep
poate primi ca argumente
grep ȘABLON FIȘIER
Șablonul este reprezentat de o expresie regulată (mai multe detalii în laboratoarele viitoare).
Folosind grep
, afișați doar liniile care conțin string-ul “avahi” din fișierul /var/log/syslog
student@uso:~$ grep avahi /var/log/syslog
c) grep
poate primi liniile pe care le filtrează și pe STDIN. Este o metodă de utilizare uzuală, care se folosește de pipe-uri (|
).
Folosind cat
și grep
, afișați doar liniile care conțin string-ul “avahi” din fișierul /var/log/syslog
student@uso:~$ cat /var/log/syslog | grep avahi
a) Utilitarul cut
primește două argumente:
Șablonul este compus din două elemente: un caracter de delimitare a câmpurilor (specificat prin -d
) și id-ul câmpurilor care se afișează (specificat prin -f
).
cut -d DELIMITATOR -f LISTĂ_CÂMPURI FIȘIER
Dacă vrem ca pentru fiecare linie din fișierul /var/log/syslog
să afișăm numai ora la care a fost înregistrată acea linie, executăm următoarea comandă:
student@uso:~$ cut -d " " -f 3 /var/log/syslog
Identificați parametrii trimiși lui cut
.
b) La fel ca grep
, cut
poate primi liniile pe care le filtrează și pe STDIN.
Afișați toate liniile din fișierul /var/log/syslog
care conțin șirul de caractere kernel
. Nu uitați să folosiți cat
.
student@uso:~$ cat /var/log/syslog | grep kernel
Pentru fiecare din liniile anterioare afișați numai ora la care a fost înregistrată:
studente@uso:~$ cat /var/log/syslog | grep kernel | cut -d " " -f 3
Dorim să aflăm statistici despre un fișier cu privire la numărul de linii, cuvinte și caractere. Folosim wc
.
Aflați câte linii are fișierul special_vars.sh
.
$ wc -l special_vars.sh
Fișierul many_numbers
conține numere naturale, câte unul pe fiecare linie. Vrem să aflăm câte numere există în total, numărând fiecare număr o singură dată (altfel spus, ignorând repetițiile).
Pentru asta vom sorta numeric
fișierul cu sort
și vom elimina duplicatele de pe liniile consecutive folosind uniq
. La final, va trebui să numărăm liniile obținute.
Câte numere conține fișierul many_numbers
?
student@uso:~$ sort -n many_numbers | uniq | wc -l
Reveniți la script-ul special_vars.sh
. Găsiți o modalitate de a transmite script-ului parametrul “Wings of Liberty”
(incluzând ghilimelele). Pentru a include ghilimele într-o expresie care este deja între ghilimele, folosiți escaping: “\”Wings of Liberty\””
.
O alternativă este folosirea apostroafelor pentru a delimita șirul:
student@uso:~$ echo '"Wings of Liberty"' "Wings of Liberty" student@uso:~$ echo "\"Wings of Liberty\"" "Wings of Liberty"
Apostroafele au, însă, și un efect neașteptat. Identificați-l urmărind pașii următori:
faction
având valoarea Terran
.echo
pentru a afișa valoarea variabilei.echo
pentru a afișa valoarea variabilei, dar de data aceasta încadrați formularea $faction
cu apostroafe.$ faction=Terran $ echo $faction Terran $ echo '$faction' $faction
$
are semnnificație literală între apostroafe.
Simbolul $
este folosit pentru expandarea unei variabile, comenzi sau expresii.
În laboratorul 5 ați întâlnit următoarea comandă:
student@uso:~$ kill -9 $(pidof signal_test)
Se executa comanda pidof signal_test
, iar outputul acesteia era trimis ca parametru comenzii kill
.
Asemănător putem să atribuim outpul comenzii pidof
unei variabile, iar apoi să transmitem variabila ca parametru comenzii kill
:
student@uso:~$ pid_signal=$(pidof signal_test) student@uso:~$ kill -9 $pid_signal
Scrieți un script care, folosind fișierul /etc/passwd
:
Ca să nu executăm de două ori comanda cut
pentru a obține ultima coloană din fișierul /etc/passwd
, putem folosi o variabilă intermediară în care reținem output-ul comenzi.
Hint:
/etc/passwd
.echo
, pentru a afișa variabila, urmat de pipe și filtrele de text. Aveți grijă la outputul comenzii echo
; nu uitați de quoting (din Introducere).student@uso:~$ cat script.sh #! /bin/bash temp=$(cut -f7 -d: /etc/passwd) echo "$temp" | sort | uniq | wc -l echo "$temp" | grep $1 | wc -l
Comanda dd
are ca scop principal copierea și conversia fișierelor la nivel de octeți. Deoarece în Linux dispozitivele pot fi accesate prin fișierele din directorul /dev, putem copia partiții întregi sau doar o parte din ele cu acest utilitar.
dd if=FIȘIER_INTRARE of=FIȘIER_IEȘIRE bs=DIMENSIUNE_BLOC count=NUMĂR_BLOCURI
De obicei parametrul bs (byte size) este o putere a lui 2 (ex: 512, 1024, 2048 etc.).
Scrieți un script care construiește un fișier conținând un număr n de octeți de 0, unde n este un multiplu de 1024. Numele fișierului și numărul n scriptul le primește ca parametrii.
./script.sh NUME_FIȘIER NUMĂR_BLOCURI
/dev/zero
pentru a obține un șir cu octeți de 0.student@uso:~$ cat script.sh #! /bin/bash dd if=/dev/zero of=$1 bs=1024 count=$2
Modificați scriptul să afișeze doar numărul total de octeți. Folosiți filtrele de text învățate din laborator.
dd
afișează la ieșirea de eroare (altfel zis stderr). Redirectați ieșirea de eroare într-un fișier din directorul curent folosind operatorul 2>
dd LISTĂ_PARAMETRII 2> FIȘIER
dd
se execută cu succes pentru a aplica după filtrele de text peste ce afișează comanda. Ce operator folosim?student@uso:~$ cat script.sh #! /bin/bash dd if=/dev/zero of=$1 bs=1024 count=$2 2> ./tmp_file && tail -n 1 ./tmp_file | cut -d ' ' -f 1 rm ./tmp_file
/tmp
. De ce?
Readline este o facilitate care ajută la editarea eficientă a comenzilor pe care le scriem în shell. Vom explora câteva concepte utile.
supercalifragilisticexpialidocious
.
Alt-.
(Alt și punct)?Alt
din stânga tastaturii.mkdir
folosind reverse search. Puteți citi mai multe despre acest concept aici, secțiunea Searching the command history.rm -rf /tmp
. Nu apăsați enter. Alt-#
(Alt și diez). mkdir supercalifragilisticexpialidocious rm <Alt-.> <Ctrl-r> + mkdir rm -rf <Alt-#>