cap8

23
1. Bourne Again Shell 8.1 Introducere Bourne Again Shell (bash) oferă o serie de facilităţi în plus faţă de shell-ul standard Bourne, atât la nivel de interpretor al liniei de comandă, cât şi la nivel de limbaj de programare. Printre altele, bash oferă istoric al comenzilor, alias-uri, auto-completarea comenzilor şi a numelor de fişiere, editarea liniei de comandă, etc. Unele dintre aceste facilităţi sunt prezente şi în shell-ul standard Bourne, însă proiectul GNU a extins shell-ul pentru a include un număr de noi facilităţi şi pentru compatibilitate POSIX. O dată cu realizarea variantei bash 2.x, au fost adăugate atât de multe funcţionalităţi (unele din Korn shell şi C shell), încât shell-ul bash reprezintă un shell complect funcţional atât la nivel de interpretor cât şi la nivel de limbaj de programare. Shell-ul bash s-a născut pe 10 ianuarie 1988 sub oblăduirea lui Brian Fox şi a fost adoptat mai târziu de către Chet Ramey. Prima versiune a fost versiunea bash 0.99, iar cele mai importante îmbunătăţiri au fost aduse odată cu versiunea 2.0. Toate versiunile sunt disponibile free sub licenţă publică GNU. Pentru a vedea ce versiune de bash foloseşte, un utilizator poate folosi comanda bash --version sau poate afişa valoarea variabilei de mediu BASH_VERSION. Următorul exemplu ne înfăţişează o posibilă situaţie: $ bash --version GNU bash, version 2.04.11(1)-release (i386-redhat-linux-gnu) Copyright 1999 Free Software Foundation, Inc. $ echo $BASH_VERSION 2.04.11(1)-release BOURNE AGAIN SHELL 8

Upload: george-padurariu

Post on 28-Dec-2015

7 views

Category:

Documents


0 download

DESCRIPTION

LINUX

TRANSCRIPT

Page 1: cap8

1. Bourne Again Shell

8.1 Introducere

Bourne Again Shell (bash) oferă o serie de facilităţi în plus faţă de shell-ul standard Bourne, atât la nivel de interpretor al liniei de comandă, cât şi la nivel de limbaj de programare. Printre altele, bash oferă istoric al comenzilor, alias-uri, auto-completarea comenzilor şi a numelor de fişiere, editarea liniei de comandă, etc. Unele dintre aceste facilităţi sunt prezente şi în shell-ul standard Bourne, însă proiectul GNU a extins shell-ul pentru a include un număr de noi facilităţi şi pentru compatibilitate POSIX.

O dată cu realizarea variantei bash 2.x, au fost adăugate atât de multe funcţionalităţi (unele din Korn shell şi C shell), încât shell-ul bash reprezintă un shell complect funcţional atât la nivel de interpretor cât şi la nivel de limbaj de programare. Shell-ul bash s-a născut pe 10 ianuarie 1988 sub oblăduirea lui Brian Fox şi a fost adoptat mai târziu de către Chet Ramey. Prima versiune a fost versiunea bash 0.99, iar cele mai importante îmbunătăţiri au fost aduse odată cu versiunea 2.0. Toate versiunile sunt disponibile free sub licenţă publică GNU. Pentru a vedea ce versiune de bash foloseşte, un utilizator poate folosi comanda bash --version sau poate afişa valoarea variabilei de mediu BASH_VERSION. Următorul exemplu ne înfăţişează o posibilă situaţie:

$ bash --version GNU bash, version 2.04.11(1)-release (i386-redhat-linux-gnu) Copyright 1999 Free Software Foundation, Inc. $ echo $BASH_VERSION 2.04.11(1)-release

BOURNE AGAIN SHELL 8

Page 2: cap8

UNIX

8.2 Iniţializare şi mediu

În momentul iniţializării, shell-ul bash parcurge o serie de paşi. Primul proces ce se rulează în momentul iniţializării este init, cu identificatorul PID=1. Acest proces iniţiază un proces getty, care deschide porturile terminalului, oferind un “spaţiu de manevră” pentru standard input şi pentru standard output şi standard error. În urma acestui proces va fi afişat un prompter de login pe ecran. În continuare se execută programul /bin/login, care cere o parolă, criptează şi verifică parola, setând un mediu iniţial de lucru şi pornind shell-ul de login (care în cazul nostru este /bin/bash).

