Bash скрипте

Извор: ВикиЕТФ

UNIX се од самог почетка развијао у правцу једноставних и малих команди. Команде које саме по себи обављају тривијалне задатке, а њиховим простим комбиновањем можете да решавате комплексне проблеме. Један од механизама комбиновања команди је свакако језик командног окружења. Поред bash-а постоје и многи други као што су tcsh, ksh, csh, sh,...

Sh је више стандард него командно окружење, а у већини Линукс система извршава bash програм. POSIX је стандард за већину ствари у Unix-оликим системима па и за командно окружење, а sh je његово виђење командног окружења. Takoђе POSIX прописује постојање засебних програма за уграђене команде као што су test, pwd,... па немојте да вас то буни ако налетите на њих.

У тексту су јасно означене уграђене команде јер оне су карактеристика самог shell-а и не постоје ван њега. То ће вам бити битно сутра када будете учили скрипт језик неког трећег окружења.

Садржај

Приступ документацији

Већина програма на UNIX-оликим системима даје приступ својој краткој документацији помоћу команде man (man ime-programa). Уколико постоји, проширеној документацији се може приступити командом info. За опис уграђених команди самог shell-а треба користити наредбу "help".

Коментари и sha-bang

Иако коментари и sha-bang нису повезани својом функцијом, али деле сличност тиме што обоје почињу са тарабом, и због тога их овде описујем заједно.

$ echo "Ovde # ne pocinje komentar."
Ovde # ne pocinje komentar.
$ echo 'Ovde # ne pocinje komentar.'
Ovde # ne pocinje komentar.
$ echo Ovde \# ne pocinje komentar.
Ovde # ne pocinje komentar.
$ echo E # ovde pocinje komentar.
E

Код прве и друге команде shell окружење не види тарабу јер смо је окружили знацима навода (наводници).

У трећој echo команди користили смо \ знак да би смо га escape-овали и спречили shell окружење да га тумачи.

#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/usr/awk -f

Ако команди file дамо као унос "#!/bin/sh" он ће нам дати тип фајла:

$ echo '#!/bin/sh' | file -
/dev/stdin: Bourne shell script text executable

Знак минус каже file команди да прима унос са стандардног улаза, такође морамо да ставимо под наводнике знакове #!, зато што имају специјално значење за shell.

У интерактивном коришћењу shell-а знак # се тумачи као коментар, ако је (inter‐active_comments намештен са уграђеном командом shopt). Знак узвика и string иза њега активирају history механизам shell-а за тражењем команди по задатом string-у.

Преусмеравање

Сваки извршени програм на почетку има три подразумевана тока комуникације са околином - стандардни улаз(stdin), стандардни излаз(stdout) и стандардни ток грешке(stderr). Дескриптори ових токова су 0, 1 и 2.

Да бисмо преусмерили излаз команде користимо знак >, a испред додајемо двојку(без размака!) ако желимо да преусмеримо само ток грешке.

$ fortune > test.txt
$ ls -E 2> error.txt

Понекад је потребно да команде употпуности ућуткамо, и да читав њихов излаз шаљемо на /dev/null што је линуксов еквивалент црној рупи. Постоје два начина за слање стандардног излаза и тока грешке у један фајл. У првом шаљемо стандардни излаз у фајл( > fajl.txt ) и онда додајемо ток грешке на стандардни излаз( > fajl.txt 2>&1). А постоји и скраћеница која то ради одједном (&> fajl.txt ).

$ ls -E >/dev/null 2>&1
$ ls -E  &>/dev/null

Да бисмо само додали садржај у фајл користимо >> оператор. Сва прича око преусмеравања стандардног излаза и тока грешке је овде иста.

$ fortune >> fortune.txt
$ ls -E 2>> moje_greske.txt
$ ls -E &>> /dev/null
$ ls -E >> /dev/null 2>>&

Стандардни улаз неке команде такође можемо да изменимо са оператором <.

$ sort < podaci.csv > sortirani_podaci.csv

Ако желимо да нека команда прими стандардни излаз претходне команде као свој стандардни улаз користићемо pipe оператор("|") између њих.

$ ps aux | grep bash
1000      7229  0.0  0.3   5760  3320 pts/0    Ss   15:56   0:00 bash
1000      7611  0.0  0.3   5760  3284 pts/1    Ss   16:05   0:00 bash
1000      7965  0.0  0.3   5760  3292 pts/2    Ss+  16:12   0:00 bash
1000      8256  0.0  0.3   5756  3304 pts/3    Ss+  16:18   0:00 bash
1000      9631  0.0  0.3   5756  3292 pts/4    Ss+  16:51   0:00 bash

