Skoči na vsebino

Uporaba lupine

V tem razdelku si ogledamo osnovne programe in konstrukte, s katerimi jih kombiniramo. Večina programov ima standardni priročnik, ki si ga lahko ogledamo na spletu ali v konzoli s programom man. V literaturi pogosto najdemo zapis oblike cat(1), ki se sklicuje na priročnik za program cat v prvem razdelku. Ogledamo si ga z

$ man 1 cat
man 1 cat

pri čemer lahko številko razdelka ponavadi tudi izpustimo. Priročniki za kompleksnejše programe pogosto vsebujejo primere uporabe, ki pa so tipično skriti nekje proti koncu.

Standardni tokovi

Vsak proces ima za komunikacijo z uporabnikom in drugimi procesi na voljo tri standardne tokove (angl. streams) s pripadajočimi številkami:

  • 0: stdin (angl. standard input),
  • 1: stdout (angl. standard output) in
  • 2: stderr (angl. standard error).

Program echo enostavno izpiše svoje argumente na standardni izhod:

$ echo "Bird is the word but words are not birds."
Bird is the word but words are not birds.
echo "Bird is the word but words are not birds."

Program cat brez argumentov pa bere vrstice s standardnega vhoda in jih sproti izpisuje na standardni izhod (povedano drugače, podvoji vse, kar vtipkamo). Konec vhoda označimo tako, da pritisnemo Ctrl+D. Namesto tega lahko proces prekinemo tudi s Ctrl+C.

$ cat
prva vrstica
prva vrstica
druga vrstica
druga vrstica
cat
prva vrstica
druga vrstica

Izhod stderr se uporablja za poročanje o napakah. Če recimo programu cat naročimo, da izpiše vsebino neobstoječe datoteke, bo sporočilo o napaki izpisal na stderr. To ni očitno iz izpisa lupine, saj tako stderr kot stdout izpisujeta na enak način.

$ cat ta-datoteka-ne-obstaja.xtx
cat: ta-datoteka-ne-obstaja.xtx: No such file or directory
cat ta-datoteka-ne-obstaja.xtx

Preusmeritve

Standardni izhod programa lahko preusmerimo v izbrano datoteko s pomočjo operatorja > (angl. output redirection):

$ echo "Bird is the word but words are not birds." > stavek.txt
echo "Bird is the word but words are not birds." > stavek.txt

Tu program echo izpiše besedilo na svoj standardni izhod, ki ga operator > usmeri v datoteko. Zato se nam na zaslonu ne izpiše nič, v trenutnem imeniku pa dobimo novo datoteko stavek.txt. Če datoteka s tem imenom že obstaja, jo lupina prepiše; namesto > lahko uporabimo operator >>, da izpis zapišemo na konec obstoječe datoteke.

Nasprotno lahko standardni vhod programa preberemo iz datoteke z operatorjem < (angl. input redirection). S programom wc (angl. word count) preštejmo vse znake, besede in vrstice v datoteki stavek.txt:

$ wc < stavek.txt
1 9 42
wc < stavek.txt

Omenimo še, da zna večina programov poleg standardnega vhoda datoteke uporabljati neposredno, če jih podamo kot argumente. Podobno kot zgoraj bi lahko dosegli z

$ wc stavek.txt
1 9 42 stavek.txt
wc stavek.txt

Medtem ko je v prejšnjem primeru datoteko odprla lupina, jo tukaj odpre wc. Razlika običajno ni bistvena, a jo je dobro poznati.

Cevovodi

Standardne tokove programov lahko kombiniramo tudi neposredno, s čimer se izognemo vmesnim datotekam. Prejšnja primera združimo tako, da z operatorjem | (angl. pipe) zgradimo cevovod (angl. pipeline) med standardnim izhodom programa echo in standardnim vhodom programa wc:

$ echo "Bird is the word but words are not birds." | wc
1 9 42
echo "Bird is the word but words are not birds." | wc

Cevovod lahko vsebuje poljubno dolgo zaporedje programov. V naslednjem primeru z ls izpišemo imena vseh datotek v imeniku /bin, z grep obdržimo le tista imena, ki vsebujejo python, in jih preštejemo:

$ ls /bin | grep python | wc --lines
13
ls /bin | grep python | wc --lines

Zamenjava ukazov

Oglejmo si še, kako standardni izhod enega programa uporabimo kot argument drugemu programu. Program date izpiše trenutni čas:

$ date
Wed May  5 17:22:35 CEST 2021
date