După aceea, procesul (programul) bash caută fişierul de configurare /etc/profile şi execută comenzile din acesta. În continuare, se caută în directorul home un fişier de configurare iniţială denumit .bash_profile. După executarea comenzilor din acest fişier, se va executa o comandă din fişierul ENV al utilizatorului, de regulă .bashrc şi, în cele din urmă, prompter-ul (semnul $ pentru utilizatorul obişnuit sau # pentru root) este afişat pe ecran, aşteptând introducerea de comenzi.

Dacă dorim modificarea shell-ului la linia de comandă, putem să introducem simplu numele shell-ului şi să apăsăm ENTER. De exemplu, dacă folosim la un moment dat shell-ul standard Bourne şi dorim să schimbăm în Bourne Again shell, nu avem decât să introducem bash la linia de comandă, urmat de tasta ENTER:

$ ps PID TTY TIME CMD 5549 tty1 00:00:00 sh 5429 tty1 00:00:00 ps $ bash bash-2.04$ bash-2.04$ ps PID TTY TIME CMD 5449 tty1 00:00:00 sh 5465 tty1 00:00:00 bash 5472 tty1 00:00:00 ps

Explicaţii. Iniţial, comanda ps (process status) ne arată procesele

aflate în execuţie. Se observă că rulează shell-ul Bourne (sh). Se introduce apoi comanda bash la prompter şi apare un nou prompter (bash-2.04$).

Page 3: cap8

Bourne again shell

Introducând din nou comanda ps, se afişează de data aceasta două shell-uri care rulează: shell-ul iniţial sh şi shell-ul bash pornit ulterior.

Fişiere de iniţializare Shell-ul bash are o serie de fişiere de configurare pe care le verifică

la pornire. Aceste fişiere de configurare sau iniţializare sunt procesate (rulate) în funcţie de mai multţi factori (dacă shell-ul curent este şi shell-ul de login, dacă este un alt shell interactiv diferit decât cel de login, sau un shell non-interactiv - shell-script). Să vedem în continuare care sunt fişierele de configurare iniţială şi ordinea în care aceste fişiere sunt procesate.

În momentul login-ului, înainte de apariţia prompter-ului shell, este executat fişierul de configurare globală, /etc/profile. După aceea, dacă există, este procesat fişierul .bash_profile din directorul home al utilizatorului. Acest fişier specifică alias-uri şi funcţii şi setează variabile de mediu specifice utilizatorului sau porneşte anumite shell-script-uri. Dacă fişierul .bash_profile nu există, dar există fişierul .bash_login, atunci acest fişier va fi procesat, iar dacă acesta din urmă nu există, va fi procesat fişierul .profile (din directorul home al utilizatorului).

8.2.1 Fişierul /etc/profile

Fişierul /etc/profile reprezintă un fişier de configurare globală a întregului sistem. Acest fişier este editat de către administratorul sistemului pentru a executa anumite comenzi atunci când se iniţializează sistemul. El este executat atunci când porneşte shell-ul bash. El are acelaşi efect şi în cazul shell-urilor Korn şi Bourne şi, de regulă, îndeplineşte anumite sarcini precum rularea programului ce administrează mailul (mail spooler) sau afişarea unui mesaj iniţial, denumit „mesajul zilei” (message of the day, din fişierul /etc/motd). În continuare este prezentat un exemplu de fişier /etc/profile (nu am numerotat liniile goale): 1 #/etc/profile 2 # System wide environment and startup programs 3 # Functions and aliases go in /etc/bashrc 4 PATH=”$PATH:/usr/X11R6/bin” 5 ulimit -S -c 1000000 > /dev/null 2>&1 6 if [ ‘id -gn’ = ‘id -un’ -a ‘id -u’ -gt 14 ]; then 7 umask 002 8 else

Page 4: cap8

UNIX

9 umask 022 10 fi 11 USER=’id -un’ 12 LOGNAME=$USER 13 MAIL=”/var/spool/mail/$USER” 14 HOSTNAME=`/bin/hostname` 15 HISTSIZE=1000 16 if [ -z “$INPUTRC” -a ! -f “$HOME/.inputrc” ]; then 17 INPUTRC=/etc/inputrc 18 fi 19 export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE INPUTRC 20 for i in /etc/profile.d/*.sh ; do 21 if [ -x $i ]; then 22 . $i 23 fi 24 done 25 unset i

Prezentăm în continuare câteva explicaţii analizând fişierul

/etc/profile pe linii: Liniile 1,2,3 - Comentarii în shell-script; Linia 4 - Variabilei PATH i se atribuie directoarele unde va căuta

shell-ul comenzile introduse; Linia 5 - Comanda ulimit stabileşte limita maximă a dimensiunii

fişierelor core la 1.000.000 bytes. Fişierele core sunt fişiere create în urma opririi anormale a programelor şi pot ocupa dimensiuni considerabile din spaţiul de pe disc;

Liniile 6-10 - Comanda if care realizează următoarele: „dacă numele grupului este egal cu numele utilizatorului şi numărul ID al utilizatorului este mai mare decât 14”, atunci setează umask cu valoarea 002. În acest caz, atunci când sunt create directoarele vor avea drepturi de acces 775 iar fişierele vor avea 664. Altfel, umask va avea valoarea 022, de unde rezultă drepturi 755 pentru directoare şi 644 pentru fişiere.

Linia 11 - Variabilei USER i se atribuie valoarea pe care o returnează comanda id -un (numele utilizatorului)

Linia 12 - Variabilei LOGNAME i se atribuie valoarea din $USER; Linia 13 - Variabila MAIL ia valoarea egală cu numele directorului

unde se programul de administare a mail-ului va salva mail-ul; Linia 14 - Variabila HOSTNAME ia valoarea numelui maşinii; Linia 15 - Variabila HISTSIZE ia valoarea 1000 (numărul de

comenzi stocate în memoria history);

Page 5: cap8

Bourne again shell

Liniile 16-18 - Comanda if care verifică dacă fişierul cu numele dat de variabila INPUTRC are dimensiunea diferită de zero şi nu există fişierul .inputrc din directorul home şi în acest caz variabila INPUTRC ia valoarea /etc/inputrc;

Linia 19 - Variabilele ce apar în această linie sunt „exportate” pentru a fi disponibile în sub-shell-uri şi procese copil;

Linia 20-24 - O comanda for şi una if care realizează următoarele: pentru fiecare fişier cu extensia .sh din directorul /etc/profile.d, se verifică dacă este executabil, iar dacă este executabil, se execută fişierul utilizându-se comanda dot (.). Fişierele din directorul /etc/profile.d, lang.sh şi mc.sh, setează setul de caractere şi de fonturi şi crează o funcţie denumită mc (Midnight Commander) care reprezintă un browser (asemănător cu vestitul Norton Commander pentru DOS/Windows) vizual de fişiere. Pentru pornirea acestuia se scrie mc la linia de comandă.

Linia 25 - Variabila i este resetată prin comanda unset, adică ştearsă din memoria shell-ului.

8.2.2 Fişierul .bash_profile

Dacă fişierul .bash_profile este găsit în directorul home al utilizatorului, atunci este executat imediat după /etc/profile. Dacă .bash_profile nu există, atunci shell-ul bash va căuta fişierul .bash_login sau, dacă nici acesta nu există, fişierul .profile, toate din directorul home. Doar unul dintre cele trei fişiere menţionate anterior va fi executat, iar căutarea se face în ordinea specificată (bash_profile, .bash_login, .profile). De asemenea, bash va verifica existenţa fişierului .bashrc şi îl va executa comenzile din acesta dacă fişierul există. Prezentăm în continuare un exemplu de fişier .bash_profile:

1 #.bash_profile 2 # Get the aliases and functions 3 if [ -f ~/.bashrc ]; then 4 . ~/.bashrc 5 fi 6 PATH=$PATH:$HOME/bin 7 BASH_ENV=$HOME/.bashrc 8 USERNAME=”razvan” 9 export USERNAME BASH_ENV PATH 10 mesg n

Page 6: cap8

UNIX

Prezentăm în continuare câteva explicaţii analizând fişierul .bash_profile pe linii:

Liniile 1,2 - Comentarii în shell-script; Liniile 3-5 - Comandă if care verifică dacă fişierul .bashrc se află în

directorul home şi dacă există îl rulează; Linia 6 - Variabilei PATH i se adaugă o cale către subdirectorul bin

din directorul home; Linia 7 - Variabilei BASH_ENV i se atribuie valoarea specificată

(calea către fişierul .bashrc). Fişierul .bashrc conţine funcţii şi alias-uri definite de utilizator;

Linia 8 - Variabila USERNAME ia valoarea razvan; Linia 9 -Variabilele specificate sunt exportate pentru a fi valabile în

subshell-uri şi procese copil; Linia 10 - Comanda mesg executată cu opţiunea n are ca efect

blocarea mesajelor primite cu comanda write de la alţi utilizatori.

8.2.3 Fişierul .bashrc

Fişierul .bashrc este automat executat ori de câte ori un shell bash porneşte, conţinând numai setări valabile pentru shell-ul bash.

Avem în continuare un exemplu de fişier .bashrc:

1 #.bashrc 2 # User specific aliases and functions 3 # Source global definitions 4 if [ -f /etc/bashrc ]; then 5 . /etc/bashrc 6 fi 7 set -o noclobber 8 alias mv=’mv -i’ 9 alias dir=’ls -al’ 10 function cd { builtin cd $1; echo $PWD; }

Explicaţii: Liniile 1,2,3 - Comentarii în shell-script; Liniile 4-6 - Comandă if care verifică existenţa fişierului /etc/bashrc

şi în acest caz îl rulează; Linia 7 - Comanda set apelată cu opţiunea noclobber determină

apariţia unor mesaje preventive în cazul suprascrierii fişierelor deja existente;

Page 7: cap8

Bourne again shell

Linia 8 - Se declară un alias pentru comanda mv, astfel încât de acum încolo ea va fi apelată automat cu opţiunea -i(interactive);

Linia 9 - Se declară un alias pe nume dir, care va efectul apelului comenzii ls -al;

Linia 10 - Se defineşte o funcţie utilizator. Când utilizatorul va schimba directorul curent se va afişa şi directorul curent de lucru. Numele fucnţiei este cd şi conţine comanda internă cd (change directory). Comanda internă builtin precede numele comenzii cd pentru a preveni intrarea în recursivitate infinită a funcţiei.

8.2.4 Fişierul .profile

Fişierul .profile este un fişier de configurare definit de utilizator. El se găseşte în directorul home al utilizatorului şi este executat imediat dacă shell-ul de login este shell-ul Bourne. În cazul în care shell-ul de login este bash, fişierul .profile va fi rulat doar dacă nici unul din fişierele enumerate anterior nu există. Fişierul conţine de regulă setări ale mediului şi ale terminalului sau alte setări legate de diverse aplicaţii, dacă acestea sunt necesare.

Avem în continuare un exemplu de fişier .profile:

1 #.profile 2 # Fisier de configurare initiala ce se executa atunci cand 3 # se utilizeaza shell-ul sh sau nu exista fisierele 4 # .bash_profile sau .bash_login 5 HOSTNAME=`uname -n` 6 EDITOR=/bin/vi 7 PATH=/bin:/usr/bin:/usr/ucb:/usr/local:/usr/bin:. 8 PS1=”$HOSTNAME $> ” 9 export TERM HOSTNAME EDITOR PATH PS1 10 stty erase ^h 11 clear

Explicaţii: Liniile 1-4 - Comentarii în shell-script; Linia 5 - Variabila HOSTNAME va lua valoarea returnată de

comanda uname -n; Linia 6 - Variabila EDITOR ia valoarea /bin/vi. Programe precum

mail şi history vor folosi acest editor; Linia 7 - Variabila PATH este setată; Linia 8 - Prompter-ului primar îi este atribuită valoarea lui

HOSTNAME (numele maşinii), urmată de semnul $ şi semnul >;

Page 8: cap8

UNIX

Linia 9 - Sunt exportate variabilele specificate, pentru a fi valabile în subshell-uri şi procese copil;

Linia 10 - Comanda stty setează caracteristicile terminalului. Tasta Erase este setată cu valoarea ^h, astfel încât când vom apăsa tasta Backspace, litera de dinaintea cursorului va fi ştearsă;

Linia 11 - Comanda clear şterge conţinutul ecranului.

8.3 Programarea în bash

Scrierea unui shell-script în bash este similară cu operaţia în Bourne Shell. Un shell-script se scrie cu ajutorul unui editor şi conţine comenzi UNIX, comentarii şi cuvinte cheie de programare shell. Deoarece utilizăm shell-ul bash, prima linie din shell-script va fi:

#!/bin/bash

De asemenea, script-ul trebuie să fie executabil, de aceea vom folosi

comanda chmod pentru a îl face executabil. În exemplul următor presupunem că script-ul se numeşte script01:

$ chmod +x script01 $ ls -lF script01 -rwxr-xr-x 1 razvan 12 Aug 12:00 script01*

8.3.1 Citirea variabilelor

Citirea variabilelor se face ca şi în shell-ul clasic cu comanda read. Comanda read preia caractere de la tastatura până când se întâlneşte un caracter newline. Caracterul (newline) de sfârşit de linie este transformat într-un octet null atunci când este citit. Dacă nu se specifică nici un nume pentru variabilă, linia citită este atribuită unei variabile interne, numită REPLY. Putem, de asemenea, folosi comanda read pentru a face un program să se oprească în momentul în care apăsăm tasta ENTER. Opţiunile comenzii read sunt prezentate în tabelul 8.1. De exemplu, opţiunea -r este folosită pentru ignorarea perechii backslash/newline, astfel încât caracterul special backslash este considerat ca parte din linie.

Page 9: cap8

Bourne again shell

Opţiunile comenzii read

Tabelul 8.1 Comanda Semnificaţie

read raspuns Se citeşte de la tastatură în variabila raspuns read r1 r2 Se citeşte de la tastatură în variabila r1 până la primul

caracter whitespace sau Enter, apoi în variabila r2 read Se citeşte în variabila internă REPLY read -a nume_vector Se citeşte o listă de cuvinte într-un vector denumit

nume_vector read -e Se foloseşte în shell-ul interactiv read -p prompter Afişează un prompter, aşteaptă input-ul şi stochează

rezultatul în variabila REPLY read -r linie Permite introducerea liniilor ce conţin un backslash

8.3.2 Operaţii aritmetice

Variabilele pot fi declarate ca numere întregi cu ajutorul comenzii declare -i. O variabilă numar poate fi declarată astfel:

$ declare -i numar

Se pot face operaţiile aritmetice uzuale cu variabilele declarate

întregi, folosind operatorii pentru adunare, scădere, înmulţire, împărţire şi împărţire modulo. Exemple:

$ declare -i numar $ numar=3+7 $ echo $numar 10 $ numar= 6 + 9 bash: +: command not found (caracterele whitespace trebuie incadrate de ghilimele, corect este: numar=”6 + 9”) $ numar=4*5 $ echo $numar 20

Page 10: cap8

UNIX

Comanda declare -i fără nici un argument va lista toate variabilele întregi definite, precum şi valorile lor, ca în exemplul următor:

$ declare -i declare -ir EUID=”0” declare -ir PPID=”846” declare -ir UID=”0” declare -i numar=”20” $

Se pot utiliza numere reprezentate în baze de numeraţie diferite, de

la baza 2 la baza 36. Formatul general de reprezentare într-o anumită bază este:

variabila=baza#numarul_in_acea_baza

Spre exemplu, dacă: nr=2#110, atunci avem numărul 110

reprezentat în baza 2. Se pot declara numere octale (în baza 8) dacă valoarea numerică a acestora este precedată de un 0 (zero). De exemplu, dacă avem comanda: declare -i nr=020, atunci variabila nr are valoarea zecimală 16.

Comanda let Comanda let este o comandă internă bash utilizată pentru a realiza

operaţii aritmetice cu întregi şi a testa expresii numerice. Operatorii care sunt acceptaţi de comanda let pot fi vizualizaţi cu ajutorul comenzii: help let.

Numere reale Shell-ul bash oferă suport doar pentru operaţii cu numere întregi;

dacă avem de făcut calcule cu numere reale, putem folosi utilitarele bc, awk sau nawk.

Exemple:

$ nr=`echo “scale=2; 12 / 5” | bc` $ echo $nr 2.40 $ p=`gawk -v x=2.2 -v y=3.3 ‘BEGIN{printf “%.2f\n”,x*y}’` $ echo $p 7.26

Page 11: cap8

Bourne again shell

8.3.3 Parametri poziţionali şi comanda set

Parametrii poziţionali sunt aceeaşi cu cei din Bourne Shell: $0 este numele script-ului, $1-$9 reprezintă valorile parametrilor poziţionali iar $* listează toţi parametrii poziţionali. Dacă numărul parametrilor poziţionali era restrâns la 9 pentru shell-ul Bourne, în bash putem folosi câţi parametri poziţionali dorim. Pentru valori mai mari decât 9, ei vor fi referiţi astfel: ${10}, ${11}, etc. ”$*” semnifică extinderea la un singur argument, de genul: “$1 $2 $3”, iar ”$@” are semnificaţia: ”$1” ”$2” ”$3”. Considerăm următorul exemplu:

#!/bin/bash #Numele script-ului este test echo “Numele script-ului este $0” echo “Primul parametru pozitional este $1” echo “Al doilea parametru pozitional este $2” echo “Al 10-lea parametru pozitional este ${10}”

Apelat astfel: $./test 1 2 3 4 5 6 7 8 9 10 11

vom obţine listing-ul: Numele script-ului este ./test Primul parametru pozitional este 1 Al doilea parametru pozitional este 2 Al 10-lea parametru pozitional este 10

Comanda set poate fi folosită pentru resetarea parametrilor

poziţionali. Dacă este apelată fără nici un argument, comanda set va afişa toate variabilele care au fost definite în acel shell, locale sau exportate. În momentul apelului comenzii cu unul sau mai multe argumente, parametrii pozitionali definiţi anterior se pierd (pentru a reveni la valorile iniţiale se poate utiliza comanda set --).

Page 12: cap8

UNIX

Fie exemplul următor: #!/bin/bash #Numele script-ului este test2 echo “Numele script-ului este $0” echo “Parametrii sunt: $*” echo “Al doilea parametru pozitional este $2” echo “Numarul parametrilor este $#” arg_vechi=$* set ion maria cornel gheorghe stefan echo “Parametrii sunt acum: $*” set $(date) echo “Astazi este $3 $2 $6” echo “Vechii parametri sunt: $arg_vechi” set $arg_vechi echo $1 $2 $3

În urma execuţiei shell-script-ului de mai sus prin apelul: ./test2 1 2

3 se va afişa:

Numele script-ului este ./test2 Parametrii sunt: 1 2 3 Al doilea parametru pozitional este 2 Numarul parametrilor este 3 Parametrii sunt acum: ion maria cornel gheorghe stefan Astazi este 15 Aug 2004 Vechii parametri sunt: 1 2 3 1 2 3

Variabilele speciale pot fi folosite şi în construcţii speciale: #!/bin/bash #Numele script-ului este test3 nume=${1:?”necesita un argument!”} echo Buna ziua, $1!

Dacă test3 este apelat fără nici un argument, va fi afişat mesajul de

eroare: ./test3: 1: necesita un argument! Dacă facem apelul: ./test3 Razvan, vom obţine rezultatul: Buna ziua, Razvan!

Page 13: cap8

Bourne again shell

8.3.4 Construcţii condiţionale şi controlul fluxului

Construcţiile condiţionale ne permit să testăm anumite condiţii şi apoi să executăm anumite comenzi în funcţie de rezultatul testărilor. Comanda if reprezintă cea mai simplă construcţie condiţională utilizată pentru luarea deciziilor. Ca şi shell-ul Bourne, bash utilizează valoarea lui exit-status pentru a vedea care este rezultatul unei comenzi (reamintim că în cazul în care comanda s-a încheiat cu succes sau cu valoarea logică true exit-status-ul este egal cu zero iar în caz contrar exit-status-ul are o valoare diferită de zero). Valoarea lui exit-status pentru ultima comandă este $?.

Pentru a evalua starea de adevăr a unei expresii este folosită comanda internă test, sau se folosesc parantezele pătrate. Considerăm în continuare câteva exemple de utilizare a comenzii test, a parantezelor pătrate, construcţia cu paranteze pătrate duble [[ ]] şi cu paranteze duble (( )).

Exemplul 1.

Vom introduce la linia de comanda: $ nume=barbu $ grep “$nume” /etc/passwd > /dev/null $ echo $? 0 #barbu a fost gasit in /etc/passwd $ test $nume != barbu $ echo $? 1 #numele nu este diferit de barbu $ [ $nume = barbu ] $ echo $? 0 #testul este adevarat de aceasta data $ [ $nume = [Bb]arbu ] $ echo $? 1 #comanda test nu permite folosirea metacaracterelor

Pentru a putea utiliza metacaractere în cadrul comenzii test trebuie să

folosim parantezele pătrate duble, ca în exemplul următor: $ [[ $nume == [Bb]arbu ]] $ echo $? 0

Page 14: cap8

UNIX

Prin utilizarea parantezelor duble (( )) se pot evalua expresii numerice. Considerăm exemplul următor:

$ x=3; y=5 $ (( x > y )) $ echo $? 1

Rezultatul afişat este 1 deoarece x nu este mai mare decât y (3<5). Comanda if Noul format al comenzii if pentru bash este:

if [[ com_test1 ]] then secventa_comenzi1 else secventa_comenzi2 fi

sau, pentru testări numerice:

if (( com_test1 )) then secventa_comenzi1 else secventa_comenzi2 fi

Observaţie. else este opţional. Comanda exit Comanda exit este utilizată pentru a termina shell-script-ul şi a

reveni la linia de comandă. Comenzii exit i se poate da un argument (un număr între 0 şi 255) pentru a indica faptul că programul s-a încheiat cu succes sau nu. Asemănător cu exit-status-ul, valoarea 0 înseamnă încheierea cu succes a programului. Argumentul pe care îl dăm comenzii exit se află stocat în variabila $?.

Page 15: cap8

Bourne again shell

Comanda case Comanda case este utilizată pentru construcţii condiţionale multiple.

Formatul acesteia este acelaşi cu formatul shell-ului standard Bourne: case variabila in sablon_1) comenzi ;; sablon_2) comenzi ;; … *) comenzi ;; esac

Dacă nici una dintre valorile sablon_1, sablon_2, etc. nu corespund

valorii variabilei respective, se vor executa în mod implicit comenzile ce urmează după construcţia specială *).

8.3.5 Comenzi de ciclare

Comenzile utilizate pentru ciclare sunt: for, while, until, select. Dintre acestea, primele trei ne sunt familiare din shell-ul Bourne. În shell-ul bash apare un nou mecanism de ciclare, denumit select, utilizat cu principalul scop de a crea meniuri.

Comanda select afişează un meniu cu opţiuni numerotate 1, 2, 3 etc. Afişarea se face în fişierul standard error (pe ecran, implicit). Pe ecran, cererea de a introduce una dintre opţiuni se face prin afişarea prompter-ului PS3 (al treilea prompter), care implicit are valoarea #?. Dacă dorim să afişăm un mesaj, va trebui să modificăm doar valoarea lui PS3. Dar să vedem cum funcţionează comanda select: după afişarea meniului şi a prompter-ului PS3, se aşteaptă pentru introducerea unui număr din lista afişată. Input-ul va fi stocat în variabila specială REPLY.

De regulă, comanda select se foloseşte împreună cu comanda case pentru a crea un meniu ce permite utilizatorului să facă o selecţie dintr-un meniu şi pe baza acelei selecţii să execute anumite comenzi. De asemenea, se pot utiliza variabilele predefinite LINES şi COLUMNS pentru a schimba modul de afişare pe ecran a opţiunilor (aceste variabile sunt definite

Page 16: cap8

UNIX

începând cu versiunea 2.0 bash). Pentru a întrerupe execuţia comenzii select se va folosi fie break (pentru ieşirea din ciclul select), fie exit pentru a ieşi din program. Formatul general al instrucţiunii select este:

select variabila in lista do comenzi done

Prezentăm în continuare câteva exemple de utilizare a comenzii

select. Exemplul 1. #!/bin/bash # Nume shell-script: meniu_select PS3=”Selectati optiunea dorita:” select prog in ‘uname -n’ date cal who exit do $prog done

Execuţia programului va duce la afişarea unui meniu: 1) uname -n 2) date 3) cal 4) who 5) exit Selectati optiunea dorita:

În funcţie de opţiunea selectată, se va executa comanda respectivă. Ciclul select va rula fie până la introducerea opţiunii 5 (exit va determina ieşirea din program), fie până la introducerea combinaţiei Ctrl+C.

Page 17: cap8

Bourne again shell

Exemplul 2. #!/bin/bash # Nume shell-script: culori PS3=”Alegeti una dintre culori sau exit:” select alegere in albastru galben verde exit do case alegere in albastru) echo “Albastrul este culoarea cerului!” break;; galben) echo “Galbenul este o culoare calda!” break;; verde) echo “Verde este padurea!” break;; *) echo “$REPLY nu este o optiune valida!” echo “Incercati din nou.” ;; esac done

Iniţial, se va afişa meniul:

1) albastru 2) galben 3) verde 4) exit Alegeti una dintre culori sau exit:

În funcţie de opţiunea aleasă se va afişa mesajul corespunzător. Dacă

se introduce valoarea 4 se va ieşi din program şi orice altă valoare introdusă va afişa mesajul în care se precizează că nu este o opţiune validă şi se va repeta introducerea opţiunii.

Rularea instrucţiunilor de ciclare în background Instrucţiunile de ciclare pot fi rulate în background, astfel încât

programul poate continua fără să aştepte terminarea instrucţiunii de ciclare.

Page 18: cap8

UNIX

În exemplul următor se trimite un email mai multor utilizatori preluat din fişierul scrisoare:

#!/bin/bash for nume in radu andrei andra serban gabi do mail $nume < scrisoare done &

Semnul & (ampersand) de la sfârşitul instrucţiunii for determină

execuţia ciclului for în background. Programul îşi va continua execuţia până la terminarea ciclului for.

Separatorul IFS Separatorul intern al shell-ului IFS (Internal Field Separator) are

valoare de spaţiu, tab sau caracter de linie nouă (newline). El este folosit drept caracter separator în cadrul comenzilor pentru interpretarea semantică a cuvintelor cheie, precum: for, do, done, etc. Acest separator poate fi modificat de către utilizator dacă se doreşte utilizarea altui separator într-o listă. Înainte de a modifica valoarea separatorului, este bine să memorăm vechea valoare a lui IFS în altă variabilă, pentru a putea reface starea iniţială a separatorului. Prezentăm în continuare un exemplu de modificare a separatorului IFS.

#!/bin/bash # Exemplu pentru IFS nume=Radu:Andrei:Andra:Serban:Gabi ifs_vechi=”$IFS” IFS=”:” for persoana in $nume do echo Salut $persoana done IFS=”$ifs_vechi” set Cristi Cristina Paul for prenume in $* do echo Buna ziua $prenume done

Page 19: cap8

Bourne again shell

La început, variabila nume are valoarea şirului de caractere Radu:Andrei:Andra:Serban:Gabi, numele fiind separate de câte un caracter „:”. Vechea valoare a lui IFS este memorată în variabila ifs_vechi. Deoarece valoarea lui IFS este un caracter whitespace, trebuie încadrată între ghilimele. Apoi lui IFS i se atribuie o nouă valoare, semnul „:” care va fi de acum utilizat pe post de separator. Ca urmare a execuţiei shell-script-ului anterior se va afişa:

Salut Radu Salut Andrei Salut Andra Salut Serban Salut Gabi Buna ziua Cristi Buna ziua Cristina Buna ziua Paul

8.3.6 Funcţii

Funcţiile au fost introduse în shell-ul Bourne începând cu versiunea AT&T UNIX SystemVR2 (SVR2) şi le-au fost adăugate o serie de îmbunătăţiri în shell-ul Bourne Again. Funcţiile reprezintă asocierea unui nume pentru o comandă sau un grup de comenzi, fiind folosite pentru modularizarea programelor, făcându-le astfel mai eficiente. Funcţiile sunt executate în contextul shell-ului curent, astfel că nu se creează un nou proces copil ca în cazul rulării unui program executabil precum cat. Funcţiile pot fi scrise în fişiere separate pentru a fi încărcate în memorie din shell-script atunci când trebuie folosite. În continuare sunt prezentate regulile de scriere a funcţiilor în Bourne Again Shell:

1. O funcţie trebuie definită înainte de a fi utilizată. 2. Funcţia rulează în mediul de lucru curent, partajând variabilele din

shell-script-ul ce o invocă şi oferă posibilitatea de transmitere de argumente prin atribuirea acestora ca parametri poziţionali. Variabilele locale pot fi create în cadrul unei funcţii prin utilizarea funcţiei local.

3. Shell-ul determină dacă se foloseşte un alias, o funcţie, o comandă internă sau un program executabil aflat pe hard-disc. Ordinea de căutare este: alias, funcţie, comandă internă, program executabil.

4. În momentul în care se utilizează comanda exit în cadrul unei funcţii, se produce ieşirea din shell-script-ul ce apelează funcţia. Dacă se

Page 20: cap8

UNIX

iese din funcţie, întoarcerea în shell-script se face pe linia următoare apelului funcţiei.

5. Comanda return în cadrul unei funcţii determină returnarea valorii exit-status a ultimei comenzi executate în cadrul funcţiei sau valoarea argumentului specificat.

6. Funcţiile pot fi exportate în sub-shell-uri folosind comanda internă export -f.

7. Pentru a lista funcţiile şi definiţiile acestora, se foloseşte comanda declare -f. Începând cu versiunea 2.x. bash, pentru a lista doar numele funcţiilor se foloseşte comanda declare -F.

8. Dacă funcţiile sunt stocate într-un fişier separat, acestea pot fi încărcate în mediul curent prin comanda source sau comanda dot (.).

9. Se permite declarea funcţiilor recursive (ce se auto-apelează). Nu există o limită maximă impusă pentru numărul de apeluri recursive.

10. Variabilele, ca şi valorile trap sunt globale în cadrul funcţiilor. Ele sunt partajate atât de către shell-script cât şi de funcţiile invocate de acel shell-script. Dacă o valoare trap este definită în cadrul unei funcţii, ea este, de asemenea, partajată de shell-script.

Alte comenzi legate de funcţii. Pentru a reseta o funcţie se foloseşte comanda unset. Formatul

general al acestei comenzi este: unset -f nume_functie

Pentru a exporta o funcţie într-un sub-shell se foloseşte comanda export. Formatul general al acestei comenzi este:

export -f nume_functie

În cadrul unei funcţii, argumentele pot fi transmise funcţiilor prin

intermediul parametrilor poziţionali. Parametrii poziţionali sunt privaţi în cadrul funcţiilor, astfel încât argumentele unei funcţii nu vor afecta parametrii poziţionali utilizaţi în afara acesteia. Pentru a crea variabile locale (care nu vor mai fi valabile la ieşirea din funcţie) pentru o funcţie anume putem folosi comanda internă local.

Comanda return poate fi utilizată pentru a termina execuţia funcţiei şi a preda controlul script-ului apelant. Valoarea returnată de o funcţie este egală cu valoarea exit-status returnată de ultima comandă executată

Page 21: cap8

Bourne again shell

în cadrul funcţiei, cu excepţia cazului în care se specifică un anumit argument prin intermediul comenzii return. În momentul în care se specifică o valoare prin comanda return, această valoare este stocată în variabila ? şi poate lua valori întregi cuprinse între 0 şi 255. Datorită acestei limitări, se poate utiliza substituirea comenzii pentru a captura valoarea returnată de o funcţie. În acest sens, se poate scrie toată funcţia între paranteze precedate de semnul $ sau între apostrofurile inverse (``) pentru a captura şi atribui valoarea returnată de către funcţie unei variabile, ca şi în cazul obţinerii rezultatului unei comenzi UNIX.

Prezentăm în continuare câteva exemple de utilizare a funcţiilor. Exemplul 1. #!/bin/bash # Nume shell-script: verific_functie function UTI { echo “Eroare: $*” 2>&1; exit 1; } if (( $# != 2 )) then UTI “$0: necesita doua argumente” fi if [[ ! ( -r $1 && -w $1 ) ]] then UTI “$1: fara drepturi de citire si scriere” fi echo Argumentele sunt: $* ...# comenzi

Explicaţii. În exemplul anterior se defineşte o funcţie UTI ce este utilizată

pentru a afişa un mesaj de eroare pe ecran (fişierul standard de eroare). Argumentele funcţiei constau în orice şir de caractere trimise odată cu apelul funcţiei. Argumentele sunt stocate în $*, variabila specială ce stochează toţi parametrii poziţionali. În cadrul unei funcţii, parametrii poziţionali sunt consideraţi locali şi nu au efect asupra parametrilor utilizaţi în afara funcţiei. În cadrul script-ului se verifică dacă numărul parametrilor poziţionali este 2 şi în caz contrar se afişează mesajul de eroare corespunzător. În momentul apelării funcţiei UTI, şirul ”$0: necesita doua argumente” este transmis funcţiei şi memorat în variabila $* iar comanda echo va afişa mesajul de eroare şi apoi se va returna valoarea de ieşire 1, ceea ce indică faptul că ceva nu a fost în regulă. În continuarea programului,

Page 22: cap8

UNIX

se verifică dacă primul argument este un fişier care poate fi citit şi modificat, iar funcţia UTI va fi apelată cu argumentul „$1: fara drepturi de citire si scriere”. Argumentele ce provin de la linia de comandă sunt stocate din nou în $*, fără a avea efect pentru valoarea $* din cadrul funcţiei.

Exemplul 2.

#!/bin/bash # Nume shell-script: incrementare INC () { local suma; let “suma=$1 + 1” return $suma } echo -n “Suma este:” INC 5 echo $? echo $suma # variabila suma nu este cunoscuta in #afara functiei

Explicaţii. În exemplul anterior se defineşte o funcţie INC. În cadrul acestei

funcţii se declară variabila locală suma, care nu este cunoscută în afara funcţiei. În momentul apelului funcţiei, valoarea primului argument, $1 va fi incrementată cu 1 iar rezultatul va fi atribuit variabilei suma. Comanda internă return va atribui variabilei $? valoarea din variabila suma, apoi şirul este afişat pe ecran. Funcţia INC este apelată cu argumentul 5, iar exit-status-ul ei va fi stocat în variabila $?. Pentru ultima linie din program nu se va afişa nimic, deoarece variabila suma a fost declarată doar în interiorul funcţiei (cu ajutorul comenzii interne local) şi valoarea acestei variabile nu este cunoscută în afara funcţiei.

Page 23: cap8

Bourne again shell

Exemplul 3.

#!/bin/bash # Nume shell-script: cub function CUB { local rez; let “rez=$1 * $1 * $1” echo “Numarul ce va fi ridicat la cub este $1” echo “Rezultatul este $rez” } echo -n “Introduceti numarul pentru ridicare la cub:” read numar rezultat=$(CUB $numar) echo -n “$rezultat”

Explicaţii. În exemplul anterior se defineşte o funcţie CUB. În cadrul acestei

funcţii se declară variabila locală rez, care va stoca rezultatul. În cadrul script-ului se citeşte valoarea care va fi ridicată la cub în variabila numar, apoi se atribuie variabilei rezultat valoarea întoarsă de funcţie prin intermediul substituirii comenzii (construcţia cu numele funcţiei între paranteze precedate de semnul $). Prin comanda echo de pe ultima linie a shell-script-ului se afişează rezultatul.

Observaţie. Ca şi în Bourne Shell, funcţiile se încarcă în memorie cu

ajutorul comenzii dot (.). În Bourne Again Shell există însă şi comanda source, care se apelează astfel:

$ source fisier_functii

unde fisier_functii este fişierul ce conţine funcţiile ce vor fi utilizate.