Unixowe skrypty powłoki dla programistów aplikacji internetowych
Unixowe skrypty powłoki dla programistów aplikacji internetowych
Created: 25th August 2022  Last Modified: 1st November 2022

Skrypty na systemach uniksowych - czyli jakich?

Posłużę się uproszczeniami, ale postaram się też nie zgubić sensu.

Systemy operacyjne klasy Unix to systemy wzorujące się i zbliżone językiem komend jak i konstrukcją całego systemu do Unixa. Do takich systemów zaliczamy wszystkie dystrybucje Linux i macOS (a właściwie jego kernel Darwin). Od niedawna(2016) możliwe jest natywne uruchamianie skryptów na Windowsie dzięki (Windows Subsystem for Linux)

Powłoka

Powłoka (shell)- Powłoki na których się skupimy izolują jądro systemu, dostarczając użytkownikowi interfejs pozwalający na dostęp do danych i zarządzanie systemem za pomocą zbioru komend. Sprawia to że użytkownik nie ma konieczności zrozumienia złożoności całego systemu. Koncepcja dzisiaj nie wydaje się bardzo rewolucyjna, ale działo się to w czasach gdy dane do komputera wprowadzano za pomocą kart perforowanych.

O powłokach cd.

Domyślne powłoki na wcześniej omówionych systemach są implementacjami

  • sh (Bourne shell command language) - nazwa pochodzi od nazwiska autora.

Popularne implementacje:

  • bash - (Bourne Again Shell) - jest domyślną powłoką w większości dystrybucji systemu GNU/Linux oraz w systemie macOS

  • zsh - rozszerzenie basha wprowadzające szereg usprawnień (domyślna powłoka w macOS od Cataliny - 2019)

Po co nam skrypty powłoki?

Umiejętność pisania skryptów nie jest wymagana od programistów, ale pozwala znacznie przyspieszyć ich pracę i sprawia, że staje się przyjemniejsza.

Przez lata powstała niezliczona ilość komend dostępnych w bashu.

Komendy te to zazwyczaj pliki binarne. Możemy użyć komendy which aby dowiedzieć jaki plik zostanie uruchomion zostanie uruchomiony przy użycu danej komendy.


which which

/usr/bin/which

Gdy znamy komendę, a potrzebujemy instrukcji użycia możemy skorzystać z dokumentacji systemowej (man <komenda>), instrukcji wbudowanej w komendę <komenda> --help, ale zazwyczaj są bardzo długie i często wystarczy (tldr <komenda>)

Podstawy składni

Zmienne lokalne:

Ciągi znaków możemy deklarować z wykorzystaniem cudzysłowów pojedynczych i podwójnych

  • deklarując zmienną nie określamy typu

  • zmienne deklarujemy wykorzystując "="

  • z zasady nie umieszczamy spacji wokół "="

my_name=Heisenberg
echo my_name
echo $my_name
echo '$my_name'
echo "$my_name"

Zmienne środowiskowe

notes

To zmienne dostępne dla wszystkich procesów uruchomionych w danej sesji. Zmienne środowiskowe zazwyczaj są zapisane dużymi literami.

Jest wiele zmiennych środowiskowych, które są dostępne w każdej sesji powłoki. Warto znać kilka z nich:

  • $HOME - ścieżka do katalogu domowego

  • $PATH - ścieżka do katalogów w których powłoka będzie szukała plików wykonywalnych

  • $PWD - ścieżka do katalogu w którym aktualnie się znajdujemy

  • $USER - nazwa użytkownika

  • $RANDOM - losowa liczba

export GLOBAL_VAR="Jestem globalną zmienną."
echo $GLOBAL_VAR
echo $HOME
Jestem globalną zmienną.
/home/pegre

Parametry pozycyjne

notes

To zmienne które są przekazywane do skryptu w momencie jego uruchomienia w zadanej kolejności. W bashu wyróżniamy również parametry specjalne na przykład:

  • $0 - nazwa skryptu

  • $1 - pierwszy parametr

  • @ - wszystkie parametry bez@

  • $# - liczba parametrów

  • $FUNCNAME - nazwa funkcji w której jesteśmy

test_func () {
    echo "Nazwa funkcji: $FUNCNAME"
    echo "Parametr 1: $1"
    echo "Parametr 2: $2"
    echo "Liczba parametrów: $#"
    echo "Wszystkie parametry: $@"
}
test_func "par1" "par2"

Nazwa funkcji: test_func
Parametr 1: par1
Parametr 2: par2
Liczba parametrów: 2
Wszystkie parametry: par1 par2