Наводници

Shell окружењe прво ради обраду командне линије а онда ако смо позвали неки програм, онда му прослеђује параметре. Ако смо реферисали неку променљиву (променљиве) замениће нпр. $promenljiva са њеном вредношћу, осим ако не назначимо на неки начин shell-у да заобиђе карактер $ који за њега има посебно значење. Имамо три начина да ово урадимо, да ставимо под једноструке, двоструке наводнике или да escape-ујемо знакове појединачно.

Они су корисни ако хоћемо да штампамо вредност променљивих пошто не спречава shell да реферише променљиве. Такође нам дозвољавају да чувамо space знакове од гутања.

$ echo ej                    gde si              tu sam
ej gde si tu sam

$ echo "ej                    gde si              tu sam"
ej                    gde si              tu sam

У првом случају нисмо ставили комплетан стринг под наводнике, па је shell радио оно што и увек ради. Поделио је све параметре у речи, оне су одвојене са размаком, и онда их је проследио команди. У другом случају смо рекли shell-у да читав стринг рачуна као један параметар. Ово је корисно ако имамо размак у имену фајла, неке специјалне знакове у њему,...

$ echo "PATH = $PATH"
PATH = /usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/home/stanislav/bin/

$ PROMENLJIVA=" ovo je         uvrnuta promenljiva"
$ echo $PROMENLJIVA
ovo je uvrnuta promenljiva

$ echo "$PROMENLJIVA"
 ovo je         uvrnuta promenljiva

Овај пример је интересантан зато што показује да shell прво реферише променљиву и да на крају дели на речи параметре команде и врши прослеђивање. Да бисте видели тачан редослед и комплетан списак како shell тумачи командну линију погледајте страницу упутства(man).

Када стављамо под једноструке све што се налази између њих чува своју вредност, ту наравно морамо да изузмемо ' знак.

$ echo '$variable $IPS'
$variable $IPS

Иако можда постоје две променљиве IPS и variable shell неће заменити њихову вредност зато што смо ставили комплетан стринг под једноструке наводнике.

Понекад желимо да escape-ујемо појединачне знакове, а то је и један од начина да приказујемо дупле и једноструке наводнике, као и друге знакове који имају за shell посебно значење.

$ echo "PROMENLJIVA = \$PROMENLJIVA"
PROMENLJIVA = $PROMENLJIVA

Иако се променљива налази унутар двоструких наводника shell неће приказати њену вредност зато што смо escape-овали долар знак.

$ echo "I RMS rece: \"GPLv3\", i bi GPLv3"
I RMS rece: "GPLv3", i bi GPLv3

Овде видимо да можемо такође да уз помоћ escape-овања знакова да спречимо shell да затвори двоструке наводнике.

