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 - 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 ", sau "sh ". 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 "=". 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 " 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 " întârzie afişarea prompt-ului sistem cu un 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$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