Dopełnienia (expansions)

notes

Dopełnienie zostaje wykonane po tym jak komenda została podzielona na tokeny. Polega na podmienieniu specjalnych sekwencji znaków na wartości zmiennych lub wynik innych komend.


Dopełnienie z wykorzystaniem nawiasów klamrowych

notes

Pozwala nam na tworzenie ciągów znaków. W nawiasach klamrowych możemy wykorzystać dowolne wyrażenie, które zostanie podstawione w miejsce nawiasów klamrowych. Przykłady użycia:

echo beg{i,a,u}n
echo {1..10}
echo {00..12..2}
begin began begun
1 2 3 4 5 6 7 8 9 10
00 02 04 06 08 10 12

Zastąpienie komendą

notes

Pozwala na wykonanie komendy i podstawienie jej wyniku w miejsce. Nazwę komendy wraz z argumentami umieszczamy w `` (backtickach) lub $().

now=`date +%T`
# lub
now=$(date +%T)
echo $now
16:01:17

Dopełnienie z obliczeniami

notes

Aby wykonać obliczenia w bashu możemy wykorzystać ((...)) lub[...]. Wynik obliczeń zostanie podstawiony w miejsce nawiasów.

echo $((2+2))
echo $[2+2]
4
4

W środku nawiasów możemy korzystać z zmiennych bez użycia $.

x = 2
y = 2
echo $((x+y))

Róznica między podwójnymi a pojedynczymi cudzysłowami

notes

W bashu podwójne cudzysłowy pozwalają na wykorzystanie zmiennych, a pojedyncze nie.

my_name=Heisenberg
echo 'My name is $my_name'
echo "My name is $my_name"
My name is $my_name
My name is Heisenberg

Tablice

notes

W bashu elementy tablicy przedzielone są specjalną zmienną IFS (Internal Field Separator). Domyślnie jest to spacja, ale można ją zmienić.

Deklaracja i odczyt tablicy

Stworzenie następuje przez przypisanie wartości do indeksu tablicy lub przez przypisanie wartości w nawiasie z aktualnym IFS. Odczytać możemy posługując się indeksem tablicy lub dopełnieniem.

owoce[0]=jablko
owoce[1]=gruszka
owoce[2]=banan

warzywa=(pomidor marchew kapusta)
echo ${owoce[1]}
echo ${owoce[@]}
echo ${warzywa[*]}

gruszka
jablko gruszka banan
pomidor marchew kapusta

Inne operacje na tablicach

notes

Wyciąganie wycinka tablicy: W poniższym przykładzie ${warzywa[@]} rozszerza całą tablice, a :0:2 wyciąga wycinek zaczynający się od zera od długośći 2.

warzywa=(pomidor marchew kapusta ogórek)
echo ${warzywa[@]:0:2}

Dodawanie i usuwanie elementów tablicy: Przy dodawaniu wykorzystujemy wcześniej poznane metody. Aby usunąć element wykorzystujemy komendę unset.

owoce=(jablko gruszka banan)
warzywa=(pomidor marchew kapusta ogorek)
warzywa[5]=ziemniak
echo ${warzywa[@]}

warzywa[6]=${owoce[@]}
echo ${warzywa[@]}

unset warzywa[3]
echo "${warzywa[@]}"
pomidor marchew kapusta ogorek ziemniak
pomidor marchew kapusta ogorek ziemniak jablko gruszka banan
pomidor marchew kapusta ziemniak jablko gruszka banan

Strumienie

notes

Bash odbiera dane z wejścia standardowego i przekazuje je do wyjścia standardowego. Możemy je przekierować do pliku lub do innej komendy. Są trzy strumienie:

  • standardowe wejście (stdin) - 0

  • standardowe wyjście (stdout) - 1

  • standardowe wyjście błędów (stderr) - 2

Przekierowanie strumieni

notes

Przekierowania pozawalają na kontrolowanie, gdzie dane będą przekazywane.

  • > - przekierowanie wyjścia do pliku

  • >> - dopisanie wyjścia do pliku

  • &> - przekierowanie wyjścia i wyjścia błędów do pliku

  • &>> - dopisanie wyjścia i wyjścia błędów do pliku

  • < - przekierowanie wejścia z pliku

echo "Hello World" > /tmp/hello.txt
echo "Hello Again" >> /tmp/hello.txt

ls ~/not_existing_dictionary 2> /tmp/error.txt
cat < /tmp/error.txt
ls: cannot access '/home/pegre/not_existing_dictionary': No such file or directory

