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