Elemente de programare în Shell-ul Unix

Mihai Budiu -- mihaib+@cs.cmu.edu
http://www.cs.cmu.edu/~mihaib/

noiembrie 2000

Subiect:
programare în Bourne Shell
Cunoștințe necesare:
cunoștințe elementare de programare; familiaritate cu Unix
Cuvinte cheie:
shell, proces, variabilă, comandă, script


Cuprins




Aveți adesea de manipulat multe fișiere? Procesați frecvent texte? Sunteți un administrator de sistem? Dacă intrați într-una din aceste categorii, atunci scula software pe care o voi descrie în continuare se poate dovedi exact obiectul de care aveți nevoie.

În acest articol voi discuta despre ``shell'', într-una din înfățișările pe care le are în sistemul de operare Unix. Am mai scris articole despre shell în PC Report; le recomand cititorilor cărora articolul de față le stîrnește interesul să se uite și peste cele mai vechi; un articol în PC Report din iunie 1997 discută despre cum este implementat un shell. Articolul meu din decembrie 1996 discută despre sistemele de operare și menționează în treacăt și rolul shell-ului. Dacă între timp ați făcut curat în bibliotecă și nu mai aveți numerele vechi, puteți găsi on-line articolele scrise de mine în pagina mea de web.

Chiar dacă 95% din umanitate folose'ste sistemul de operare Windows, de data aceasta nu mai pot fi acuzat că ignor marea audiență cu bună intenție: sistemele create de Microsoft oferă doar facilități rudimentare în această privință. Există într-adevăr mai multe pachete software pentru Windows oferite de terți care implementează funcționalități de shell, dar shell-ul standard rămîne cel DOS.

Shell-ul: un limbaj de programare

Dacă nu vreți să vă osteniți prea tare să căutați articolele mele mai vechi, voi rezuma aici cîteva din informațiile mai importante de care avem nevoie.

În primul rînd, ``shell'' înseamnă cochilie sau carapace. Numele acestui program vine din faptul că ``învelește'' nucleul sistemului de operare, precum cochilia miezul moale al melcului: utilizatorul nu are de-a face cu nucleul ci interacționează prin intermediul shell-ului. Pentru că nu îmi vine în minte nici o traducere potrivită, voi continua să folosesc cuvîntul englezesc.

Shell-ul este un program care permite utilizatorilor să tasteze și execute comenzi. Una din funcțiile foarte importante ale shell-ului este de a permite utilizatorilor să pornească în execuție alte programe. Numim un program în curs de execuție proces. Toate procesele utilizatorilor pe un sistem Unix descind dintr-un shell.

În Unix shell-ul oferă mai mult decît abilitatea de a lansa procese în execuție: oferă o sumedenie de comenzi și facilități suplimentare, care-l fac un mediu ideal pentru a activități de administrare a sistemului. Toate aceste comenzi formează un limbaj de programare deosebit de puternic; shell-ul este deci un interpretor, care citește și execută comenzi.

Shell-uri interactive și neinteractive

Putem deci distinge două utilizări separate ale shell-ului: prima utilizare constă în a executa comenzi simple, în general pentru a lansa în execuție alte programe. În acest caz shell-ul prezintă utilizatorului un prompt, o invitație de a primi o comandă. După ce utilizatorul tastează comanda, shell-ul o execută imediat, iar cînd comanda își termină execuția (sau chiar mai înainte, dacă este instruit în acest sens), shell-ul oferă din nou un prompt. Acest mod de utilizare, în care fiecare comandă este citită de la utilizator și executată se numește interactiv. În acest text vom folosi semnul procent % pentru a indica prompt-ul.

A doua utilizare a shell-ului este pentru execuția unor programe mai complicate, scrise dinainte și depozitate în fișiere. Un fișier cu comenzi pentru shell se numește în engleză shell script (adică un ``scenariu pentru cochilie''), sau pe scurt ``script''. Executarea unui script se mai numește ``procesare în vrac'', batch processing, pentru că shell-ul nu se mai oprește după fiecare comandă cu un prompt.