Potok (pipe)

W bashu możemy łączyć komendy za pomocą potoku. W ten sposób możemy przekazywać dane z jednej komendy do drugiej. Wykorzystujemy do tego znak |.

curl -s https://www.gutenberg.org/files/1513/1513-h/1513-h.htm > /tmp/romeo_and_juliet.txt
cat /tmp/romeo_and_juliet.txt | grep -i juliet | wc -l
193

Instrukcje warunkowe

notes

Wyrażenia logiczne

Wyrażenie logiczne zapisywane są nawiasach kwadratowych mogą być łączone za pomocą operatorów logicznych przykładowo: && (i), || (lub), ! (negacja).

Wyrażenie z ciągami znaków:

  • -z STR sprawdza czy ciąg jest pusty (długość jest równa zero)

  • -n STR sprawdza czy ciąg nie jest pusty (długość jest większa od zera)

  • STR1 == STR2 sprawdza czy ciągi są równe

  • STR1 != STR2 sprawdza czy ciągi są różne

Wyrażenia z liczbami:

  • ARG1 -eq ARG2 - sprawdza czy ARG1 jest równy ARG2

  • ARG1 -ne ARG2 - sprawdza czy ARG1 jest różny od ARG2

  • ARG1 -gt ARG2 - sprawdza czy ARG1 jest większy od ARG2

  • ARG1 -ge ARG2 - sprawdza czy ARG1 jest większy lub równy ARG2

  • ARG1 -lt ARG2 - sprawdza czy ARG1 jest mniejszy od ARG2

  • ARG1 -le ARG2 - sprawdza czy ARG1 jest mniejszy lub równy ARG2

Instrukcja if

# Single-line
if [[ 1 -eq 1 ]]; then echo "true"; fi

# Multi-line
if [[ 1 -eq 1 ]]; then
  echo "true"
fi

if [[ `uname` == "Adam" ]]; then
  echo "Nie jedz jabłka"
elif [[ `uname` == "Ewa" ]]; then
  echo "Nie jedz jabłka"
else
  echo "Smacznego `uname`!"
fi
true
true
Smacznego Linux!

Instrukcja case

notes

Każdy case próbuje dopasować wartość do wzorca. Jeśli dopasowanie się powiedzie, to wykonuje się instrukcje pod case. W przypadku braku dopasowania, to wykonuje się instrukcje pod *). Zamknięcie bloku case następuje słowem esac ( to typowe dla basha, że słowo zamykające jest odwrócone od słowa otwierającego).

extension="png"
# get extension of file
case "$extension" in
  "jpg"|"jpeg")
    echo "To zdjęcie jest w formacie JPEG."
  ;;
  "png")
    echo "To zdjęcie jest w formacie PNG."
  ;;
  "gif")
    echo "Wygląda na to, że jest to plik GIF."
  ;;
  *)
    echo "To chyba nie jest zdjęcie."
  ;;
esac
Wygląda na to, że to jest plik GIF.

Pętla for

notes

for i in {1..5}; do
  echo $i
done
1
2
3
4
5

Petla while

x=0
while [[ $x -lt 10 ]]; do # wartośc x jest mniejsza od 10
  echo $(( x * x ))
  x=$(( x + 1 )) # zwiększamy x o 1
done

Funkcje

Kod wyjścia dla poprawnie wykonanej funkcji to 0. W przypadku błędu jest dodatnią liczbą całkowitą od 1 do 255.

function hello() {
  echo "Hello $1"
}
hello "World"
echo $?
Hello World
0

Czytanie danych od użytkownika

notes

Do czytania danych od użytkownika służy funkcja read. Domyślnie odpowiedź zapisywana jest w zmiennej $REPLY.

Debugowanie skryptów

notes

Shell daję nam narzędzia umożliwiające debugowanie skryptów. Aby włączyć debugowanie możemy dodać specjalnę flagę do shebangu: s notes #+endnotes Shell daję nam narzędzia umożliwiające debugowanie skryptów. Aby włączyć debugowanie możemy dodać specjalnę flagę do shebangu "#!/bin/bash flaga" lub użyć polecenia "set flaga"

Flagi w debugowaniu:

  • -x wyświetla każdą komendę przed jej wykonaniem

  • -v wyświetla każdą zmienną przed jej użyciem

  • -n sprawdza składnię skryptu, ale nie wykonuje go

set -x

function iterate(){
  for i in {1..5}; do
    echo $i
  done
}
iterate