Shell Scripting
Shell Scripting
Shell scripting este o metodă prin care se creează programe sau scripturi ce sunt
executate într-un shell al sistemului de operare. Shell scripting-ul este puternic
și flexibil, permițând automatizarea sarcinilor repetitive, gestionarea sistemului,
executarea comenzi complexe și multe altele.
Scripturile shell sunt de obicei scrise pentru shell-uri de tip Unix, cum ar fi
Bourne Shell (sh), Bourne Again Shell (bash), C Shell (csh), Korn Shell (ksh), și
altele, fiecare având propria sa sintaxă și set de funcționalități. Cel mai comun
shell utilizat pentru scripting în sistemele Linux și Unix este bash.
# Salută utilizatorul
echo "Salut, $nume!"
Acest script utilizează:
Pentru a executa un script shell pe un sistem Unix sau Linux, trebuie să urmezi
câțiva pași simpli. Presupunând că ai un script salvat cu numele script.sh, iată
cum l-ai putea rula:
1. Fă Scriptul Executabil
În primul rând, trebuie să te asiguri că scriptul are permisiuni de executare.
Acest lucru se face cu comanda chmod (change mode). Deschide un terminal și
navighează la directorul unde ai salvat scriptul. Apoi, rulează:
Copy
~$ chmod +x script.sh
Această comandă modifică permisiunile fișierului script.sh pentru a permite
executarea acestuia.
2. Execută Scriptul
După ce ai acordat permisiunile necesare, există două moduri prin care poți executa
scriptul:
Copy
~$ ./script.sh
Dacă scriptul se află într-un alt director, poți utiliza calea absolută:
Copy
~$ /path/to/your/script.sh
Opțiunea 2: Folosind Interpretorul Direct
De asemenea, poți folosi shell-ul direct pentru a executa scriptul, fără a modifica
permisiunile de executare. Dacă scriptul este scris pentru Bash, folosești:
Copy
~$ bash script.sh
Acest mod de executare interpretează direct conținutul fișierului ca un script
Bash, fără a necesita permisiuni de executare pe fișier.
Note Importante
Asigură-te că prima linie a scriptului tău conține shebang-ul (#!) urmat de calea
către interpretorul adecvat. Pentru un script Bash, aceasta este de obicei
#!/bin/bash.
Comentarii pe mai multe linii: Bash nu are o sintaxă specifică pentru comentariile
pe mai multe linii similar cu alte limbaje de programare, dar poți folosi mai multe
linii de comentarii pe o singură linie sau un hack folosind : '...comment...'.
Copy
#!/bin/bash
echo "Salut!"
Copy
numar1=5
numar2=10
Alocarea valorilor variabilelor se face fără spații în jurul semnului =. Pentru a
accesa valoarea unei variabile, se utilizează semnul $ înaintea numelui variabilei.
Copy
suma=$((numar1 + numar2)) # Adună numărul1 și numărul2
Acest mecanism, $((expresie)), evaluează expresia matematică specificată în
interiorul parantezelor duble și atribuie rezultatul variabilei suma. În acest caz,
numar1 și numar2 sunt adunate, iar rezultatul lor, 15, este stocat în variabila
suma.
Copy
echo "Suma este: $suma"
Aceasta afișează valoarea variabilei suma în consolă, care în exemplul nostru este
15.
Chiar dacă Bash gestionează variabilele într-un mod flexibil, această flexibilitate
vine cu responsabilitatea de a asigura că valorile variabilelor sunt adecvate
pentru operațiile la care sunt supuse. De exemplu, încercarea de a efectua o
operație aritmetică cu un șir de caractere care nu poate fi interpretat ca un număr
va duce la o eroare.
Array-uri (Vectori)
Array-urile permit stocarea mai multor valori într-o singură variabilă, fiecare
valoare putând fi accesată printr-un index. Bash suportă array-uri unidimensionale.
Copy
nume=('Alice' 'Bob' 'Carol')
echo "${nume[0]}" # Afișează Alice
echo "${nume[@]}" # Afișează toate elementele array-ului
echo "${#nume[@]}" # Afișează numărul de elemente din array
nume+=("Dave")
echo "${nume[@]}" # Afișează toate elementele array-ului, inclusiv Dave
Lucrul cu sub-array-uri (sau porțiuni dintr-un array) se poate face utilizând
sintaxa de slicing de array-uri. Aceasta permite extragerea unei secțiuni dintr-un
array bazată pe indicii de start și stop. Sintaxa generală pentru slicing este:
Copy
${array[@]:pozitie_start:lungime}
Unde:
Exemplu de Utilizare
Să presupunem că avem următorul array:
Copy
numere=(0 1 2 3 4 5 6 7 8 9)
Dacă dorim să extragem un sub-array care începe de la indicele 3 și are lungimea 4,
vom folosi:
Copy
echo "${numere[@]:3:4}"
Acesta va afișa:
Copy
3 4 5 6
Dacă omiti parametrul lungime, se vor extrage toate elementele începând cu
pozitie_start până la sfârșitul array-ului.
Copy
declare -A capitale
capitale["Franța"]="Paris"
capitale["Germania"]="Berlin"
echo "Capitala Franței este: ${capitale["Franța"]}"
IF / THEN / ELSE
Structura if-then-else în Bash este o construcție fundamentală folosită pentru a
testa condiții și a executa diferite blocuri de cod bazate pe rezultatul testului.
Această structură permite scripturilor să ia decizii și să execute diferite acțiuni
în funcție de diferite condiții.
Sintaxa de bază
Copy
if [ condiție ]; then
# Comenzi executate dacă condiția este adevărată
else
# Comenzi executate dacă condiția este falsă
fi
if - începe testul condițional.
then - dacă condiția evaluată este adevărată, comenzile care urmează până la else
sau fi vor fi executate.
else - (opțional) specifică comenzile care vor fi executate dacă condiția este
falsă.
numar=10
filename="document.txt"
if [ -e "$filename" ]; then
echo "Fișierul există."
else
echo "Fișierul nu există."
fi
Acest script verifică dacă fișierul document.txt există în directorul curent. Dacă
fișierul există, va afișa "Fișierul există."; altfel, va afișa "Fișierul nu
există."
Copy
#!/bin/bash
nota=85
Pentru a testa mai multe condiții în cadrul aceleiași instrucțiuni if, poți folosi
operatorii logici && (și) și || (sau).
Testarea Numerică
-eq: egal cu (ex: $a -eq $b)
Testarea Fișierelor
-e: fișierul există (ex: -e fisier.txt)
-f: fișierul există și este un fișier obișnuit (nu un director sau un dispozitiv)
(ex: -f fisier.txt)
-s: fișierul există și nu este gol (are dimensiunea mai mare decât zero) (ex: -s
fisier.txt)
Operatori Logici
!: negație (inversarea stării de adevăr) (ex: ! -e fisier.txt)
-a sau &&: și logic (ex: [$a -lt 20] && [$b -gt 100])
-o sau ||: sau logic (ex: [$a -lt 20] || [$b -gt 100])
Pentru a utiliza acești operatori în scripturile tale Bash, poți folosi comanda
test sau paranteze pătrate (de exemplu, [ $a -lt $b ]). Este important să pui
spațiu după [, înainte de ], și în jurul operatorilor pentru a asigura
interpretarea corectă a expresiei de către shell.
1. FOR
Structura for repetă un set de comenzi pentru fiecare element dintr-o listă.
Exemplu:
Copy
#!/bin/bash
Exemplu 2:
Copy
#!/bin/bash
2. WHILE
Structura while execută un bloc de comenzi atât timp cât o condiție este adevărată.
Exemplu:
Copy
#!/bin/bash
contor=1
while [ $contor -le 5 ]
do
echo "Iterația $contor"
((contor++))
done
Acest script va afișa numărul iterației de cinci ori, incrementând variabila contor
la fiecare pas până când aceasta devine mai mare decât 5.
3. UNTIL
Structura until este similară cu while, dar repetă comenzi până când condiția
devine adevărată (adică face loop pe condiție falsă).
Exemplu:
Copy
#!/bin/bash
contor=1
until [ $contor -gt 5 ]
do
echo "Iterația $contor"
((contor++))
done
Acest script funcționează la fel ca exemplul while, afișând numărul iterației până
când contor depășește 5. Diferența principală este în verificarea condiției: until
execută blocul de comenzi atât timp cât condiția este falsă.
search_string="error"
for file in *.txt; do
echo "Caută '$search_string' în $file:"
grep "$search_string" "$file" && echo "Găsit în $file" || echo "Nu a fost găsit
în $file"
done
2. Numărarea Fișierelor dintr-un Director
Scriptul folosește find pentru a număra toate fișierele dintr-un anumit director.
Copy
#!/bin/bash
directory="/var/log"
num_files=$(find "$directory" -type f | wc -l)
echo "Există $num_files fișiere în directorul $directory."
3. Backup al unui Director
Acest script creează un arhivă (backup) tar.gz a unui director specificat și îl
salvează într-un alt director.
Copy
#!/bin/bash
# Creează backup
tar -czf "$destinatie_backup/$nume_arhiva" "$director_sursa"
Copy
#!/bin/bash
Copy
nume_functie() {
# comenzi
}
Sau
Copy
function nume_functie {
# comenzi
}
Exemplu: Funcție pentru Afisarea Salutului
Copy
#!/bin/bash
salut() {
echo "Salut, $1!"
}
suma() {
echo $(($1 + $2))
}
Variabila specială $@
Reprezintă un array cu toți parametrii poziționali (argumentele) transmiși
funcției, începând de la primul. Fiecare argument este tratat ca un șir de
caractere separat, ceea ce înseamnă că dacă funcția este apelată cu mai multe
argumente, $@ le va include pe toate, păstrând intactă orice spațiere sau ghilimele
utilizate.
Variabila specială $#
Într-un script Bash sau într-o funcție, $# reprezintă numărul de argumente
poziționale sau parametrii transmiși scriptului sau funcției. Această variabilă
este utilă pentru a verifica dacă scriptul sau funcția a fost apelată cu numărul
corect de argumente. De exemplu, dacă un script necesită exact doi parametri pentru
a rula corect, se poate folosi $# pentru a testa acest lucru și pentru a afișa un
mesaj de eroare sau instrucțiuni de utilizare dacă numărul de argumente nu
corespunde așteptărilor.
verifica_fisier() {
if [ -e "$1" ]; then
echo "Fișierul $1 există."
else
echo "Fișierul $1 nu există."
fi
}
verifica_fisier "/path/to/fisier"
echo "Cod de ieșire: $?"
În acest exemplu, return 0 indică faptul că fișierul există (succes), în timp ce
return 1 indică faptul că fișierul nu există (o formă de eroare sau condiție de
eșec). $? este o variabilă specială care păstrează codul de ieșire al ultimei
comenzi executate.
Variabila Specială $0
Variabila $0 reprezintă numele scriptului care este executat. În contextul unei
funcții, $0 va continua să reprezinte numele scriptului întreg, nu numele funcției.
Copy
echo "Acest script se numește $0."
Dacă acest cod este parte dintr-un script numit script.sh și executi scriptul, va
afișa:
Copy
Acest script se numește script.sh.
Indiferent unde este folosită în script, $0 va referi întotdeauna la numele
scriptului inițial executat, nu la funcțiile apelate în cadrul scriptului.
n
!
=
n
×
(
n
−
1
)
×
(
n
−
2
)
×
…
×
2
×
1
n!=n×(n−1)×(n−2)×…×2×1
0
!
=
1
0!=1 prin definiție
factorial() {
if [ $1 -le 1 ]; then
echo 1
else
prev=$(factorial $(($1 - 1)))
echo $(($1 * prev))
fi
}
# Exemplu de utilizare
read -p "Introduceți un număr pentru a calcula factorialul: " numar
if [[ $numar =~ ^[0-9]+$ ]]; then
rezultat=$(factorial $numar)
echo "Factorialul lui $numar este: $rezultat"
else
echo "Vă rugăm să introduceți un număr întreg pozitiv."
fi
Cum Funcționează Scriptul
Definirea Funcției factorial: Funcția factorial este definită pentru a calcula
factorialul unui număr. Ea primește un argument ($1), care este numărul pentru care
se calculează factorialul.
Cazul de Bază: Dacă argumentul este mai mic sau egal cu 1, funcția returnează 1,
deoarece
0
!
=
1
0!=1 și
1
!
=
1
1!=1.
Notă
Bash nu este optimizat pentru recursivitate profundă și poate fi ineficient sau
limitat pentru calculul factorialului unor numere foarte mari. Pentru astfel de
calcule, limbaje de programare precum Python sau C++ sunt mai potrivite.
Copy
str1="Hello"
str2="World"
result="$str1 $str2"
echo "$result" # Outputs: Hello World
2. Determinarea Lungimii unui Șir
Puteți obține lungimea unui șir de caractere folosind ${#string}:
Copy
str="Hello"
echo "${#str}" # Outputs: 5
3. Extragerea Subșirurilor
Extragerea unui subșir se poate face utilizând sintaxa ${string:start:length}, unde
start este indicele de început (bazat pe zero), iar length este numărul de
caractere de extras:
Copy
str="Hello World"
substring="${str:6:5}"
echo "$substring" # Outputs: World
4. Înlocuirea în Șiruri
Pentru a înlocui text într-un șir, folosiți ${string/old/new} pentru a înlocui
prima apariție sau ${string//old/new} pentru a înlocui toate aparițiile:
Copy
str="Hello World World"
modified="${str//World/There}"
echo "$modified" # Outputs: Hello There There
5. Compararea Șirurilor de Caractere
Compararea șirurilor de caractere se poate face folosind operatorii de condiție în
structuri if sau în bucle. Exemplu de verificare dacă două șiruri sunt egale:
Copy
str1="Hello"
str2="World"
if [ "$str1" = "$str2" ]; then
echo "Strings are equal."
else
echo "Strings are not equal."
fi
6. Testarea Prezenței unui Subșir
Bash oferă modalități de a testa dacă un subșir este prezent într-un șir:
Copy
str="Hello World"
if [[ "$str" == *"World"* ]]; then
echo "Substring found."
else
echo "Substring not found."
fi
7. Iterarea peste Caracterele unui Șir
Puteți itera peste fiecare caracter al unui șir folosind o buclă for:
Copy
str="Hello"
for (( i=0; i<${#str}; i++ )); do
echo "${str:$i:1}"
done
Aceste exemple acoperă operațiunile de bază cu șiruri de caractere în Bash, însă
Bash oferă și alte funcționalități avansate, precum lucrul cu expresii regulate,
care pot fi integrate în scripturi pentru manipularea și procesarea textelor
complexe.
Sed
Comanda sed (Stream Editor) este un utilitar puternic în Bash pentru manipularea
textului în fluxuri de date și fișiere. Acesta este folosit frecvent pentru
substituție, ștergere, inserare și alte operațiuni de procesare a textului. Iată
câteva exemple comune care ilustrează cum să folosești sed în Bash:
1. Substituirea Textului
Cel mai comun caz de utilizare pentru sed este substituirea textului într-un fișier
sau flux. Pentru a înlocui toate aparițiile unui text cu altul:
Copy
echo "Hello World" | sed 's/World/There/'
Acest exemplu afișează "Hello There". Pentru a modifica un fișier direct, adăugați
opțiunea -i care modifică fișierul "in-place":
Copy
sed -i 's/old-text/new-text/g' filename.txt
Opțiunea g la sfârșitul pattern-ului de substituție indică faptul că toate
aparițiile textului trebuie înlocuite, nu doar prima.
2. Ștergerea Liniilor
sed poate fi folosit pentru a șterge linii care corespund unui anumit criteriu. De
exemplu, pentru a șterge toate liniile care conțin cuvântul "delete":
Copy
sed '/delete/d' filename.txt
Pentru a șterge linia a 3-a dintr-un fișier:
Copy
sed '3d' filename.txt
3. Adăugarea de Text
Puteți adăuga text înainte sau după o anumită linie folosind sed. De exemplu,
pentru a adăuga "New Line" înainte de a 5-a linie:
Copy
sed '5iNew Line' filename.txt
Pentru a adăuga text după linia:
Copy
sed '5aNew Line' filename.txt
4. Extragerea unor Secțiuni de Text
Utilizând sed, puteți extrage anumite porțiuni de text bazate pe numărul liniei:
Copy
sed -n '1,5p' filename.txt
Acesta comandă afișează liniile de la 1 la 5 din fișierul dat. Opțiunea -n suprimă
afișarea implicită a datelor, iar p indică sed să afișeze acele linii.
Copy
echo "path/to/file" | sed 's|path/to|new/path/to|'
Aici, delimitatorul este schimbat din / în |, ceea ce face comanda mai ușor de
citit și evită necesitatea de a face "escape" pentru caracterele /.
Copy
sed '/start_pattern/,/end_pattern/s/old/new/g' filename.txt
7. Înlocuirea unei secvențe dintre două caractere utilizând sed
Această comandă caută un bloc de text care se găsește în a 3a pereche de caractere
delimitate de caracterul , și înlocuiește cu șirul kiwi.
Copy
fruits='apple,banana,orange,mango,grapefruit,strawberry'
echo $fruits | sed -E 's/^(([^,]*,){3})[^,]*/\1kiwi/'
# apple,banana,orange,kiwi,grapefruit,strawberry
Expresii regulate
Expresiile regulate (sau regex, de la Regular Expressions) sunt șiruri de caractere
care formează un pattern de căutare, folosite pentru a identifica, căuta, și
manipula textul bazat pe anumite modele. Acestea sunt extrem de puternice și
flexibile, fiind utilizate în programare, procesarea textelor, analiza datelor, și
în diverse utilitare de sistem pentru a efectua operații complexe pe texte, cum ar
fi căutări, înlocuiri, și validări de format.
(abc)+: Se potrivește cu unul sau mai multe secvențe ale șirului abc.
Copy
echo -e "This is a critical error in the system\nThis is an error in the
application" | sed '/critical/s/error/warning/'
Acesta comandă utilizează expresia regulată pentru a căuta liniile care conțin
"critical" și apoi aplică substituția de "error" cu "warning" doar pe acele linii.
Copy
sed '/ERROR/!d' filename.log
Aici, ! este folosit pentru a nega pattern-ul, astfel d (delete) se aplică tuturor
liniilor care nu conțin "ERROR".
Copy
echo "John Doe; Address: Some Location; Phone: 123-456-7890" | sed -n 's/.*Phone: \
([0-9\-]\+\).*/\1/p'
Acesta comandă folosește paranteze pentru a crea un grup de capturare pentru
numărul de telefon și \1 pentru a referi primul grup de capturare în înlocuire,
astfel afișând doar numărul de telefon.
Copy
sed '/TODO/s/$/ [CHECKED]/' filename.txt
Acesta comandă caută liniile care conțin "TODO" și adaugă " [CHECKED]" la sfârșitul
fiecărei astfel de linii, folosind $ pentru a indica sfârșitul liniei.
Copy
sed '1,5s/^/Header: /' filename.txt
Acesta comandă adaugă "Header: " la începutul fiecărei linii de la 1 la 5.
Există mai multe tipuri de expresii regulate (regex), fiecare cu propriile sale
caracteristici și sintaxă, adaptate pentru diferite limbaje de programare și
unelte. Pentru contextul curent, prezintă relevanță:
Exemplu:
Copy
echo "Start long process..."
sleep 10 &
echo "Continuing with other commands..."
În acest exemplu:
Exemplu:
Copy
for i in {1..5}
do
(sleep $i; echo "Finished sleep $i") &
done
echo "All sleep commands are running in the background."
În acest exemplu:
Bucla for lansează cinci comenzi sleep, fiecare cu durate diferite (sleep 1, sleep
2, ..., sleep 5), și fiecare comandă este executată în background.
Fiecare comandă sleep afișează un mesaj când se finalizează, iar ordinea afișării
acestor mesaje depinde de durata fiecărei comenzi sleep.
Mesajul "All sleep commands are running in background." este afișat imediat după ce
toate comenzile sleep sunt inițiate, demonstrând concurența.
Exemplu:
Copy
#!/bin/bash
sleepy() {
sleep 5
echo $1
pidof $0 #Comanda pidof este utilizată pentru a afișa ID-ul procesului al
procesului curent executat. $0 într-un script Bash se referă la numele scriptului
însuși.
return
}
Când rulezi funcții sau comenzi în background în Bash (utilizând &), fiecare
comandă este lansată într-un proces separat de către shell. Aceste procese rulează
în paralel cu procesul principal și între ele. Sistemul de operare gestionează
aceste procese în mod independent, alocând timp de procesor în funcție de
disponibilitate și de prioritățile proceselor, care pot varia din diverse motive,
cum ar fi încărcarea sistemului și alți factori de scheduling intern.
Deoarece fiecare proces (în cazul acesta, fiecare execuție a funcției sleepy) poate
fi inițiat și programat independent de celelalte, nu există nicio garanție că vor
termina în ordinea în care au fost inițiate. De exemplu, chiar dacă sleepy 0 este
lansat primul, el poate fi preemtat de procesor, iar sleepy 1 sau sleepy 2 pot
obține acces la procesor și pot finaliza execuția înaintea lui sleepy 0.
Funcționalitatea fork:
Când un proces apelează fork(), sistemul de operare creează un nou proces. Noul
proces este aproape o copie identică a procesului părinte, inclusiv:
Codul programului
Datele
Valoarea variabilelor
Diferența majoră este că procesul copil are un nou ID unic de proces (PID). De
asemenea, unele resurse, cum ar fi semafoarele sau conexiunile de rețea, nu sunt
împărțite între procesul părinte și cel copil.
În Bash, când execuți o comandă în background folosind operatorul &, Bash folosește
fork pentru a crea un nou proces copil care să execute comanda, permițând shell-
ului să continue rularea altor comenzi fără a aștepta finalizarea comenzii lansate
în background.
Exemplu:
Copy
#!/bin/bash
$! este o variabilă specială în Bash care stochează PID-ul ultimului proces lansat
în background.
Prin urmare, atunci când scrii scripturi Bash care folosesc execuția asincronă sau
concurentă, este esențial să înțelegi și să planifici pentru aceste potențiale
probleme, asigurându-te că scriptul tău funcționează corect în toate scenariile
anticipate.
Source
Comanda source în Bash este utilizată pentru a executa un script în contextul
shell-ului curent. În loc să pornească un nou sub-shell pentru a rula scriptul,
comanda source încarcă și execută conținutul scriptului direct în shell-ul curent.
Acest lucru permite oricăror variabile sau funcții definite în script să rămână
disponibile în shell-ul curent după terminarea executării scriptului.
Exemplu de utilizare:
Pentru a demonstra, să presupunem că ai un script numit setenv.sh care setează
variabilele de mediu necesare pentru un proiect:
Copy
# Conținutul fișierului setenv.sh
export PROJECT_PATH="/path/to/project"
export DB_USER="username"
export DB_PASS="password"
Pentru a încărca acest script în shell-ul tău curent, ai folosi:
Copy
source setenv.sh
După executarea acestei comenzi, variabilele PROJECT_PATH, DB_USER, și DB_PASS vor
fi disponibile în shell-ul tău curent. Poți verifica acest lucru folosind echo
$PROJECT_PATH, care ar trebui să afișeze calea specificată.
Comanda source poate fi, de asemenea, invocată folosind punctul (.) ca un alias.
Următorul exemplu face același lucru ca și comanda source de mai sus:
Copy
. setenv.sh
Utilizarea comenzii source este esențială pentru lucrul eficient în Bash, permițând
modificări rapide și dinamice ale mediului de lucru fără a restarta shell-ul sau a
pierde starea curentă.
# Afișăm suma.
echo "Suma dintre $1 și $2 este $sum"
Copy
./call_sum_script.sh
Suma dintre 27 și 132 este 159