Napišimo ukaz, ki izpis programa date uporabi kot argument programu echo. Za to uporabimo operator $(…), ki ga lupina zamenja z izpisom ukaza v oklepajih:

$ echo "Trenutni čas: $(date)"
Trenutni čas: Wed May  5 17:22:35 CEST 2021
echo "Trenutni čas: $(date)"

Spoznali smo tri osnovne gradnike za kombiniranje programov v ukazni lupini. Izpis programa lahko

  • z > preusmerimo v datoteko,
  • z | preusmerimo v drug program, ali
  • z $(…) uporabimo kot del ukaza.

Vse tri možnosti pogosto uporabljamo pri delu z ukazno vrstico; več o njih izvemo v bash(1) v razdelkih REDIRECTION, Pipelines in Command Substitution. V nadaljevanju si bomo ogledali nekaj funkcionalnosti lupine, ki nam olajšajo predvsem pisanje skript – seveda pa lahko vse uporabljamo tudi iz ukazne vrstice.

Spremenljivke

Poljubno vrednost lahko shranimo v spremenljivko in jo uporabimo kasneje:

$ x=42
$ echo "Vrednost spremenljivke x je $x"
Vrednost spremenljivke x je 42
x=42
echo "Vrednost spremenljivke x je $x"

Vrednost spremenljivke torej nastavimo z x=… in uporabimo z $x.

Opomba

V x=y okoli simbola = ne sme biti presledkov, sicer bo lupina poskusila pognati program x z argumentoma = in y. Za dostop do vrednosti spremenljivke pa je namesto $spremenljivka bolje pisati "${spremenljivka}", ki se obnaša pravilno tudi, kadar vrednost vsebuje presledke.

Poleg spremenljivk, ki jih nastavimo sami, imamo ponavadi na voljo vrsto okoljskih spremenljivk, ki jih nastavi sistem. Med njimi so informativne spremenljivke, kot sta $HOME in $PWD z domačim in trenutnim imenikom. Druge spremenljivke vplivajo na delovanje posameznih programov. Ena najpomembnejših je $PATH s seznamom imenikov, v katerih lupina išče programe. Trenutne okoljske spremenljivke in njihove vrednosti izpišemo z env:

$ env

PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin

env

Če želimo sami nastaviti okoljsko spremenljivko, uporabimo export. S tem bo spremenljivka postala na voljo tudi programom, ki jih zaganjamo, in ne samo lupini. Poskusimo s spremenljivko LC_TIME spremeniti lokalizacijo izpisa programa date:

$ date
Mon May 10 13:43:58 CEST 2021
$ LC_TIME=sl_SI.utf8
$ date
Mon May 10 13:44:01 CEST 2021
$ export LC_TIME
$ date
pon maj 10 13:44:04 CEST 2021
date
LC_TIME=sl_SI.utf8
date
export LC_TIME
date

Tudi lupina nastavi nekatere spremenljivke. V skriptah nam pridejo prav $0$9, ki hranijo argumente, s katerimi smo skripto poklicali, in $#, ki vsebuje število podanih argumentov. Če želimo prebrati vse argumente podane skripti lahko uporabimo tudi spremenljivko "$@".

Rezultat (angl. exit status) zadnjega ukaza dobimo v $?. Po dogovoru vrednost 0 pomeni uspešno izvajanje, ostale vrednosti pa označujejo razne napake.

$ ls ta-datoteka-ne-obstaja.xtx
ls: cannot access 'ta-datoteka-ne-obstaja.xtx': No such file or directory
$ echo $?
2
ls ta-datoteka-ne-obstaja.xtx
echo $?

Več informacij o vgrajenih spremenljivkah najdemo v sh(1).

Zanka

Ukaz lahko z uporabo zanke for ponovimo za vsak element danega seznama. V splošnem ima obliko

for VAR in A B C… ; do
    …
done

oziroma ekvivalentno v eni vrstici

for VAR in A B C… ; do … ; done

pri čemer je VAR spremenljivka, ki zaporedoma dobiva vrednosti A, B in C. To spremenljivko lahko (kot $VAR) uporabimo v telesu zanke.

$ for i in 1 2 3 ; do
>     echo $i
> done
1
2
3
for i in 1 2 3 ; do
    echo $i
done

Seveda bi v dejanskih skriptah raje uporabili seq z istim učinkom. Če želimo telo zanke ponoviti za vse datoteke v trenutnem imeniku, uporabimo *:

