6  Efektivno pisanje Bash skripti

Efikasna dijagnostika sistema i automatizacija rutinskih zadataka zahtevaju razumevanje načina na koji se komandna linija i alati koriste za rešavanje problema. Ovo poglavlje prikazuje jednostavne i napredne tehnike Bash skriptovanja.

6.1 Zaglavlje

Zaglavlje (eng. shebang) je prvi red skripte, koji počinje znakovima #!. On određuje koji program treba da izvrši skriptu kada se pokrene kao izvršna datoteka.

#!/bin/bash
#!/usr/bin/python
#!/usr/bin/env bash

Skript se nalazi u fajlu 01_shebang.

6.2 Argumenti i zamene parametara

Pozicioni parametri predstavljaju argumente prosleđene skripti: $0 je ime skripte, $1, $2 i ostali su pojedinačni argumenti, a $# je njihov broj. "$@" prosleđuje svaki argument kao zasebnu celinu, uključujući argumente sa razmacima.

$ ./02_args first "second arg"
Script: ./02_args
Argument count: 2
Argument: <first>
Argument: <second arg>

Skript se nalazi u fajlu 02_args.

Napomena: Proslediti argument koji sadrži razmak

6.2.1 Zamene

Primer pokazuje podrazumevane vrednosti, broj znakova i izdvajanje dela teksta:

$ ./03_substitutions
Hello, Guest
File: report.txt
Uppercase name: GUEST
First three characters: Gue
Character count: 5

Skript se nalazi u fajlu 03_substitutions.

Oblici zamene parametara koji se često koriste:

${var:-default}    # Koristi default ako je var nedefinisan ili prazan.
${var-default}     # Koristi default samo ako var nije definisan.
${var:?message}    # Ispiši poruku i završi ako je var nedefinisan ili prazan.
${var:=default}    # Dodeli default ako je var nedefinisan ili prazan.

6.2.2 Zamena komande i aritmetička ekspanzija

Zamena komande čuva standardni izlaz komande u promenljivoj. Koristite oblik $(...), koji je pregledniji od starih povratnih navodnika (``).

$ ./04_command_subst
Today: 2024-01-15
Files in current directory: 5

Skript se nalazi u fajlu 04_command_subst.

Aritmetička ekspanzija služi za celobrojne izraze:

$ ./05_arithmetic
count=11, next=8

Skript se nalazi u fajlu 05_arithmetic.

6.3 Provera argumenata i parsiranje opcija

6.3.1 Duge opcije ručnim upravljanjem

getopts obrađuje kratke opcije. Za jednostavan skup dugih opcija može se koristiti petlja:

$ ./06_parse_long --verbose --output result.txt input.txt
Input: input.txt
Output: result.txt

Oznaka -- završava obradu opcija. Korisna je kada ime datoteke počinje crticom:

$ ./06_parse_long -- -odd-filename.txt

Skript se nalazi u fajlu 06_parse_long.

6.3.2 Kratke i duge opcije zajedno

getopts obrađuje kratke opcije. Za kombinovano parsiranje kratkih i dugih opcija koristi se getopt:

$ ./07_parse_options -v -o result.txt input.txt
$ ./07_parse_options --verbose --output result.txt input.txt
$ ./07_parse_options -o result.txt --verbose input.txt

Skript se nalazi u fajlu 07_parse_options.

6.4 Uslovi i status komande

Status komande je 0 kada je uspešna, a različit od nule kada nije. Na primer, grep vraća status 1 kada ne pronađe podudaranje.

$ ./08_command_status
Errors found

Skript se nalazi u fajlu 08_command_status.

Uslovi za datoteke mogu da se izraze pomoću [ ] operatora:

$ ./09_file_conditions /tmp
Directory: /tmp

Skript se nalazi u fajlu 09_file_conditions.

Operatori && i || pokreću sledeću komandu u zavisnosti od statusa prethodne:

mkdir -p output && printf 'Output directory is ready\n'

6.5 Funkcije, nizovi i petlje

6.5.1 Funkcije i statusi

Funkcija vraća status poslednje izvršene komande, osim ako status nije izričito postavljen pomoću return. local ograničava promenljivu na funkciju.

$ ./10_functions /etc/passwd
File is readable

Skript se nalazi u fajlu 10_functions.

6.5.2 Nizovi

Nizovi čuvaju listu vrednosti bez gubitka razmaka ili posebnog značenja znakova kao što je *.

$ ./11_arrays
There are 3 entries
Entry: <first file.txt>
Entry: <second.txt>
Entry: <*.log>
All entries: <first file.txt> <second.txt> <*.log> <new file.csv>

Skript se nalazi u fajlu 11_arrays.

Koristite "${array[@]}" kada treba da se svaki element prosledi kao poseban argument.

6.5.3 Bezbedna obrada imena datoteka

Imena datoteka mogu da sadrže razmake, tabulate, pa čak i nove redove. Kombinacija find -print0 i read -d '' bezbedno obrađuje takva imena.

$ ./12_safe_filenames

Skript se nalazi u fajlu 12_safe_filenames.

6.6 Redirekcija ulaza i izlaza

Proces obično ima tri standardna toka: standardni ulaz (0), standardni izlaz (1) i standardni izlaz za greške (2).

$ ./13_redirections

Skript se nalazi u fajlu 13_redirections.

Redosled redirekcija je važan. > run.log 2>&1 prvo preusmerava standardni izlaz u datoteku, a zatim preusmerava izlaz za greške tamo gde je standardni izlaz trenutno usmeren.

6.6.1 “Here”-dokument i “here”-niska

“Here”-dokument prosleđuje više redova kao standardni ulaz:

$ ./14_here_doc
Hello, class.
This text is supplied on standard input.