Ca orice alt limbaj de programare, cunoașterea doar a unei fracțiuni din elementele de bază se dovedește perfect satisfăcătoare pentru nevoile de zi cu zi. Voi ilustra aici numai comenzile cele mai puternice, și pe acestea le voi descrie mai mult prin exemple decît riguros. Pentru cei doritori de aprofundarea subiectului, secțiunea finală despre surse de informații suplimentare se poate dovedi un punct bun de plecare.

Familii de shell-uri

Dacă Windows NT nu are un shell decent, în lumea Unix situația este chiar pe dos; un scurt istoric este necesar pentru a lămuri babilonia de opțiuni existente.

Primul shell tradițional pentru Unix a fost scris în 1976 de Steve Bourne, care pe vremea aceea lucra la laboratoarele Bell ale companiei AT&T. În onoarea creatorului său, shell-ul acesta este numit ``Bourne shell''. Programul cu pricina se numește simplu ``sh'', și se află de obicei în directorul /bin pe un sistem Unix (/bin/sh). Să nu uităm că sistemul de operare Unix însuși fusese creat cu puțin timp în urmă în același loc, inspirat de sistemul de operare Multics (vedeți și articolul meu despre istoria Unix-ului). Shell-ul Bourne introducea o mulțime de concepte revoluționare, care făceau viață utilizatorilor și administratorilor mult mai simplă decît în sistemele de operare precedente. Shell-ul Bourne era mai curînd proiectat pentru utilizarea sub formă de interpretor, și mai puțin pentru cea interactivă.

Cînd la începutul anilor '80 la universitatea Berkeley din California a fost dezvoltată varianta locală de Unix, numită BSD, Bill Joy, un talentat programator, a scris un nou shell numit C Shell, sau csh (pronunțat de guru în engleză ``siși''). (Bill Joy este unul dintre fondatorii companiei Sun, la care actualmente este Chief Scientist.) Acest shell introduce facilități foarte utile pentru execuția interactivă, dar din păcate nu este compatibil cu Bourne, și suferă de o mulțime de neajunsuri.

În 1984 David Korn, tot de la Bell Labs ale lui AT&T, și-a propus să modernizeze shell-ul Bourne adăugîndu-i facilități interactive a la csh. Astfel s-a născut shell-ul Korn ksh, care este excelent realizat și compatibil cu Bourne.

Din păcate ksh inițial nu era software ``free''; așa că unul din primele proiecte ale fundației Free Software Foundation (vedeți articolul meu despre Open Source din PC Report din iunie 1998) a fost să implementeze un nou shell, de data asta complet ``liber''. Acest shell moștenește din ideile lui ksh, dar ia cîteva lucruri bune de la csh. Noul shell a fost implementat original de Brian Fox, care era plătit de FSF; numele shell-ului este ``Bourne Again SHell'' (``din nou Bourne''), sau bash (citit aproximativ ``beș''). Acesta este shell-ul standard pe sistemele GNU/Linux.

O mișcare paralelă a transformat substanțial csh în ceea ce a devenit tcsh, sau ``tisișel''. tcsh este compatibil cu csh, dar nu cu sh.

Prin 1981 Microsoft a lansat sistemul de operare MS-DOS. Acesta era echipat cu un shell foarte primitiv, numit COMMAND.COM. Deși creat ulterior Unix-ului, și inspirat de acesta, shell-ul MS-DOS este extrem de primitiv, inconsistent și greu de folosit. Din păcate a rămas, cu minore îmbunătățiri, shell-ul standard chiar și sub Windows2000.

În Windows însă multe dintre funcțiunile unui shell sunt luate de un program grafic, numit Command Manager sub Windows 3.1. Pentru utilizatori novici un shell grafic oferă o interfață mult mai simplă și intuitivă, dar pentru un ins experimentat sau pentru un administrator facilitățile acestuia (și ale altor programe de administrare) sunt adesea frustrante.

De aici încolo avem de a face cu o explozie de noi shell-uri, care aduc tot felul de înflorituri și varietăți; voi cita astfel: ash, zsh, scsh, rc, bsh, pdksh, es.