$ for datoteka in * ; do
>     echo V trenutnem imeniku obstaja datoteka $datoteka
> done
for datoteka in * ; do
    echo V trenutnem imeniku obstaja datoteka $datoteka
done

Pogojni stavek

Če želimo ukaz izvesti le pod določenim pogojem, uporabimo stavek if. V splošnem ima obliko

if CMD ; then
    …
else
    …
fi

Vse ukaze med then in else lupina izvede le, če ukaz CMD vrne rezultat 0, v nasprotnem primeru pa izvede ukaze med else in fi. Primer else lahko tudi izpustimo. Recimo:

$ if ls ta-datoteka-ne-obstaja.xtx ; then
>     echo "datoteka obstaja"
> else
>     echo "datoteka ne obstaja"
> fi
ls: cannot access 'ta-datoteka-ne-obstaja.xtx': No such file or directory
datoteka ne obstaja
if ls ta-datoteka-ne-obstaja.xtx ; then
    echo "datoteka obstaja"
else
    echo "datoteka ne obstaja"
fi

Opomba

Lupina izpiše tudi stdout in stderr programov, ki jih uporabimo v pogoju. Če tega nočemo, lahko izpis za te programe preusmerimo v »črno luknjo« z > /dev/null 2> /dev/null. Oba izhoda preusmerimo v isto datoteko z 2>&1 > /dev/null; v večini lupin deluje tudi &> /dev/null.

Pogosto želimo preveriti kakšen drug pogoj. Pomaga nam program test(1), s katerim lahko med drugim primerjamo nize in števila. test ne izpiše ničesar in vrne 0, če je pogoj izpolnjen.

$ x=42
$ test $x -gt 10    # "greater than"
$ echo $?
0
$ test "a" = "b"    # za primerjanje števil uporabimo -eq
$ echo $?
1
x=42
test $x -gt 10    # "greater than"
echo $?
test "a" = "b"    # za primerjanje števil uporabimo -eq
echo $?

V kombinaciji s pogojnim stavkom lahko test uporabimo tako (tu smo cel ukaz zapisali v eni vrstici, zato smo uporabili ;):

$ if test 3 -gt 2 ; then echo "tri je večje od dva" ; fi
tri je večje od dva
if test 3 -gt 2 ; then echo "tri je večje od dva" ; fi

Za lepše branje lahko namesto test … pišemo tudi [ … ]. Pri tem pazimo, da med [ in ] ne izpustimo kakšnega presledka.

$ if [ ! a != a ] ; then echo "ni res, da je a različno od a" ; fi
ni res, da je a različno od a
if [ ! a != a ] ; then echo "ni res, da je a različno od a" ; fi

Skripte

Skripta je datoteka, v kateri naštejemo ukaze, ki jih naj lupina izvaja enega za drugim. Skripto ponavadi začnemo s posebno vrstico, ki operacijskemu sistemu pove, s katerim programom naj jo izvaja. Recimo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/sh
# Znak "#" pomeni komentar: lupina ignorira vse, kar v vrstici sledi temu znaku.
# To velja tudi za #!/bin/sh na začetku datoteke. Znak #! naroči operacijskemu
# sistemu, da za zagon skripte uporabi podani program.

echo Trenutni čas: $(date)
echo Uporabnik: $USER
echo
echo Ime skripte: $0
echo Število argumentov: $#
echo Prvi argument: $1
echo Vsi argumenti: "$@"
echo
echo V okolju je $(env | wc -l) spremenljivk. Prvih deset:
env | head
skripta.sh

Če jo zapišemo v datoteko skripta.sh, jo lahko poženemo z ukazom

$ sh skripta.sh "ena dva"  tri
Trenutni čas: Thu May 6 13:30:53 CEST 2021
Uporabnik: tlazar

Ime skripte: foo.sh
Število argumentov: 2
Prvi argument: ena dva
Vsi argumenti: ena dva tri

V okolju je 67 spremenljivk. Prvih deset:
SHELL=/bin/bash
[…]
sh skripta.sh

Če za datoteko skripta.sh vklopimo dovoljenje za izvajanje, jo lahko poženemo tudi neposredno:

$ chmod +x skripta.sh
$ ./skripta.sh
Trenutni čas: Thu May 6 13:30:53 CEST 2021
[…]
chmod +x skripta.sh
./skripta.sh

Preveri svoje znanje

Vaja

Vaja 02: na podatkih preizkusi zgornje ukaze.