Skript se nalazi u fajlu 14_here_doc.

Ako je oznaka razgraničenja pod navodnicima, promenljive i komande se ne proširuju:

$ ./15_here_doc_literal
This is literal text: $HOME is not expanded.

Skript se nalazi u fajlu 15_here_doc_literal.

“Here”-niska prosleđuje jedan tekstualni niz, uz završni novi red:

$ ./16_here_string
haystack with a needle

Skript se nalazi u fajlu 16_here_string.

6.7 Cevovodi i tee

Cevovod (|) prosleđuje standardni izlaz jedne komande na standardni ulaz sledeće.

$ ./17_pipes

Skript se nalazi u fajlu 17_pipes.

tee istovremeno prikazuje ulaz na standardnom izlazu i upisuje ga u datoteku. Sa opcijom -a dodaje sadržaj na kraj datoteke.

$ ./18_tee_log

Skript se nalazi u fajlu 18_tee_log.

Primer beleženja izlaza komande:

$ ./19_pipefail

Skript se nalazi u fajlu 19_pipefail.

Bez pipefail, status cevovoda je obično status poslednje komande. Sa pipefail, cevovod prijavljuje neuspeh ako ne uspe neka od komandi u njemu.

6.8 Privremene datoteke i čišćenje

mktemp kreira jedinstveno ime privremene datoteke. trap pokreće funkciju za čišćenje pri izlasku iz skripte.

$ ./20_temp_files

Skript se nalazi u fajlu 20_temp_files.

Primer sa privremenim direktorijumom:

$ ./21_temp_dir

Skript se nalazi u fajlu 21_temp_dir.

Oznaka -- ukazuje da se argumenti opcija završavaju. To sprečava da se putanja koja počinje crticom protumači kao opcija komande.

6.9 Signali

trap povezuje komandu ili funkciju sa signalom ili završetkom skripte. Ctrl-C obično šalje signal INT; signal TERM traži da se proces zaustavi.

$ ./22_signals &

Pritisnite Ctrl+C da zaustavite skriptu.

Skript se nalazi u fajlu 22_signals.

Funkcija za čišćenje treba da bude jednostavna i bezbedna za pozivanje tokom završavanja skripte.

6.10 Pozadinski procesi

Znak & pokreće komandu u pozadini. PID poslednjeg pokrenutog pozadinskog procesa dostupan je u promenljivoj $!.

$ ./23_background
Started jobs 5678 and 5679
First job exit status: 0
Second job exit status: 0

Skript se nalazi u fajlu 23_background.

wait PID čeka završetak procesa i vraća njegov status. Sačuvajte $! odmah nakon pokretanja procesa, jer se vrednost menja nakon sledeće pozadinske komande.

6.10.1 Pokretanje više zadataka i provera statusa

$ ./24_multi_task
alpha: starting
beta: starting
beta: finished
alpha: finished
All tasks succeeded

Skript se nalazi u fajlu 24_multi_task.

Izlaz pozadinskih procesa može da se prepliće. Da biste sačuvali izlaz svakog zadatka zasebno, redirektujte ga u posebnu datoteku:

$ ./25_separate_output

Skript se nalazi u fajlu 25_separate_output.

6.10.2 Ograničenje broja paralelnih zadataka

wait -n čeka da se završi bilo koji od navedenih pozadinskih zadatka. Primer ograničava broj istovremenih zadataka na dva.

$ ./26_parallel_limit
Processing alpha
Processing beta
alpha: finished
Processing gamma
Processing delta
beta: finished
All tasks finished

Skript se nalazi u fajlu 26_parallel_limit.

wait -n je dostupan u novijim verzijama Bash-a.

6.10.3 Zaustavljanje pozadinskih procesa tokom čišćenja

Skripta koja pokreće pozadinske procese treba po potrebi da ih zaustavi i sačeka pri izlasku.

$ ./27_cleanup_bg &

Pritisnite Ctrl+C da zaustavite skriptu.

Skript se nalazi u fajlu 27_cleanup_bg.

Ovaj primer šalje signal za prekid i čeka procese. Složenije skripte možda moraju da vode računa i o procesima koje su pokrenuli njihovi pozadinski zadaci.

6.11 Potprocesi i zamena procesa

Komande u zagradama pokreću se u potprocesu (eng. subshell). Promena direktorijuma ili vrednosti promenljive unutar potprocesa ne menja roditeljski proces.

$ ./28_subshell
Before: /home/user
Inside subshell: /tmp
After: /home/user

Skript se nalazi u fajlu 28_subshell.

Zamena procesa omogućava da se izlaz komande koristi kao datoteka slična ulazu. U ovom primeru, diff dobija dva zasebna ulaza:

$ ./29_proc_subst

Skript se nalazi u fajlu 29_proc_subst.

6.12 Primer: brojanje podudaranja

Ova skripta kombinuje parsiranje opcija, proveru ulazne datoteke, privremenu datoteku, čišćenje, obradu izlaznog statusa komande i opcioni izlaz u datoteku.

$ ./30_count_matches -v ERROR application.log
$ ./30_count_matches -o matches.txt ERROR application.log

Skript se nalazi u fajlu 30_count_matches.

6.13 Zadaci za vežbu

  • Dodajte opciju -n koja ograničava broj prikazanih rezultata.
  • Izmenite primer sa tee tako da dodaje sadržaj u dnevnik umesto da ga zamenjuje.
  • Dodajte proveru da se ulazna datoteka razlikuje od izlazne.
  • Izmenite primer sa pozadinskim procesima tako da svaki zadatak upisuje rezultat u zasebnu datoteku.
  • Testirajte skripte sa praznim argumentima, nepostojećim datotekama i imenima koja sadrže razmake.