În articolul de față voi vorbi despre Bourne Shell; acest shell a evoluat și el de-a lungul timpului, și a fost și standardizat de comitetul POSIX, care a standardizat Unix. De aceea, sh este de departe alternativa cea mai sigură: chiar dacă nu aveți alte shell-uri la dispoziție, sh este sigur disponibil (pe sisteme gen GNU/Linux se află bash, care are un mod de funcționare în compatibilitate 100% cu sh).

Elemente fundamentale de programare în Shell

În restul acestui articol voi discuta doar despre programarea în shell. Elementele shell-ului legate de utilizarea interactivă vor fi trecute sub totală tăcere (cum ar fi editorul liniilor de comandă, mecanismul de re-execuție a comenzilor (history), controlul job-urilor lansate în execuție).

Comenzi externe

Shell-ul este un program ale cărui capacități pot fi înzecite de faptul că poate controla toate celelalte programe. Am spus deja că shell-ul poate porni în execuție orice alt program; în plus, shell-ul poate influența în mai multe moduri mediul în care programul respectiv este executat. Vom vedea mai jos cîteva exemple.

Cel mai important de știut pentru moment este faptul numele oricărui fișier executabil (adică un fișier care conține o aplicație) este o comandă shell. Astfel, fișierul ``netscape'' conține imaginea executabilă a browser-ului Internet; cînd shell-ului îi este oferită spre execuție comanda netscape, el va lansa în execuție programul acesta. Un fișier executabil mai este de aceea numit ``comandă externă''.

Capacitatea de a controla alte programe dă shell-ului alura unui limbaj extensibil; creind noi programe de fapt creăm noi ``comenzi'' pentru shell. Shell-ul poate combina execuția mai multor programe controlînd comunicația dintre ele, creînd astfel cu ușurință programe mai complicate din bucăți simple.

Pentru ilustrație vom folosi în acest text cu precădere cîteva comenzi externe simple; iată-le rezumate aici:

ls:
Programul ls se citește ``list'', și este echivalentul lui ``dir'' din DOS. Este urmat de o listă de nume de fișiere, iar efectul lui este de a afișa chiar numele acestor fișiere. Urmat de un director, afișează fișierele din acel director. (Fără argumente operează pe directorul ``curent''.) Iată un exemplu:

% ls
Mail  bin  data  lib  man  src  tmp
% ls src
Makefile  hello.c   
%

Am ilustrat aici funcționarea interactivă a shell-ului, pe care o vom folosi pînă avem destule elemente pentru a scrie programe mai mari. Caracterul % a fost scris de shell, fiind prompt-ul. utilizatorul a tastat apoi ls; shell-ul a identificat acest șir de caractere ca fiind numele unui fișier executabil, care a executat. Cînd se execută, ls afișează conținutul directorului curent (Mail, bin, etc.). După ce ls s-a terminat, shell-ul afișează din nou prompt-ul, cu promptitudine.

wc:
Programul Word Count număra liniile, cuvintele și caracterele din fișierele care-i sunt date ca argumente. Iată un exemplu:

% wc src/hello.c
      6      9     74 src/hello.c
%

Vedem mai sus că fișierul hello.c din directorul src are 6 linii, 9 cuvinte și 74 caractere.

cat:
deși înseamnă ``pisică'', programul cat de fapt tipărește conținutul fișierelor care-i sunt oferite drept argument (echivalent cu type din DOS). Numele lui vine de la ``CATalogue''.

% cat src/hello.c
#include <stdio.h>

main() {
    printf("Hello world\n");
    return 0;
}
%

Succes și eroare

În Unix, cînd un proces se termină returnează procesului său părinte1 un cod de eroare, care permite părintelui să detecteze dacă odrasla-i s-a executat cu succes sau a întîmpinat niște probleme.

În mod surprinzător, cel puțin pentru un programator C, 0 este codul pentru succes, și orice valoare nenulă indică o eroare. Vom vedea mai încolo cum aceste valori pot fi folosite de shell pentru a crea programe complicate.

Variabile shell

