Lucrarea 2
Interpretorul de Comenzii
1. Introducere
În Linux, shell-ul este interpretorul de comenzi (asemănător cu command.com din MS-DOS). Spre deosebire de MS-DOS, în Linux există mai multe interpretoare de comenzi: bash (Bourne-Again Shell), tcsh (C Shell), pdksh, ksh, zsh. Între ele pot existe destul de mari diferenţe. În cele ce urmează ne vom ocupa de shell-ul bash, care este cel mai utilizat.
2. Desfăşurarea lucrării
2.1. Crearea şi rularea scripturilor (programelor shell)
Programele shell sunt fişiere care conţin una sau mai multe comenzi shell sau Linux. Aceste programe pot fi folosite pentru a simplifica anumite task-uri repetitive (ex. Installer-e interactive, testere automate de teme de programare).
Un exemplu de script este:
touch file1 # creare fisier file1
chmod 666 file1
# acum vom redirecta iesirea comenzii ls -l catre fisierul file1
# dupa care vom afisa continutul fisierului
ls -l > file1
cat file1
Pentru a executa programele se pot folosi mai multe modalităţi. Cea mai folosită este setarea atributului de executabil ( chmod +x <fişier> - vezi "man chmod" pentru mai multe detalii), după care comanda poate fi executată ca orice altă comandă Linux. Pentru comenzile care se află în directoare care nu sunt în calea de căutare ( se poate afla calea de căutare cu "echo $PATH" ) executarea lor se poate face cu "./comanda". O altă modalitate este de a pasa fişierul de comenzi ca un parametru al shell-ului: "bash <fisier>", sau "sh <fisier>".
2.2. Variabile
Ca şi în cazul limbajelor uzuale de programare, utilizarea variabilelor este foarte importantă în programele shell. Pe lângă variabilele predefinite (PS1, PATH, OSTYPE etc.), se pot defini şi altele. Pentru a asigna o valoare unei variabile, se foloseşte sintaxa:
variabilă=valoare
Exemplu: count=5, param=7, name=Garry, etc.
! Atenţie: Nu trebuie să existe spaţii de-o parte sau de alta a semnului "="
Se observă că nu este necesară declararea variabilelor. Pentru a accesa valoarea stocată într-o variabilă, se prefixează numele variabilei cu semnul "$".
Exemplu: pentru a afişa pe ecran valoarea variabilei count, se foloseşte:
echo $count
Dacă nu puneţi semnul "$", va fi afişat pe ecran cuvântul "count".
2.3. Variabile sistem
Există câteva variabile importante pentru sistem. Afişarea lor se face cu comanda "set" (puteţi folosi şi "set |less" pentru paginarea afişării). Setarea unei variabile se face la prompt-ul shell-ului cu comanda "<Nume_variabilă>=<Valoare>". <Valoare> nu înseamnă ceva numeric, poate fi şi un text, scris fără "". Se pot adăuga şi alte variabile la cele afişate de "set". Pentru a şterge o variabilă sistem, se foloseşte comanda "unset <Nume_variabilă>"
Informaţii despre variabilele importante pentru shell, precum şi ale informaţii despre acesta pot fi aflate cu comanda "man bash".
Exemplu (în dreptul celor mai importante am adăugat un comentariu):
BASH=/bin/bash # calea unde se află interpretorul de comenzi
BASH_ENV=/home/linux1/.bashrc #unul din fişierele de configuraţie ale shell-ului
BASH_VERSION=1.14.7(1) # versiunea interpretorului de comenzi
BROWSER=/usr/bin/netscape # browser-ul de web implicit
DISPLAY=:0
EUID=502
HISTFILESIZE=1000
HISTSIZE=1000
HOME=/home/linux1 # directorul de bază (home directory) al utilizatorului
HOSTNAME=localhost.localdomain # numele calculatorului
HOSTTYPE=i386 # tipul calculatorului (nu înseamnă tipul procesorului, ci familia de procesoare)
KDEDIR=/usr
LOGNAME=linux1 #numele de login al utilizatorului curent
MAIL=/var/spool/mail/linux1
OPTERR=1
OPTIND=1
OSTYPE=Linux # tipul sistemului de operare
PATH=/usr/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/linux1/bin:/home/linux1/Office51/bin # căile unde sunt căutate programele executabile
PPID=551
PS1=\u>[\W]\$ #structura prompt-ului
PS4=+
PWD=/home/linux1 # calea curentă
SHELL=/bin/bash
SHLVL=4
TERM=linux #tipul terminalului
UID=502
USER=linux1 #utilizatorul
USERNAME=
WRASTER_COLOR_RESOLUTION0=4
_=/etc/bashrc
2.4. Parametrii poziţionali. Alte variabile predefinite ale shell-ului.
Shell-ul recunoaşte un tip special de variabilă numit parametru poziţional. Aceştia sunt folosiţi pentru a accesa parametrii care au fost transmişi programului shell în linia de comandă sau unei funcţii shell de către programul care a apelat-o. Când rulaţi un program shell care suportă sau are nevoie de un număr de opţiuni, fiecare din aceste opţiuni este stocată într-un parametru poziţional. Primul parametru este stocat într-o variabilă cu numele "1", al doilea într-o variabilă cu numele "2" etc. Aceste variabile sunt rezervate de către shell astfel încât să nu poată fi definite de utilizator. Pentru a accesa aceste variabile, ele trebuie precedate de semnul "$", la fel ca variabilele definite de utilizator.
Exemplu: Următorul program este apelat cu 2 parametri. Programul tipăreşte cei 2 parametrii în ordine inversă:
echo "$2" "$1"
Alte variabile importante:
$# - conţine numărul de opţiuni transmise programului în linia de comandă;
$? - conţine valoarea de ieşire a ultimei comenzi executate (orice program executat întoarce o valoare; de obicei valoarea 0 înseamnă terminare normală a programului);
$0 - numele comenzii (numele programului);
$* - conţine toate argumentele care au fost transmise;
"$@" - conţine toate argumentele care au fost transmise programului în linia de comandă, fiecare între "".
2.5. Importanţa caracterelor "", ' ', \, ` `
Aceste caractere sunt folosite de către shell pentru a realiza diverse funcţii şi pentru a ascunde diverse caractere speciale faţă de shell.
"" - ascund spaţiile şi alte caractere de separaţie faţă de shell. Se foloseşte pentru a memora un şir care conţine asemenea caractere într-o variabilă. Exemplu:
salut="buna dimineata"
echo $salut
"" nu ascund caracterul "$", de exemplu.
salut="buna dimineata $LOGNAME"
echo $salut
va afişa: buna dimineata linux1
(dacă numele de utilizator cu care aţi intrat în sistem este linux1).
' ' - sunt cele mai puternice caractere de ascundere. Ele ascund şi caracterul "$". De exemplu:
salut='bună dimineaţa $LOGNAME'
echo $salut
va afişa: bună dimineaţa $LOGNAME
\ - ascunde câte un caracter. De exemplu, dacă dorim să afişăm textul:
preţul este $5
folosim: echo "preţul este \$5".
Dacă ar lipsi "\", atunci shell-ul ar interpreta $5 ca al 5-lea parametru poziţional.
` ` (backquote, butonul cu tilda, dinaintea lui 1 ) - se folosesc când este nevoie de rezultatul unei comenzi în interiorul altei comenzi:
contents=`ls` - execută comanda 'ls' şi stochează rezultatul acestei comenzi în variabila contents.
2.6. Rularea in background
Orice comandă poate fi rulată în background (în fundal) punând caracterul "&" după numele comenzii, la apelarea ei din shell.
Ex: comanda "sleep <număr>" întârzie afişarea prompt-ului sistem cu un <număr> de secunde. Această comandă este rulată în foreground - shell-ul nu poate primi alte comenzi până la terminarea ei. (Încercaţi, de exemplu, "sleep 10"). Să rulăm această comandă în background:
sleep 1000 &
[1] 704
704 - numărul de identificare (PID) al procesului lansat. Acest lucru se observă cu comanda "ps -A":
PID TTY TIME CMD
.............
.............
704 pts/0 00:00:00 sleep
705 tty1 00:00:00 ps
Pentru a termina un proces care rulează, se foloseşte comanda "kill PID", unde PID este numărul de identificare al procesului ce se doreşte terminat, aflat cu comanda "ps". Dacă se încearcă terminarea unui proces care nu a fost startat de utilizatorul curent, comanda va genera un mesaj de eroare ("not owner").
2.7. Operatii cu numere intregi
Intr-un program bash se poate lucra cu numere intregi. Pentru crearea de expresii asemanatoare cu cele din C, se pot folosi mai multe sintaxe. Doua dintre ele sunt “$[expresie]” si “$((expresie))”.
De asemenea comanda “expr expresie” afiseaza valoarea expresiei, si poate fi folosita cu backquotes (`expr expresie`). Nota: pentru expr, este nevoie de pauze intre operatii, iar unele caractere (de ex *) trebuie precedate de un backslash (\*)
# Exemplu de utilizare a operatiilor pe numere intregi
a=3 # a este initializat cu 3
a=$[$a-2] # a devine 1
echo '$a este' $a # afisarea variabilei a
b=2
echo 'Forma "$[$a+$b]" are ca rezultat' "$[$a+$b]"
echo 'Forma "$(($a+$b))" are ca rezultat tot' "$(($a+$b))"
echo 'Forma `expr $a + $b` are ca rezultat' `expr $a + $b`
echo '"$((1*2+3*4+5/6))" are ca rezultat' "$((1*2+3*4+5/6))"
echo '"$[6<<1]" are ca rezultat' "$[6<<1]"
b=$[2#11] # 11 in baza 2, deci b=3
echo "a=$a b=$b a+b*2=$[$a+$b*2] (a+b)*2=$[($a+$b)*2] a+1b=$[$a+1$b] a+b/4=$(($a+$b/4)) a=(1+$a)"
2.8. Operatii cu siruri de caractere (stringuri)
${#sir} – reprezinta lungimea sirului
${sir:pos}, ${sir:pos1:pos2} – extrag din sir, subsirul de la pozitia pos +1 pana la sfarsit sau de la pozitia pos1+1, un numar de caractere egal cu pos2.
${sir#subsir}, ${sir##subsir}, ${sir%subsir}, ${sir%%subsir} – elimina din sir (de la inceput daca se foloseste simbolul # sau de la sfarsit daca se foloseste simbolul %) cea mai scurta portrivire (daca e un singur simbol) sau cea mai lunga potrivire (daca sunt doua simboluri consecutive).
${sir/vechi/nou}, ${sir//vechi,nou}, ${sir/#vechi,nou}, ${sir/%vechi,nou} – sunt folosite pentru inlocuirea de subsiruri in sirul dat. Subsirul, $nou inlocuieste subsirul $vechi astfel: numai pentru prima aparitie, pentru toate aparitiile, numai daca $sir incepe cu $vechi, numai daca $sir se termina cu $vechi.
sir=abcABC123ABCabc
echo ${sir} # abcABC123ABCabc
echo ${#sir} # 15
echo ${sir:10} # BCabc
echo ${sir:3:6} # ABC123
echo ${sir#a*C} # 123ABCabc
echo ${sir##a*C} # abc
echo ${sir%b*c} # abcABC123ABCa
echo ${sir%%b*c} # a
echo ${sir/c/x} # abxABC123ABCabc
echo ${sir//c/x} # abxABC123ABCabx
echo ${sir/#abc/x} # xABC123ABCabc
echo ${sir/%abc/x} # abcABC123ABCx
2.9. Comanda test
Această comandă este folosită în shell pentru a evalua expresii condiţionate. Se foloseşte pentru a evalua o condiţie care este folosită la luarea unei decizii sau ca condiţie pentru terminarea sau continuarea iteraţiilor. Are următoarea formă:
test expresie sau [expresie] -> întoarce True sau False
Câţiva operatori predefiniti pot fi folosiţi cu această comandă. Sunt 4 grupuri de operatori: pentru întregi, pentru şiruri de caractere, pentru fişiere şi operatori logici.
Operatori pentru întregi:
int1 -eq int2- întoarce True dacă int1=int2
int1 -ge int2- întoarce True dacă int1>=int2
int1 -gt int2- întoarce True dacă int1>int2
int1 -le int2- întoarce True dacă int1<=int2
int1 -lt int2- întoarce True dacă int1<int2
int1 -ne int2- întoarce True dacă int1 diferit de int2
Operatori pentru şiruri de caractere:
str1=str2- întoarce True dacă str1=str2
str1!=str2- întoarce True dacă str1 diferit de str2
str- întoarce True dacă str nu este vid
-n str- întoarce True dacă lungimea şirului este mai mare ca 0
-z str- întoarce True dacă şirul are lungimea 0
Operatori pentru fişiere:
-d nume_fişier- întoarce True dacă fişierul este director
-f nume_fişier- întoarce True dacă fişierul este fişier obişnuit
-r nume_fişier- întoarce True dacă fişierul poate fi citit de către proces
-s nume_fişier- întoarce True dacă fişierul are o lungime diferită de 0
-w nume_fişier- întoarce True dacă fişierul poate fie scris de către proces
-x nume_fişier- întoarce True dacă fişierul este executabil
Operatori logici:
! expresie- întoarce True dacă expresia nu este adevărată
expresie1 -a expresie2 - întoarce True dacă cele două expresii sunt ambele adevărate (şi)
expresie1 -o expresie2 - întoarce True dacă expresia 1 sau expresia 2 sunt adevărate (sau)
2.10. Instrucţiuni conditionale
Instrucţiunea if
Sunt suportate instrucţiuni shell imbricate. Sintaxa instrucţiunii este:
if [expresie]
then
comenzi
elif [expresie2]
then
comenzi
else
comenzi
fi
Clauzele elif şi else sunt opţionale. elif este o prescurtare de la else if. Comenzile asociate cu instrucţiunea else sunt executate numai dacă nici una din expresiile asociate cu if sau elif sunt adevărate.
Exemplu:
Următorul program verifică dacă există un fişier .profile în directorul curent.
if [ -f .profile ]
then
echo "Există un fişier .profile în directorul curent"
else
echo "Nu am putut găsi un fişier .profile în directorul curent"
fi
Instrucţiunea case
Instrucţiunea case dă posibilitatea comparării unui model cu mai multe alte modele şi să execute un bloc de instrucţiuni dacă este găsită o identificare. Instrucţiunea case din shell este ceva mai puternică decât omoloaga ei din Pascal sau C (case, respectiv switch) deoarece poate compara şiruri de caractere conţinând aşa numitele caractere wildcards.
Sintaxa instrucţiunii este următoarea:
case sir1 in
s1)
comenzi;;
s2)
comenzi;;
*)
comenzi;;
esac
şir1 este comparat cu s1 şi cu s2. Dacă unul din aceste două şiruri este identic cu şir1 atunci se execută comenzile înaintea semnelor ";;". Dacă nici unul din aceste şiruri nu este identic cu şir1 atunci se execută comenzile asociate semnului "*". Acesta se foloseşte în cazul nici unei identificări deoarece semnul "*" este echivalent cu orice şir.
Exemplu:
Acest program verifică dacă prima opţiune din linia de comandă a fost -i sau -e. Dacă a fost -i programul numără câte linii conţine programul specificat de a doua opţiune care încep cu litera i. Dacă prima opţiune a fost -e programul face acelaşi lucru ca în primul caz pentru liniile ce încep cu e. Dacă prima opţiune nu a fost -i sau -e, atunci programul afişează un scurt mesaj de eroare.
case $1 in
-i)
count=`grep ^i $2 | wc -l`
echo "Numărul de linii din $2 care încep cu litera i este $count"
;;
-e)
count=`grep ^e $2 | wc -l`
echo "Numărul de linii din $2 care încep cu litera e este $count"
;;
*)
echo "Această opţiune nu este recunoscută"
;;
esac
2.11. Instrucţiuni de iterare
Instrucţiunea for
Instrucţiunea for execută comenzi de un anumit număr de ori. Există două variante ale acestei instrucţiuni.
Prima variantă de sintaxă a instrucţiunii este:
for var1 in listă
do
comenzi
done
În această formă, instrucţiunea for se execută odată pentru fiecare element din listă. Această listă poate fi o variabilă care conţine mai multe cuvinte separate prin spaţii sau poate fi o listă de valori care sunt introduse direct în instrucţiune. De fiecare dată când execută o buclă, variabilei var1 îi este dată valoarea elementului curent din listă, până când se ajunge la ultimul element.
A doua formă de sintaxă a instrucţiunii este:
for var1
do
comenzi
done
În această formă, instrucţiunea for se execută odată pentru fiecare element din variabila var1. Când este folosită această formă a instrucţiunii, programul presupune ca variabila var1 conţine toţi parametrii poziţionali care au fost transferaţi în shell din linia de comandă. Această formă este echivalentă cu:
for var1 in "$@"
do
comenzi
done
Exemplu:
Programul foloseşte ca parametri orice număr de fişiere text, pe care le citeşte, le converteşte toate literele în majuscule şi apoi salvează fişierele modificate cu acelaşi nume cu extensia .caps.
for file
do
tr a-z A-Z < $file >$file.caps
done
Instrucţiunea while
Aceasta execută un bloc de cod atâta timp cât o expresie condiţională este adevărată.
Sintaxa instrucţiunii este:
While expresie
do
comenzi
done
Exemplu:
Următorul program listează parametrii care au fost transferaţi programului, împreună cu numărul lor.
count=1
while [ -n "$*" ]
do
echo "Acesta este parametrul cu numărul $count $1"
shift
count=`expr $count + 1`
done
Aşa cum veţi vedea mai departe, comandă shift mută parametrii din linia de comandă peste un parametru la stânga.
Instrucţiunea until
Instrucţiunea until este foarte asemănătoare ca sintaxă şi funcţie cu while. Singura diferenţă reală între cele două este că instrucţiunea until execută blocul de cod atâta timp cât expresia condiţională este falsă, pe când while execută atâta timp cât expresia condiţională este adevărată.
Sintaxa instrucţiunii este:
until expresie
do
comenzi
done
Exemplu:
Acelaşi exemplu care a fost folosit la instrucţiunea while poate fi folosit şi în cazul de faţă. Tot ceea ce trebuie făcut este negarea condiţiei.
count=1
until [ -z "$*" ]
do
echo "Acesta este parametrul cu numărul $count $1"
shift
count=`expr $count + 1`
done
Singura diferenţa dintre acest exemplu şi cel anterior este faptul că opţiunea -n (care însemna că şirul are lungime diferită de 0) a fost înlocuită cu opţiunea -z (care semnifică faptul că şirul are lungime 0).
În practică instrucţiunea until nu este foarte folositoare deoarece poate fi oricând înlocuită cu while.
Instrucţiunea shift
Instrucţiunea shift mută valoarea curentă stocată în parametrul poziţional cu o poziţie la stânga. De exemplu, dacă valorile parametrilor poziţionali curenţi sunt: $1 = -r $2 = file1 $3 = file2 şi se execută comandă shift atunci parametrii poziţionali rezultaţi vor fi: $1 = file1 $2 = file2.
De asemenea, se pot muta parametrii poziţionali cu mai multe poziţii la stânga cu instrucţiunea shift. Următorul cod mută parametrii poziţionali cu 2 poziţii la stânga: shift 2.
Această instrucţiune este foarte folositoare atunci când avem un program shell care trebuie să parcurgă opţiuni din linia de comandă. Deoarece opţiunile sunt, de obicei, procesate într-o buclă, de multe ori poate se doreşte trecerea la următorul parametru odată ce s-a identificat ce opţiune ar trebui să urmeze. De exemplu, următorul program asteaptă 2 opţiuni - una care specifică un fişier de intrare şi una care specifică un fişier de ieşire. Programul citeşte fişierul de intrare, transformă toate caracterele fişierului în majuscule şi apoi salvează rezultatul în fişierul de ieşire.
while [ "$1" ]
do
if [ "$1" = "-i" ]
then
infile="$2"
shift 2
elif [ "$1" = "-o" ]
then
outfile="$2"
shift 2
else
echo "Programul $0 nu recunoaşte opţiunea $1"
shift 1
fi
done
if [ -n "$infile" -a -n "$outfile" -a -f "$infile" ]
then
tr a-z A-Z <$infile >$outfile
else
echo "Parametrii insuficienti sau invalizi"
fi
2.12. Funcţii
Limbajele shell permit utilizatorului să definească funcţii. Acestea se comportă la fel ca şi funcţiile definite în C sau alte limbaje de programare. Principalul avantaj al folosirii funcţiilor este în scopuri organizaţionale. Codul scris folosind funcţii tinde să fie mai uşor de citit, de corectat şi, de asemenea, tinde să fie mai mic deoarece se poate grupa codul care apare în mai multe locuri în program în funcţii.
Sintaxa creării unei funcţii este următoarea:
numef () {
comenzi
}
Odată ce a fost definită funcţia, aceasta se apelează cu următoarea comandă:
numef [param1 param2 param3 ...]
Se poate transmite orice număr de parametri la o funcţie. Când transmiteţi parametri la o funcţie, aceasta îi vede ca parametrii poziţionali, exact ca un program shell care preia parametrii din linia de comandă.
Exemplu:
Următorul program shell conţine câteva funcţii, fiecare din acestea realizând o anumită cerinţă asociată cu unul din parametrii liniei de comandă. Acest exemplu ilustrează mai multe instrucţiuni discutate în această lucrare. Programul citeşte toate fişierele care sunt date în linia de comandă şi, în funcţie de opţiunea care este utilizată, scrie fişierele de ieşire cu majuscule, litere mici sau le tipăreşte.
upper () {
shift
for i
do
tr a-z A-Z <$1 >$1.out
rm $1
mv $1.out $1
shift
done; }
lower () {
shift
for i
do
tr A-Z a-z <$1 >$1.out
rm $1
mv $1.out $1
shift
done; }
print () {
shift
for i
do
cat $1
shift
done; }
usage_error () {
echo "$1 syntax is $1 <option> <input files>"
echo ""
echo "where option is one of the following"
echo "p — to print frame files"
echo "u — to save as uppercase"
echo "l — to save as lowercase"; }
case $1
in
p | -p) print $@;;
u | -u) upper $@;;
l | -l) lower $@;;
*) usage_error $0;;
esac
2.13. Exercitii:
A. Scrie un script care primeste ca parametru un numar N, N > 1. Scriptul creeaza N fisiere executabile, denumite 'fis_k', k=1..N. Prin executia fisierululi cu numarul k se afiseaza la consola numere de la k la N
B. Scrieti un script care listeaza intr-un fisier HTML_HEADS prima linie a fiecarui fisier cu extensia html din directorul curent
2.14. Linkuri utile:
Bash Tutorial: http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO.html
Advanced Bash-Scripting Guide: http://tldp.org/LDP/abs/html/index.html