$ echo Koliko jednostrukih navodnika vidis? \'\'\'\'\
Koliko jednostrukih navodnika vidis? '''''
$ echo "Koliko jednostrukih navodnika vidis? '''''"
Koliko jednostrukih navodnika vidis? '''''

У првом случају смо escape-овали једноструке наводнике док смо их у другом ставили под двоструке. Можемо такође да радимо и обрнуто, да стављамо двоструке под једноструке наводнике ако треба да их прикажемо у некој поруци. Овај пример је ту да покаже сличност између стављања стринга под наводнике и escape-овање појединачних знакова.

У bash-у такође имамо -e опцију за уграђену команду echo скојом можемо да користимо escape секвенце као што су \n, \t,...

$ echo -e "\nVidis me\n"

Vidis me

$ echo -e "\nVidis\tme\n"

Vidis   me

Променљиве

Bash има могућност дефинисања променљивих које се касније могу користити у командама. За разлику од класичних програмских језика Bash смешта све своје променљиве као стринг, а када користимо неку променљиву као број он врши имплицитну конверзију. Такође за разлику од C/C++ код којих је неопходно дефинисање променљиве пре коришћења, у Bash-у се она дефинише при првој употреби.

Област важења променљиве је другачији него у програмским језицима. Уместо стандардног концепта да када дефинишемо променљиву она важи надаље и у свим програмима који се извршавају, имамо концепт окружења. Окружење наслеђују сви процеси које извршава командно окружење, и да би смо убацили нову променљиву у табелу окружења користимо уграђену команду "export".

$ Promenljiva="nesto"
$ echo $Promenljiva
nesto
$ bash -c 'echo $Promenljiva'

Овде видимо да променљива се не преноси у окружење "bash" команде коју извршавамо.

$ export Promenljiva
$ bash -c 'echo $Promenljiva'
nesto

Овде смо илустровали коришћење команде "export" и њену функцију. Ово је битно ако желимо да мењамо понашање неког програма преко променљива у окружењу.

$ echo $PROMENLJIVA
nesto
$ export PROMENLJIVA
$ bash -c "PROMENLJIVA=nova_vrednost"
$ echo $PROMENLJIVA
nesto

Као што видимо за разлику од глобалних променљивих промене вредности експортованих променљива у изведеним процесима не утичу на вредност исте променљиве у родитељским процесима.

$ PROMENLJIVA="Evo sadrzaja promenljive."
$ echo $PROMENLJIVA
Evo sadrzaja promenljive.
$ echo $PROMENLJIVA_DODATAK_

$ echo ${PROMENLJIVA}_DODATAK_
echo Evo sadrzaja promenljive._DODATAK_

Овде смо видели како да додамо стринг на вредност променљиве, а тако да не збунимо shell. Користили смо дужи облик реферисања на променљиву у витичастим заградама, па смо могли да додајемо садржај одмах после променљиве.

Низове дефинишете и користите на следећи начин:

$ NIZ[1]="Prvi clan niza"
$ NIZ[2]="Drugi clan niza"
$ echo ${NIZ[2]}
Drugi clan niza
$ echo ${NIZ[*]}
Prvi clan niza Drugi clan niza

Додела је иста као и у претходним примерима само што задајемо индекс у низу где се чува вредност. Када реферишемо чланове низа користимо дужи облик са витичастим заградама. Видели смо и начин како да добијемо вредност свих чланова низа одједном користећи звезду уместо индекса. Битно је напоменути да ова звезда нема никакве везе са глобирањем, о коме ћемо причати касније.

На сваку програм који се извршава на Unix-оликом оперативном систему може се утицати на више начина. Најчешћи су конфигурациони фајлови и опције које прослеђујемо команди, али такође можемо мењати извршавање команде у зависности од неке променљиве у окружењу. Променљиве које утичу на извршавање неког програма можете наћи у ENVIROMENT секцији његове ман странице.

Битније променљиве окружења:

Резултати команди и контролне структуре

Ако сте некада запитали ко прихвата резултат који враћа main функција програма, тај резултат се прихвата у shell-у. Нула означава да је команда успешно завршена, а број различит од нуле oзначава неуспешан завршетак. Овај податак се даље може користити за коришћење у контролним структурама у Bash-у.

Ако желимо да извршимо команду која враћа тачну вредност(0) извршићемо уграђену команду true(/false). Ове команде су корисне када хоћемо да извршавамо бесконачне петље.

На крају сваке скрипте можемо да вратимо вредност као што то радимо у осталим програмским језицима, само што овде уместо return који смо користили у C-у користимо exit уграђену команду заједно са вредношћу која се враћа, ако нисмо експлицитно задали вредности користи се излазна вредност последње команде.

if grep $LOGNAME /etc/passwd &> /dev/null
then
  echo "Ja sam korisnik na ovom racunaru"
  exit 0 #true
fi

Овде видите да смо уместо услова као у другим програмским језицима користили вредност извршавање команде grep. Ово је само илустритиван пример за то да све команде враћају вредност, и да можемо да их користимо у контролним структурама.

Уграђена команда test

Команда test служи за поређење података у Bash-у али и поред тога можемо са њом проверавати својства фајлова.

Такође можемо уместо позивања као програма test можемо да користимо скраћеницу [.

$ test -d /home/ && echo /home/ jeste direktorijum
$ [ -d /home ] &&   echo /home/ jeste direktorijum

Ово је пример коришћења скраћеног начина позивања уграђене команде test. Битно је нагласити да у скраћеном облику мора постојати размак са обе стране унутар услова.

У наставку следе неке најкоришћенији оператори уграђене команде test.

Оператор Скраћеница Еквивалент у C-у
-lt (less than) <
-gt (greater than) >
-eq (equal) ==
-ge (is greater than or equal to) >=
-le (is less than or equal to) <=
-ne (is not equal to)  !=
-z string је празан, то јест дужина му је нула
-n string није NULL
= једнакост string-ова
 != неједнакост string-ова
-a Логичко и
-o Логичко или
 ! Негација
-f Постоји фајл и упитању је обичан фајл
-s Фајл није празан
-r Фајл је читљив од стране корисника који извршава скрипту
-w Можемо да уписујемо у фајл
-x Можемо да извршавамо фајл
-d Тип фајла је директоријум
-h Фајл је симболички линк
$ BASHRC="/home/stanislav/.bashrc" test -f $BASHRC -a -r $BASHRC && cat /home/stanislav/.bashrc

Овде сам и поред примера коришћења test-а показао сам вам како можете да промените неку променљиву само за тај програм. Испред програма наводите редом променљиве које желите да промените за тај програм и одвајајте их размацима. Морате само водите рачуна да су окружења cat програма и уграђене команде test различита, па сам због тога cat-у морао да дам пуну путању до фајла. Тачније cat преузима окружење из текућег shell-а.

Уграђена команда let

Када желимо да израчунамо неки аритметички израз или да поредимо целе бројеве користићемо уграђену команду let. Она поседује такође операторе за поређење и додељивање. Постоје два начина да позивамо команду "let", као команду или са синтаксном скраћеницом са заградама.

Листа оператора, имају исто значење као у стандардним програмским језицима: ++, --, +, -, !(логичка негација), ~(негација над битовима), ~~ (експонент), *, /, %, <<, >>, ==, !=, &, |, ^, &&, ||,...

$ var=0
$ let var++
$ echo $var
1

Видимо да при коришћењу let уграђене команде не морамо да наводимо $ знак. То је због тога што let прима или нумеричке константе или променљиве које садрже вредност неку нумеричку вредност.

$ ((var +=var))
$ echo $var
2

Ово је слично томе као што имамо скраћени облик за уграђену команду test([]), само што не морамо да одвајамо размацима оно што се налази унутар заграда.

$ ((var= ~1 & 131))
$ echo $var
130

Овде смо прво користили негацију над јединицом, потом смо радили & операцију над битовима децималног броја 131. И наравно добили смо број 130.

Листе

komanda1 && komanda2 # Izvrsava komandu 2 samo ako je komanda1 bila uspesna.
komanda1 || komanda2 # Izvrsava komandu 2 samo ako je komanda1 bila neuspesna.

Листе су згодан алат када Вам је потребно да се команде извршавају у зависности од предходне а немате потребе за комплексним if контролном структуром.

$ test -x /bin/lsx && echo Bila je uspesna || echo Bila je neuspesna
Bila je neuspesna

У претходном примеру прво извршавамо команду тест, а онда се питамо да ли је она тачна, а пошто није не извршавамо следећу echo команду, а || услов се пита да ли је претходна команда нетачна, а пошто јесте овде се извршава команда echo и штампа се вредност стринга "Bila je neuspesna".

Ако желимо да инсталирамо програм из изворног кода можемо да користимо следећу конструкцију:

$ ./configure && make && make install || echo "Instalacija je bila neuspesna, zao mi je"

Да бисмо само секвенцијално извршавали команде користимо сепаратор ";".

$ echo "====DATE===="; date; echo "====END=OF=DATE===="
====DATE====
Wed Apr  9 12:13:56 CEST 2008
====END=OF=DATE====
$ echo "====UPTIME===="; uptime; echo "=END="
====UPTIME====
12:14:23 up  3:53,  2 users,  load average: 0.00, 0.07, 0.03
=END=

if-then-else

Дужи облик претходне структуре би био:

if komanda1
then
	 komanda2
else
	 komanda3
fi

Примери:

if [ -f /bin/ls -a -x /bin/ls ]
then
    echo "ls je fajl i izvrsiv je";
else
    echo "nije fajl ili nije izvrsiv";
fi

Пример простог "~/.bashrc" фајла.

 #!/bin/bash

#file: ~/.bashrc
#Ovo je fajl koji bash cita pri inicijalizaciji
#interaktivnog shell-a koji nije interaktivan, pre toga se cita
#/etc/bash.bashrc koji sadrzi podesavanja za bash na globalnom nivou.

#Ako se ne izvrsava interaktivno izadji iz skripte
#bez odzivnika nemamo interaktivni shell
[ -z "$PS1" ] && return

#Ako postoji fajl ~/.bash_aliases onda ga source-uj (izvrsi komande 
#koje se nalaze u njemu) u tekucem shell-u
if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
    #isto kao i
    # source ~/.bash_aliases
fi

#Ako je podrazumevani editor vim, onda mozemo da ga koristimo za edit-ovanje komandi
#samo pritisnimo <ESC> imacemo stare dobre komande iz vi editor-a
if [ $EDITOR = "vim" ]; then
    #ovo je jos jedan nacin da menjamo ponasanje shell-a
    #pomocu njegove ugradjene komande set
    #ako zelite da vidite vise o njoj vidite u help set
    set -o vi
fi

#Dodavanje naseg direktorijuma u PATH promenljivu, tu mozemo da drzimo skripte
PATH="$PATH:/home/stanislav/bin/"
#Razni aliasi za komande
#umesto da se da uvek kucam ls -l, sad mogu da kucam samo ll komandu
alias ll='ls -l'
alias la='ls -A'
alias l='ls -CF'

for-do

Уместо класичне for петље у Bash-у ћемо користити foreach тип петље.

for i in *
do
    echo Naziv datoteke je: $i
done

Ово је један од начина да направимо листинг директоријума.

for i in `seq 0 10|sort -gr`
do
    sleep 1
    echo $i
done

Један од начина да бројимо од десет до нула. Користимо опције "-g" за нумеричко a "-r" за обрнуто сортирање. Такође смо користили програм "seq" који штампа бројеве са подразумеваним инкрементом 1.

Ако нпр. желимо да направимо леп листинг директоријума који се налазе у PATH променљивој то можемо да урадимо на сл. начин:

#!/bin/bash

IFS_old=$IFS
IFS=":" #umesto da razdvajamo reci sa <SPACE> znakom, radicemo to sa :
n=0
for i in $PATH
do
    echo "Dir $n: $i"
    ((n++))
done
IFS=$IFS_old

Овде смо мењали IFS променљиву која каже shell-у како да раздваја речи. Променили смо је у : пошто су поља у PATH променљиви тако раздвојена.

while-do

while komanda1
do
	komanda2
done

Следећи пример штампа све параметре команде. Овде користимо уграђену команду shift која помера позиционе параметре за један у лево ($1 <--- $2, $2 <--- $3, $3 <--- $4, итд.).

#!/bin/bash

while [ ! -z "$1" ] #mozemo da koristimo i 'until [ -z "$1" ]'
do
  echo -n "$1 "
  shift
done

echo               # extra ENTER

exit 0

case

Case наредба се не разликује много од својих рођака у другим програмским језицима. Само што пореди реч са узорком који може бити састављен од џокера, ? који замењује било који један знак, или * који замењује колико год знакова,... Ови џокери такође немају везе са глобирањем које ћете радити на крају.

case rec in
 	uzorak1)
		komanda1
		komanda2
	;;
	uzorak2)
		komanda3
	;;
	*) #poklapa se sa bilo kojim uzorkom
		komanda4	
	;;
esac

Користан је за обраду опција које задајемо скрипти, ево једног примера које сам извадио из скрипте за прављење *.deb и скратио сам га.

#!/bin/bash

#Default values
progname="`basename $0`" #sa basename dobijamo samo ime programa, bez putanje do njega
desc=
noclean=false

# $# nam kaze koliko parametara trenutno ima program
while [ $# != 0 ]
do
         #za parametre koje primaju vrednosti ovako vadimo prvo slovo
         #i dodeljujemo string promenljivoj $value
        value="`echo x\"$1\" | sed -e 's/^x-.//'`"
        case "$1" in
        -h|--help)
                echo "help message: $progname [parameters]";
				exit 0 ;;
        --version)
                echo "Version";
				exit 0 ;;
        -nc)	noclean=true ;
				;;
        -v)
                echo "Value = $2";
				shift;;
        *)	
                # sa >&2 saljemo izlaz na stderr, sto i trebamo jer je upitanju greska
                echo >&2 "$progname: unknown option or argument $1"
                echo "help message"; exit 2 ;;
        esac
        shift
done

Глобирање

Глобирање нам дозвољава да команди пренесемо више фајлова путем џокера. Shell је задужен за њихово распарчавање и прослеђивање програму. Ако кажемо ls * команда ls не прима као параметар * знак, већ га shell распарчава и замењује свим фајловима у директоријуму. Тако да команда ls не може знати да је позвана са џокером, то води до једног честог проблема са командом rm и брисања погрешних фајлова (можете више да видите о овој теми у UGH).

$ echo /b*
/bin /boot
$ echo /b[io]*
/bin /boot
$ echo /b[io]{n,o}?
/b[io]n? /boot

Последњи пример је један од интересантнијих, као што видимо када командно окружење неможе да пронађе фајл који се поклапа са секвенцом џокера онда их штампа као такве. Али по томе знамо и да командно окружење када парсира {n,o}, онда их дели на "/b[io]n?" и "/b[io]o?". Ово нам такође говори и о редоследу којим парсира, али ако вас баш интересује погледајте више на bash ман страни.

$ find /usr/lib/ -name *.so
find: paths must precede expression: libanl.so
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

Ова команда ће радити како треба само ако немамо фајл који се завршава са ".so", у супротном ћемо добити грешку као у претходном примеру.

$ find / -name "*.so"

Ово је оно што желимо да добијемо. Ово је само пример како нам наводници могу олакшати живот ако знамо да их користимо.

Везе

Спољашње везе

Личне алатке
Именски простори
Варијанте
Акције
Навигација
Алатке