Ca orice limbaj de programare, limbajul shell-ului conține variabile. În mod tradițional, variabilele shell sunt scrise numai cu majuscule, pentru că în Unix programele executabile au nume scrise cu minuscule; în felul acesta se evită confuziile. Valoarea unei variabile shell este un șir arbitrar de caractere. Numele variabilelor sunt aceleași ca în limbajul C: o literă urmată de litere, cifre și semnul ``subliniat'' _.

Principala limitare a shell-ului, care îl face nepractic pentru scrierea de programe mari, constă în sărăcia tipurilor de date disponibile: există un singur tip de date, șirul de caractere. Nu există numere, structuri, matrici, liste, arbori, etc. Pentru a scrie programe mai complicate, aceste structuri de date trebuie implementate pornind de la șiruri, ceea ce e complicat și adesea ineficient. Dar asta nu ne va îndepărta de la scopul de a vedea ce putem face totuși folosind shell-ul.

Atribuiri

Cea mai simplă comandă a shell-ului este cea de atribuire; ea are forma:

variabila=valoare

Efectul acestei comenzi este de a atribui valoarea din dreapta variabilei din stînga. Atenție: nu puteți pune spații la stînga și la dreapta semnului egal.

Valoarea unei variabile

sh este un limbaj straniu prin faptul că pentru a accesa valoarea unei variabile trebuie s-o prefixăm cu semnul dolar. Deci atribuirea și citirea folosesc nume diferite! O eroare comună este de a folosi dolar la atribuire sau de a uita dolarul la citire. Atenție, deci:

[1] % DIR=src
[2] % ls $DIR
[3] Makefile  hello.c
[4] % ls DIR
ls: DIR: No such file or directory
[5] % $DIR=bin
sh: src=bin: command not found
[6] % DIR=ls
[7] % $DIR src
Makefile  hello.c
[8] % DIR=$DIR$DIR
[9] % ls $DIR
ls: lsls: No such file or directory
[10] % DIR=src/
[11] % ls ${DIR}Makefile
Makefile

În linia [2] variabila DIR este substituita cu valoarea sa, src. În linia [4], DIR este chiar numele directorului căutat. Iar în linia [5], în loc să se facă o atribuire variabilei DIR, valoarea acesteia este substituită, generînd comanda src=bin, care nu este interpretată la rîndul ei ca o atribuire, ci este direct executată, și care, fiind inexistentă, generează un mesaj de eroare. Dacă atribuim lui DIR valoarea ls, ca în linia [6], executînd linia [7] executăm de fapt ls src. În linia [8] observăm cum putem concatena valoarea a două variabile juxtapunîndu-le. În fine, linia [11] arată cum procedăm dacă vrem să concatenăm valoarea unei variabile DIR cu un alt șir de caractere: trebuie să folosim acolade pentru a delimita numele variabilei.

Ghilimele

