U ovoj lekciji, predstavićemo neke osnove koje se tiču korišćenja bash-a kao scripting jezika zajedno sa više shell alata koji pokrivaju nekoliko najčešćih zadataka koje ćete konstantno izvoditi kroz komandnu liniju.
Shell scripting
Do sada smo vidjeli kako da izvršimo komande u shell-u i kako da ih spojimo. Ipak, u mnogim slučajevima željećete da izvršite seriju komandi i iskoristite kontrolu niza izraza kao što su uslovni iskazi ili petlje.
Shell skripte su sledeći korak u kompleksnosti. Većina shell-ova imaju svoje skripting jezike sa varijablama, kontrolom toka i ličnom sintaksom. Ono što čini shell skripting različitim od ostalih skripting programskih jezika jeste da je optimizovan za izvršavanje zadataka koji se tiču shell-a. Takođe, kreiranje komandnih pajplajna, čuvanje rezultata u fajlovima, i čitanja kroz standardni input su primitive u shell skriptingu, koje je lakše koristiti u odnosu na skripting jezike koji su generalne prirode. Za ovu sekciju mi ćemo se fokusirati na bash skripting, budući da je on najčešći.
Da bi dodali varijble u bash-u, koristite sintaksu
1
foo=bar
1
$foo
1
foo = bar
1
=
1
bar
Stringovi u bash-u mogu biti definisani sa
1
'
1
"
1
'
1
"
1 2 3 4 5
foo=bar echo "$foo" # prints bar echo '$foo' # prints $foo
Kao i sa većinom programskih jezika, bash podržava kontrolu toka sa tehnikama koje uključuju
1
if
1
case
1
while
1
for
1
bash
1 2 3 4
mcd () { mkdir -p "$1" cd "$1" }
Ovdje je
1
$1
- = Naziv skritpte
1
$0
- do
1
$1
- Argumenti skripte.1
$9
je prvi argument i tako dalje.1
$1
- - Svi argumenti
1
$@
- - Broj argumenata
1
$#
- - Vraća kod od prethodne komande
1
$?
- - Indetifikacioni broj procesa (IBP) za trenutnu skriptu
1
$$
- - Čitava poslednja komanda, uključujući argumente. Često se koristi da bi se izvršila komanda koja je prethodno podbacila zbog dozvola koje nedostaju; Možete brzo re-izvršiti komandu sa sudo koristeći
1
!!
1
sudo !!
- - Poslednji argument od poslednje komande. Ako ste u interaktivnom shell-u, možete takođe brzo dobiti ovu vrijednost kucanjem
1
$_
i zatim1
Esc
1
.
Komande će često vratiti output koristeći
1
STDOUT
1
STDERR
Exit kodovi se mogu koristiti da bi se uslovno izvršavala komanda koristeći
1
&&
1
||
1
;
1
true
1
false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
false || echo "Oops, fail" # Oops, fail true || echo "Will not be printed" # true && echo "Things went well" # Things went well false && echo "Will not be printed" # true ; echo "This will always run" # This will always run false ; echo "This will always run" # This will always run
Još jedan poznati obrazac je kada želite da dobijete output komande kao varijablu. Ovo može biti odrađeno zamjenom komandi. Kada god postavite
1
$( CMD )
1
CMD
1
for file in $(ls)
1
ls
1
<( CMD )
1
CMD
1
<()
1
STDIN
1
diff <(ls foo) <(ls bar)
Kako je ovdje prikazano jako puno informacija, hajde da vidimo primjer koji prikazuje primjenu ovih stvari. Izvršiće iteraciju kroz argumente koje smo obezbijedili,
1
grep
1
foobar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#!/bin/bash echo "Starting program at $(date)" # Date will be substituted echo "Running program $0 with $# arguments with pid $$" for file in "$@"; do grep foobar "$file" > /dev/null 2> /dev/null # When pattern is not found, grep has exit status 1 # We redirect STDOUT and STDERR to a null register since we do not care about them if [[ $? -ne 0 ]]; then echo "File $file does not have any foobar, adding one" echo "# foobar" >> "$file" fi done
U poređenju smo testirali da li
1
$?
1
[[ ]]
1
[ ]
1
sh
Kada pokrećemo skriptu, često ćete željete da pružite argumente koji su slični. Bash ima način da se ovo olakša, širenjem izraza provođenjem expanzije imena fajla. Ove tehnike se česte nazivaju shell globbing.
-
Wildcards - Kada god želite da izvedete neku vrstu wildcard podudaranja, možete koristiti
i1
?
da bi se podudarili sa jednim ili više karaktera. Na primer, dati fajlovi1
*
,1
foo
,1
foo1
,1
foo2
i1
foo10
, i komanda1
bar
će izbrisati1
rm foo?
i1
foo1
, dok će1
foo2
izbrisati sve osim1
rm foo*
.1
bar
-
Vitičaste zagrade {} - Kada god imate zajednički substring u seriji komandi, možete koristiti vitičaste zagrade za bash da bi proširili ovo automatski. Ovo je veoma korisno kada pomjerate ili konvertujete fajlove.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
convert image.{png,jpg} # Will expand to convert image.png image.jpg cp /path/to/project/{foo,bar,baz}.sh /newpath # Will expand to cp /path/to/project/foo.sh /path/to/project/bar.sh /path/to/project/baz.sh /newpath # Globbing techniques can also be combined mv *{.py,.sh} folder # Will move all *.py and *.sh files mkdir foo bar # This creates files foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h touch {foo,bar}/{a..h} touch foo/x bar/y # Show differences between files in foo and bar diff <(ls foo) <(ls bar) # Outputs # < x # --- # > y
Pisanje
1
bash
Imajte na umu da skripte ne moraju biti napisane u bash-u da bi bile pozvane iz terminala. Na primer, ovo je jednostavna Python skripta koja ispisuje argumente u obrnutom redosledu:
1 2 3 4
#!/usr/local/bin/python import sys for arg in reversed(sys.argv[1:]): print(arg)
Kernel zna da izvrši ovu skriptu sa python interpreterom umjesto sa shell komandom zato što smo uključili shebang linije na samom vrhu skripte. Dobra praksa je da pišete shebang liniju koristeći env komandu koja će se izvršiti bez obzira koja je komanda linija u sistemu, povećavajući prenosivost vaših skripti. Da bi riješio lokaciju, env će koristiti
1
PATH
1
#!/usr/bin/env python.
Neke razlike između shell funcija i scripti koje bi trebali da imate u vidu su:
- Funkcije moraju biti u istom jeziku kao i shell, dok skripte mogu biti napisane u bilo kojem jeziku. Ovo je razlog zašto je uključivanje shebang za skripte veoma važno.
- Funkcije se učitavaju jednom kada se pročita njihova definicija. Skripte se učitavaju svaki put kada se izvršavaju. Ovo čini funkcije malo bržim za učitavanje, ali kada ih promijenite moraćete da ponovo učitate njihove definicije.
- Funkcije se izvršavaju u trenutnom shell okruženju, dok se skripte izvršavaju u njihovom posebnom procesu. Dodatno, funkcije mogu promijeniti varijable okruženja, npr. promijeniti vaš trenutni direktorijum, dok skripte ne mogu. Skripte će biti zaobiđene od vrijednosti environment varijabli koje su bile eksportovane koristeći export.
- Kao i sa bilo kojim drugim programskim jezikom, funkcije su moćan konstrukt za postizanje modularnosti, ponovne upotrebe koda, i jasnoće shell kod-a. Obično će shell skripte uključiti njihovu sopstvenu definiciju funkcije.
Shell Tools
Pronalaženje načina za korišćenje komandi
U ovom trenutku, vjerovatno se pitate kako da pronađete za komande u sekcijama kao što su:
1
ls -l, mv -i i mkdir -p
Kao što smo vidjeli u shell lekciji, prvi pristup bi bio da pozovete komadnu sa
1
-h
1
--help
1
man rm
1
-i
1
:help
1
?
Nekada manpages mogu pružiti previše detaljan opis komandi, i mogu da otežaju odluku koji flag/sintaksu će biti korišćen za standardnu upotrebu. TLDR strance su sjajno rešenje koje se fokusira na dati primjer tako da možete mnogo brže i lakše da izaberete opciju koju ćete koristiti. Lično, češće koristim stranice za tar i ffmpeg u odnosu na manpages.
Traženje fajlova
Jedan od najčešćih zadataka koji se ponavljaju jeste traženje fajlova ili direktorijuma. Svi UNIX-like sistemi dolaze sa find, odličnim shell alatom kojim se pretražuju fajlovi.
1
find
1 2 3 4 5 6 7 8
# Find all directories named src find . -name src -type d # Find all python files that have a folder named test in their path find . -path '*/test/*.py' -type f # Find all files modified in the last day find . -mtime -1 # Find all zip files with size in range 500k to 10M find . -size +500k -size -10M -name '*.tar.gz'
Osim listinga fajlova, pretraga takođe može izvršiti akcije za fajlove koji se poklapaju sa vašim upitom. Ovo svojstvo može biti nevjerovatno korisno da bi se pojednostavilo ono što može biti jako monoton zadatak.
1 2 3 4
# Delete all files with .tmp extension find . -name '*.tmp' -exec rm {} \; # Find all PNG files and convert them to JPG find . -name '*.png' -exec convert {} {}.jpg \
Uprkos sveprisutnosti pretrage, njegova sintaksa ponekad može biti nezgodna za pamćenje. Na primer, da bi se pojednostavila pretraga fajlova koji se poklapaju sa nekim obrascem, morate da izvršite
1
find -name '*PATTERN*' (ili -iname
1
PATTERN
1
fd PATTERN
Većina će se složiti da su
1
find
1
fd
1
Locate
1
updatedb
1
find
1
locate
Traženje koda
Pronalaženje datoteka po imenu je korisno, ali prilično često želite da pretražite na osnovu sadržaja datoteke. Uobičajeni scenario je da se traže sve datoteke koje sadrže neki obrazac zajedno sa onim datotekama gdje se navedeni obrazac pojavljuje. Da bi se ovo postiglo, većina UNIX-like sistema pružaju grep, generički alat za usklađivanje obrazaca iz ulaznog teksta.
1
grep
Za sada, znajte da
1
grep
1
-C
1
-v
1
grep -C 5
1
-R
Ali
1
grep -R
1
.git
1
grep
1
rg
1 2 3 4 5 6 7 8
# Find all python files where I used the requests library rg -t py 'import requests' # Find all files (including hidden files) without a shebang line rg -u --files-without-match "^#!" # Find all matches of foo and print the following 5 lines rg foo -A 5 # Print statistics of matches (# of matched lines and files ) rg --stats PATTERN
Imajte u vidu da je sa
1
find
1
fd
Traženje shell komandi
Do sada smo vidjeli kako da pronađete datoteke i kod, ali kako budete provodili više vremena u shell-u, možda ćete željeti da pronađete specifične komande koje ste pozvali u jednom trenutku. Prva stvar koju treba da znate jeste da će vam kucanje strelice na gore vratiti poslednju komandu koju ste pozvali, ukoliko nastavite da je pritiskate polako će proći kroz vašu shell istorju.
1
history
1
grep-u
1
history | grep find
U većini shell-ova možete koristiti
1
Ctrl + R
1
Ctrl + R
1
Ctrl + R
1
fzf
Još jedan dobar trik koji se tiče istorije jesu autosugestije zasnovane na istoriji. Prvo su predstavljene od strane fish shell-a, ova funkcija dinamički dovršava vašu trenutnu shell komandu sa poslednjom komandom koju ste unijeli a koje imaju zajednički prefix. To se može omogućiti u zsh i to je sjajan trik za vaš shell.
I na kraju, ono što treba imati u vidu, ako započnete vašu komandu sa leading space-om ona neće biti dodata u vašu istoriju. Ovo je pogodno kada pišete komande sa passwordom ili drugim osjetljivim informacijama. Ukoliko napravite grešku, pa ne dodate leading space, uvijek možete ručno ukloniti unos editovanjem
1
.bash_history
1
.zhistory
Navigacija kroz direktorijume
Do sada smo pretpostavljali da se nalazite tačno tamo gdje treba da budete da bi izvršili ove akcije. Ali, šta mislite o brzom kretanju kroz direktorijume? Postoji mnogo jednostavnih načina da se ovo uradi kao što je pisanje shell alijasa ili kreiranje sym-linkova sa ln -s, ali je istina da su programeri smislili izuzetno pametno i sofisticirano rešenje do sada.
Kao i kod teme ove lekcije, često želite da optimizujete uobičajenu upotrebu. Pronalaženje učestalih i novijih fajlova i direktorijuma se može odraditi kroz alate kao što su fasd i autojump. Fasd rangira fajlove i direktorijume kroz frecency koji podrazumijeva i učestalost i rijetkost. Uobičajeno,
1
fasd
1
z
1
cd
1
/home/user/files/cool_project
1
z cool
1
j cool
Postoje složeniji alati da biste brzo dobili pregled strukture direktorijuma: tree, broot ili čak punopravni menadzeri datoteka kao što su nnn i ranger.
Vježbe
- Pročitajte man ls i napišite komandu koja će ispisate fajlove na sledeće način
1
ls
- Uključuje sve fajlove, kao i skrivene fajlove
- Veličine su navedene u formatu čitljivom ljudima (npr. 454M umjesto 454279954)
- Fajlovi su posloženi do najnovijeg
- Output je kolorizovan
Jednostavan output bi izgledao ovako
1 2 3 4 5
-rw-r--r-- 1 user group 1.1M Jan 14 09:53 baz drwxr-xr-x 5 user group 160 Jan 14 09:53 . -rw-r--r-- 1 user group 514 Jan 14 06:42 bar -rw-r--r-- 1 user group 106M Jan 13 12:12 foo drwx------+ 47 user group 1.5K Jan 12 18:08 ..
-
Napišite bash funkcije
i1
marco
koje izvršavaju sledeće. Kada god izvršite funkciju1
polo
, trenutni radni direktorijum bi trebao da bude sačuvan na neki način, onda kada izvršite funkciju1
marco
, bez obzira u kom se direktorijumu nalazite ,1
polo
bi trebao da uradi1
polo
nazad u direktorijum gdje ste izvršili funkciju1
cd
. Da bi lakše uklonili bugove možete napisati kod u fajlu1
marco
i ponovo učitali definicije u vaš shell izvršavanjem1
marco.sh
.1
source marco.sh
-
Recimo da imate komandu koja rijetko ne uspijeva. Da biste otklonili grešku morate da snimite njen output ali može proći dosta vremena da biste ostvarili neuspjeh. Napišite bash skriptu koja pokreće sledeću skriptu dok se ne desi greška i zabilježi standardni output i ispiše greške iz datoteka i fajlova. Bonus poeni ukoliko dobijete podatak koliko je puta skripta pokrenuta prije nego se desila greška.
1 2 3 4 5 6 7 8 9 10 11
#!/usr/bin/env bash n=$(( RANDOM % 100 )) if [[ n -eq 42 ]]; then echo "Something went wrong" >&2 echo "The error was using magic numbers" exit 1 fi echo "Everything went according to plan"
- Kao što smo rekli u ovoj lekciji 's
1
find
može biti veoma moćno za izvođenje operacija sa datotekama koje tražimo. Ipak, šta ukoliko želimo da uradimo nešto sa svim fajlovima, kao npr. da kreiramo zip fajlove? Kao što ste vidjeli do sada komande će primiti inpute iz argumenata i STDIN. Kada pajpujete komande, povezujete STDOUT I STDIN, ali neke komande kao1
-exec
uzimaju inpute iz argumenata. Za prevazilaženje ovog prekida postoji xargs komanda koja će izvršiti komandu korišćenjem STDIN-a kao argumenta. Na primer1
tar
će izbrisati sve fajlove iz trenutnog direktorijuma.1
ls | xargs rm
Vaš zadatak je da napišete komandu koja će rekurzivno pronaći sve HTML fajlove u folderu i zip-ovati ih. Imajte u vidu da bi vaša komanda trebala da radi čak ako fajlovi imaju razmake (nagovještaj: provjerite
1
d
1
xargs
- (Napredno) Napišite komandu ili skriptu koja će rekurzivno pronaći poslednje podešene fajlove u direktorijumu. Dodatno, možete li razvrstati sve fajlove od najstarijeg do najnovijeg?