Shell-ul permite construirea de șiruri de caractere folosind două tipuri de ghilimele: apostroful () și ghilimelele duble ("). Diferența între cele două tipuri este că între apostrofuri valorile variabilelor nu sunt substituite, pe cînd între ghilimele sunt. (La fel și pentru expresiile regulate, despre care nu am vorbit încă). Ghilimelele sunt utile cînd vrem să includem spații în valorile variabilelor.

Variabile interne

Shell-ul însuși folosește unele variabile pentru nevoile sale interne. Schimbînd valoarea acestor variabile putem afecta comportarea sa. De exemplu, variabila numită PS1 este chiar prompt-ul. Variabila internă PWD este directorul curent.

% PS1="ordonati, stapine: "
ordonati, stapine: ls
Mail  bin  data  lib  man  src  tmp
ordonati, stapine:

Unele din variabilele interne nu pot fi atribuite, ci pot fi doar citite. Unele din acestea au nume stranii, formate din alte caractere decît litere și cifre; de exemplu variabila $? conține rezultatul cu care s-a terminat ultima comandă (0 pentru succes).

Comenzi interne importante

Unele comenzi ar putea fi implementate atît sub formă de comenzi interne cît și externe. Dar altele trebuie neapărat să fie implementate de către shell. De exemplu comanda cd, care schimbă directorul curent: aceasta nu poate fi o comandă externă; dacă cd ar fi implementată de un program separat, atunci cînd shell-ul ar porni programul numit cd, acesta s-ar executa, ar schimbă directorul său curent după care s-ar termină. Dar schimbările făcute de un proces fiu nu se propagă la părinții lui (ci doar invers), deci shell-ul rămîne în directorul inițial.

În această secțiune vom vedea alte cîteva comenzi interne importante.

cd:
(change directory) este urmată de numele unui director și schimbă directorul curent în acel director;

exit:
este urmată de o valoare numerică; shell-ul își termină execuția cu valoarea indicată. Deci exit 0 înseamnă ``succes''. Adesea programatorii utilizeaza acest cod pentru a transmite procesului-părinte informații despre eroarea petrecută;

echo:
(ecou) își tipărește argumentele. Este o comandă deosebit de utilă:

[1] % DIR=src
[2] % echo DIR $DIR
DIR src
[3] % echo DIR         DIR
DIR DIR
[4] % echo "DIR         DIR"
DIR         DIR
[5] % echo "$DIR         $DIR"
src         src
[6] % echo '$DIR         $DIR'
$DIR         $DIR

Observați diferența între liniile [3] și [4]; în linia [3] echo primește două argumente, iar în linia [4] primește unul singur, un șir care conține și spații.

eval:
Aceasta este o comandă extrem de puternică. Argumentul ei este un șir de caractere, care este executat ca și cum ar fi o comandă shell.

[1] % DIR=src
[2] % COMANDA=ls
[3] % echo "$COMANDA $DIR"
ls src
[4] % eval "$COMANDA $DIR"
Makefile  hello.c

test:
e foarte utilă pentru a evalua expresii boolene (care adică generează un rezultat ``adevărat'' sau ``fals''). Ca și codurile de eroare ale proceselor, ``adevărat'' este 0, iar fals este orice valoare diferită de 0.

test poate face patru feluri de teste diferite:

% DIR=src; FILE=Makefile
% test -f $DIR/$FILE
% echo $?
0
%

În exemplul anterior ilustrăm mai multe concepte noi: în prima linie punem două comenzi pe aceeași linie, separîndu-le cu punct-și-virgulă. În linia a doua testăm existența unui fișier. În linia a treia tipărim rezultatul testului anterior (vă amintiți că variabila $? conține rezultatul ultimei comenzi executate, și că 0 reprezintă succes).

Pentru conveniența utilizatorului, există o comandă cu numele [ (paranteză dreaptă deschisă), care este echivalentă cu test. Astfel în loc de: test -f fisier putem scrie: [ -f fisier ]. Observați că paranteza trebuie închisă, și în plus trebuie lăsate spații în jurul parantezelor.

if:
Putem face tot felul de teste, dar cum putem beneficia de rezultatul lor? Folosind instrucțiunea de execuție condițională, ca în următorul exemplu:

% DIR=src; FILE=Makefile
% if [ -f $DIR/$FILE ]; then echo "Exista"; else echo "Nu exista"; fi
Exista
%

Aici am combinat două comenzi: if și test. Comanda internă if este urmată de o altă comandă. Dacă cea din urmă se termină cu succes (returnează 0), atunci comanda de după then este executată; altfel ramura de după else. Nu-l uitați pe fi la sfîrșit.

Cînd compunem un script putem separa părțile unui if pe mai multe linii. Iată un exemplu de script:

#!/bin/sh

# acest script tipareste fisierul argument, sau un mesaj de 
# eroare daca acesta nu exista

if [ $# != 1 ]; then
        echo "Imi trebuie un argument"
        exit 1
elif [ -f $1 ]; then
        cat $1
else
        echo "$1 nu exista"
fi

Acesta este deja un script interesant, care introduce cîteva elemente noi:

Dacă punem script-ul anterior în fișierul numit ``arata.sh'', putem să-l executăm cu secvența de comenzi:

% chmod a+x arata.sh
% ./arata.sh
Imi trebuie un argument
% ./arata.sh bibi
bibi nu exista
% ./arata.sh src/hello.c
#include <stdio.h>

main() {
    printf("Hello world\n");
    return 0;
}
%

Comanda chmod a+x arata.sh are drept efect de a face fișierul arata.sh executabil în așa fel încît oricine să-l poată executa (CHange the MODe for All to add (+) eXecutable).

Observați că pornim script-ul în execuție indicînd directorul unde se află (./arata.sh). Dacă un program nu este indicat exact prin directorul său, shell-ul folosește o variabilă numită PATH care indică o serie de directoare (separate cu semnul două puncte) unde acest script trebuie căutat. Încercați și echo $PATH.

read: citește cuvinte într-una sau mai multe variabile:

% read a b c
tastez multe cuvinte sa vedem cum merg
% echo ":$a:$b:$c:"
:tastez:multe:cuvinte sa vedem cum merg:
%

Read citește de la intrare o linie (linia care apare după comandă a fost introdusă de la tastatură, și nu tipărită de shell); apoi read sparge linia în cuvinte separate de spații, și fiecare cuvînt este atribuit unei variabile; ultima variabilă primește restul liniei pînă la sfîrșit.

for:
permite să executăm un ciclu într-o listă de cuvinte, astfel:

for i in hello goodbye ok; do
    for j in .o .c~; do
        if [ -f $i$j ]; then 
            echo "Sterg $i$j"
            rm $i$j
        fi
    done
done

Acest program caută fișierele hello, goodbye sau ok urmate de sufixul .o sau .c~ (ceea ce în genere înseamnă fișier obiect, respectiv o versiune veche a unui fișier C) și le șterge. Observați două bucle imbricate.

Înainte de a prezenta alte cîteva comenzi interne foarte utile, vom discuta sumar despre alte funcțiuni foarte importante ale shell-ului.

Expansiunea expresiilor regulate

O trăsătură extrem de puternică a shell-ului este că poate expanda expresii regulate care descriu succint nume de fișiere. Pentru că am scris de curînd un articol despre acest subiect (în PC Report din aprilie 2000), voi fi foarte sumar aici, prezentînd doar cîteva exemple:

Expresie Semnificație
a* Toate fișierele al căror nume începe cu a
*.c Toate fișierele al căror nume se termină cu .c
.??* Toate fișierele al căror nume începe cu punct și conține cel puțin 3 caractere
*/src/*.c Toate fișierele terminate cu .c aflate în subdirectorul src al oricărui director din directorul curent.
*[0-3]* Toate fișierele care conțin una din cifrele 0,1,2 sau 3 în nume
{ab,cd}.{c,h} fișierele existente în mulțimea ab.c, ab.h, cd.c, cd.h

Iată și un program care folosește expresiile regulate:

#!/bin/sh

ACEST_SCRIPT=$0

for i in *.c; do
        if [ -f $i ]; then echo $i; fi
done

for i in *; do
        if [ -d $i ]; then 
                cd $i; $ACEST_SCRIPT; cd ..
        fi
done

Dacă script-ul anterior este în undeva într-un director indicat de variabila PATH, atunci execuția lui va cauza tipărirea tuturor fișierelor C dintr-o ierarhie de directoare. Observați cum script-ul folosește variabila $0, care conține propriul lui nume, pentru a se auto-executa în toate sub-directoarele.

Comenzi paralele

Am văzut că putem folosi semnul punct-și-virgulă pentru a lansa în execuție mai multe comenzi succesiv. Dacă vrem să lansăm în execuție două comenzi simultan, punem între ele semnul &:

% netscape & xterm &
%

Această comandă va lansa în execuție programul netscape, și fără a aștepta terminarea sa va lansa și programul xterm. Semnul & de la sfîrșit îi spune shell-ului să nu aștepte nici terminarea lui xterm, ci să ofere un nou prompt.

Se pot de asemenea folosi semnele && și ||. Dacă scriem c1 && c2, înseamnă că vrem ca c2 să se execute dacă și numai dacă c1 se termină cu succes. Dimpotrivă, c1 || c2 înseamnă că c2 se execută dacă și numai dacă c1 a eșuat.

Redirectare și conducte (pipes)

Cînd shell-ul lansează un proces are posibilitatea de a cupla intrarea și ieșirea acestuia în diferite moduri. În Unix fiecare proces la pornire are 3 canale de comunicație deschise: un canal de la care primește date (intrarea), unul la care scrie date (ieșirea) și unul la care scrie erori. Pentru shell în mod normal aceste canale sunt cuplate la tastatura și respectiv ecran. Ele pot fi însă redirecționate spre fișiere foarte simplu:

% ls >lista
% cat lista
Mail  bin  data  lib  man  src  tmp
% 

Semnul > este urmat de un fișier; ieșirea comenzii executate este ``depusă'' atunci în acel fișier.

Mai spectaculos este că putem cupla ieșirea unui proces la intrarea altuia folosind un singur caracter, scris | și citit ``țeavă'' (pipe). Pentru a afla de exemplu cîte fișiere sunt în directorul curent, putem folosi:

% ls | wc -w
7
% ls >lista
% wc -w lista
7
% rm lista

wc -w număra doar cuvintele (words). Pentru a număra fișierele putem fie trimite rezultatul lui ls într-un fișier lista folosind redirectare, după care putem număra cuvintele, sau, mult mai eficient și rapid, putem cupla ieșirea lui ls la intrarea lui wc cu o țeavă, ca în prima linie.

Un idiom ades folosit este de a redirecta erorile spre un dispozitiv fictiv numit /dev/null, care este găleata de gunoi pe un sistem Unix:

% wc -l *.c */*.c 2>/dev/null

Această comandă numără liniile din toate fișierele care se termină cu .c în directorul curent și în toate subdirectoarele sale. Dacă unele din fișiere sunt însă ilizibile (de exemplu pentru că avem directoare al căror nume se termină cu .c), atunci wc va tipări niște erori, care însă vor fi trimise spre ``null'' datorită redirectării canalului de eroare, designat de 2>.

Accentul grav

O facilitate extrem de puternică a shell-ului este oferită de semnele de accent grav `. Orice este cuprins între semne de accent grav este executat ca o comandă; rezultatul acelei comenzi devine un șir de caractere, care înlocuiește comanda între accente grave.

% wc -w src/hello.c
9 src/hello.c
% a=`wc -w src/hello.c`
% echo $a
9 src/hello.c
%

Alte comenzi

while:
ne permite să ciclăm în mod repetat. În script-ul de mai jos, while se execută atîta timp cît comanda read funcționează cu succes; astfel citim fiecare linie din fișierul text-de-prelucrat. Tipărim apoi numai liniile care au mai mult de 10 cuvinte.

#!/bin/sh

cat text-de-prelucrat | while read linie; do
    cuvinte=`echo $linie | wc -w`
    if [ $cuvinte -ge 10 ]; then
        echo $linie
    fi
done

expr:
permite shell-ului să opereze și cu numere. Am văzut că singurul tip de date al shell-ului sunt șirurile de caractere. Cum putem atunci face ceva aritmetică? Comanda expr este urmată de o expresie aritmetica a cărei valoare o tipărește la ieșire. E o metodă un pic cam complicată pentru a evalua simple expresii aritmetice, dar dacă faceți puține calcule, e foarte practică. Trebuie să fiți atenți să separați toate argumentele lui expr cu spații.

#!/bin/sh

contor=0
while [ $contor -le 100 ]; do
        echo $contor
        contor=`expr $contor + 1`
done

Încheiere

Aici pun capăt acestei incursiuni blitz în programarea în shell. Sper să vă fi pus la-ndemînă suficiente scule ca să puteți să scrieți programe utile. Shell-urile moderne sunt foarte sofisticate, oferă o sumedenie de facilități suplimentare, și pot fi configurate și adaptate în mii de moduri. Dar 90% din problemele întîlnite în practică pot fi rezolvate folosind cele 10% din limbaj pe care le-am prezentat.

Oricum, nu puteți să vă perfecționați decît folosind limbajul; m-aș bucura dacă v-am dat motive să încercați.

Alte surse de informație



Note

... părinte1
Acesta este procesul care l-a pornit în execuție, în cazul nostru shell-ul însuși.