Skrypty

Popularne interpretery

  • sh — (Bourne shell) szeroko dostępny interpreter

  • bash — (Bourne-again shell) rozszerzona wersja sh

  • fish — (Friendly Interactive shell) interpreter z ułatwieniami dla użytkowników i programistów

  • csh and tcsh — (C shell) interpreter luźno oparty na składni języka C

  • zsh — (Z shell) rozszerzona wersja bash-a

Wykonywanie skryptów

Lista komend zapisana w dowolnym pliku tekstowym może być wykonana za pomocą intepretera:

  • sh PLIK

  • bash PLIK

Pliki wewnętrznie oznacza się jako skrypt podając w pierwszej lini pliku dyrektywę interpretera która informuje system operacyjny za pomocą jakiego polecenia je otworzyć. Dyrektywa to pełna ścieżka programu do wykonania poprzedzona znakiem komentarza i wykrzyknikiem #! (tzw. shebang):

  • #!/bin/sh

  • #!/bin/bash

Można w ten sposób oznaczyć dowolny plik tekstowy do otwarcia za pomocą dowolnego programu:

  • #!/usr/bin/env python

  • #!/usr/bin/firefox

Plik tekstowy oznaczony dyrektywą można uruchomić:

  • ./SKRYPT.sh – w katalogu bieżącym

  • /home/user/SKRYPT.sh – pełna ścieżka (z dowolnego katalogu)

Żeby uruchomić plik należy mu nadać odpowiednie prawa (chmod).

Parametry i zmienne

Skrypt może zostać uruchomiony z argumentami, np.:

./SKRYPT.sh a b c d

Argumenty są dostępne przez parametry pozycyjne (positional parameters):

$1 $2 $3 $4 ... ${10} ${11} ...

Polecenie shift n przesuwa zawartość zmiennych o n w lewo (domyślnie n = 1).

Przykłady:

  • shift          # 1=$2, 2=$3, 3=$4 ...

  • shift 2        # 1=$3, 2=$4, 3=$5 ...

Zmienne specjalne

  • $0 – ścieżka do skryptu (nazwa wykonywanego skryptu)

  • $# – liczba argumentów

  • $* – wszystkie parametry pozycyjne jako jeden napis ("$1 $2 ..."

  • $@ – wszystkie parametry pozycyjne jako osobne napisy ("$1" "$2" ...)

  • $$ – PID procesu skryptu

Komentarze

Komenatrze zaczynają się od znaku # i kończą się wraz z końcem bieżącej lini.

# Prawo Ohma
I=`expr $V / $R`
#echo "I=$V/$R -> $I"

# TODO Dodać prawo Kirchhoffa

Jakie zastosowania mają komentarze?

Konstrukcje językowe

Separatory instrukcji

Sposoby wykonania dwóch komend:

# wykonanie sekwencyjne
COMMAND1
COMMAND2

# wykonanie sekwencyjne
COMMAND1; COMMAND2

# wykonanie warunkowe
COMMAND1 && COMMAND2

# wykonanie warunkowe
COMMAND1 || COMMAND2

# wykonanie współbieżne
COMMAND1 & COMMAND2

# potok
COMMAND1 | COMMAND2

Konstrukcja if

if COMMAND0
then
    INSTRUCTIONS
elif COMMAND1           # Opcjonalne
    INSTRUCTIONS
else                    # Opcjonalne
    INSTRUCTIONS
fi

Wykonywane instrukcje w zależności od stanu wykonania wskazanej komendy COMMAND0.

Przydatne polecenia:

  • false – nie robi niczego i kończy się stanem 1

  • true – nie robi niczego i kończy się stanem 0

  • test – sprawdza wskazany warunek

  • [ ] – j/w tylko krócej

  • [[ ]] – j/w tylko bezpieczniej zalecane, patrz niżej

  • (( )) – sprawdza warunek arytmetyczny zalecane, patrz niżej

  • $(( )) – wykonuje działanie arytmetyczne

Porównywanie wartości zmiennych

Uwaga: Bash operuje na ciągach znaków, więc nawet jak do zmiennej mamy przypisana liczbę, traktowana ona jest jako ciąg znaków. Dla bezpieczeństwa warto umieszczać zmienne w cudzysłowy (bo inaczej będzie problem jak ciąg znaków umieszczony w zmiennej będzie zawierać spacje).

x="abc"
y=42

if [ "$x" == "abc" ]; then
    echo true                  # true
else
    echo false
fi

if [ "$x" > "aaa" ]; then
    echo true                  # true
else
    echo false
fi

if [ ! "$y" == 100 ]; then
    echo true                  # true
else
    echo false
fi

if [ "$y" != 100 ]; then
    echo true                  # true
else
    echo false
fi

Porównywanie wartości liczbowych:

  • -lt – lower-than

  • -le – lower-equal

  • -eq – equal

  • -ge – greater-equal

  • -gt – greater-than

y=42

if [ $y -lt 100 ]; then
    echo true                  # true
else
    echo false
fi

Łączenie warunków odbywa się przy pomocy operatora -o (or) oraz operatora -a (and).

x="abc"

if [ $x == "abc" -o $x == "def" ]; then
    echo true                  # true
else
    echo false
fi

if [ $x == "abc" -a $x == "def" ]; then
    echo true
else
    echo false                 # false
fi

W warunkach można sprawdzać różne rzeczy związane z plikami, np.:

  • # [ -a FILE ] – prawda jeśli FILE istnieje

  • # [ -d FILE ] – prawda jeśli FILE istnieje i jest katalogiem

  • # [ -r FILE ] – prawda jeśli FILE istnieje i można go czytać

  • # [ -w FILE ] – prawda jeśli FILE istnieje i można do niego pisać

  • # [ -x FILE ] – prawda jeśli FILE istnieje i jest wykonywalny

  • # [ FILE1 -nt FILE2 ] – prawda jeśli FILE1 był zmieniony później niż FILE2, lub jeśli FILE1 istnieje a FILE2 nie

  • # [ FILE1 -ot FILE2 ] – prawda jeśli FILE1 jest starszy niż FILE2, lub jeśli FILE2 istnieje a FILE1 nie

filename="file.txt"            # 'file.txt' jest w bieżącym katalogu

if [ -a "$filename" ]; then
    echo true                  # true
else
    echo false
fi

W ogólności bezpieczniej używać następującej składni z podwójnymi [[ ]]. Wtedy np. nie trzeba umieszczać zmiennych w cudzysłowie (nawet jak wartość zmiennej to ciąg znaków ze spacjami) oraz można używać operatorów logicznych || i && jak w C.

x="abc"

if [[ $x == "abc" || $x == "def" ]]; then
    echo true                  # true
else
    echo false
fi

if [[ $x == "abc" && $x == "def" ]]; then
    echo true
else
    echo false                 # false
fi

filename="file.txt"
if [[ -a "$filename" ]]; then    # if 'file.txt' exists
    echo true                  # true
else
    echo false
fi

Do wykonywania porównań arytmetycznych wygodniej użyć składni (( )) (porównaj $(( ... ))).

y=42

if (( $y < 100 + 13 )); then
    echo true                  # true
else
    echo false
fi

Szczegóły: link

Konstrukcja case

case VALUE in
    PATTERN0) INSTRUCTIONS;;             # lub ;&
    PATTERN1) INSTRUCTIONS;;             # lub ;&
    PATTERN2) INSTRUCTIONS;;             # lub ;&
    PATTERN3) INSTRUCTIONS;;             # lub ;&
esac

Możliwa dowolna liczba warunków i dowolny blok instrukcji po każdym z warunków.

Przy zastosowaniu ;; po przypasowaniu do warunku i wykonaniu instrukcji wychodzi z konstrukcji case. Przy zastosowaniu ;& wykonuje instrukcje także kolejnego warunku.

Przykłady warunków:

  • napis – traktowany dosłownie

  • * – dowolny ciąg znaków

  • [A-Z] – klasa znaków

  • ? – dowolny znak

  • image_199[0-9]\*.jp?g – złożony warunek

Pętla while

while COMMAND
do
    INSTRUCTIONS
done

Wykonywane instrukcje do czasu az stan wykonania komendy COMMAND będzie inny od 0.

Przydatne instrukcje (dla każdego rodzaju pętli):

  • break – przerywa działanie pętli

  • continue – przerywa wykonywanie instrukcji w ciele pętli i przechodzi do następnej iteracji

Pętla for

for VARIABLE_NAME in ARGUMENT_LIST
do
    INSTRUCTIONS
done

Wykonuje instrukcje dla każdego z argumentów z ARGUMENT_LIST. Każdy z argumentów jest przypisywany kolejno do zmiennej VARIABLE_NAME.

Alternatywna uproszczona składnia:

for VARIABLE_NAME
do
    INSTRUCTIONS
done

Wykounje instrukcje dla każdego z argumentów (parametrów pozycyjnych) skryptu.

Zakończenie działania skryptu

exit

Zakończ wykonywanie skryptu. Jako argument przyjmuje stan zakończenia. Domyślnie status 0.

Operacje wejścia/wyjścia

read

Wczytaj do zmiennej. (BASH shell bultin)

-p prompt

znak (napis) zachęty

-t timeout

czas wygaśnięcia w sekundach

-s

nie wyświetlaj znaków wpisanych przez użytkownika

# Najprostszy przypadek
read i
echo $i

# Hasło wpisywane bez echa, max. przez 30 sekund
read -p "Password: " -s  -t 30 password
echo $password

Funkcje

function NAME () {      # Opcjonalne "function" lub "()"

    INSTRUCTIONS

} REDIRECTION           # Opcjonalne "REDIRECTION"

Przykłady:

# Przekierowuje echo na STDERR
function echo_err {
    echo $@
} >&2

echo_err "Ala ma kota"

# Przeciąża polecenie echod
function echo {
    if $DEBUG
    then
        echo $@
    fi
}

# Sprawdza warunek i ustawia stan wyjścia
function fsm_spaghetti_day {
    if [ `date +%A` == 'Friday' ]
    then
        exit 0
    fi
    exit 1
}

Wywołanie funkcji i przechwycenie jej outputu do zmiennej odbywa się tak jak wywołanie podprogramu. Do zmiennej trafi wszystko co funkcja wypisuje na stdout.

function foo() {
    echo "Start FOO"
    echo "Liczba wszystkich argumentów: $#"
    for i in $@; do
        echo "- $i"
    done
    echo "KONIEC foo"
}

foo_output=$(foo arg1 arg2 arg3)

echo "$foo_output"
printf "%s\n" "$foo_output"

# zarówno echo jak i printf wypiszą na ekran:
#
# Start FOO
# Liczba wszystkich argumentów: 3
# - arg1
# - arg2
# - arg3
# KONIEC foo

Tablice

Tablice w bashu indeksowane są od 0.

Przypisanie wartości do elementu tablicy (tablica jest tworzona, gdy nie była zadeklarowana wcześniej):

  • myarray[3]="abc"

  • myarray[4]=42

Odwołanie do elementu tablicy:

  • ${myarray[3]}

  • ${myarray[$i]} – gdzie do zmiennej i jest przypisana wartość np. 4

Wszystkie elementy tablicy:

  • ${myarray[@]} – podobnie do $@ (wszystkie parametry pozycyjne jako osobne napisy ("$1" "$2" ...))

Rozmiar tablicy:

  • ${#myarray[@]} – podobnie do $# (liczba argumentów skryptu/funkcji)

Iterowanie po elemntach tablicy:

myarray[3]="abc"
myarray[4]=42

for i in ${myarray[@]}; do
    echo $i
done

for i in $(seq 0 ((${#myarray[@]} - 1)) ); do
    echo $i
done

Rozbijanie ciągów znaków na tablice:

str="Ala ma...     kota"
echo $str                   # Ala ma...     kota

list=($str)

echo ${list[@]}             # Ala ma... kota

echo ${list[0]}             # Ala
echo ${list[1]}             # ma...
echo ${list[2]}             # kota

Rozbijanie ciągów znaków na tablice przy pomocy innego separatora niż spacja:

str="Ala_ma..._____kota"
echo "$str"                 # Ala_ma..._____kota
OLD_IFS=$IFS                ## zapisanie domyslnego separatora
IFS="_"
list=($str)
echo "${list[@]}"           # Ala ma...     kota

echo ${list[0]}             # Ala
echo ${list[1]}             # ma...
echo ${list[2]}             #
echo ${list[3]}             #
echo ${list[4]}             #
echo ${list[5]}             #
echo ${list[6]}             # kota

IFS=$OLD_IFS                ## przywrocenie domyslnego separatora

Szczegóły: link

Zadania

  1. Skrypt tworzący nazwę projektu, zmiennej, itp. wg określonego formatu.

    Jako pierwszy parametr podajemy nazwe formatu docelowego,

    Przykłady użycia:
    • ./chcase joined    my new project     # Zwraca: mynewproject

    • ./chcase underline my new var         # Zwraca: my_new_project

    • ./chcase uppercase my new const       # Zwraca: MY_NEW_CONST

    • ./chcase dashes    my new resource    # Zwraca: my-new-resources

    Jeśli format nie zostanie podany wyświetl dostępne formaty i zakończ z błędem. Jeśli użytkownik nie poda argumentów, wyświetl krótką instrukcję i zakończ z błędem.

  2. Skrypt tworzacy kopie zapasowe wskazanych plików i katalogów. Jako pierwszy

    parametr należy podać miejsce utworzenia kopii. Jako kolejne podajemy nazwy kopiowanych plików i katalogów. Do nazw kopiowanych zasobów dodana powinna być też data i czas utworzenia kopii.

    Przyklad użycia:

    ./bup /tmp/backup asia/ basia/ casia.txt

    Spowoduje powstanie plików:
    • /tmp/backup/10-05-12_10:03_asia/

    • /tmp/backup/10-05-12_10:03_asia/cokolwiek_było_w_katalogu

    • /tmp/backup/10-05-12_10:03_basia/

    • /tmp/backup/10-05-12_10:03_basia/cokolwiek_było_w_katalogu

    • /tmp/backup/10-05-12_10:03_casia.txt

    Jeśli wskazany katalog docelowy nie istnieje to należy go utworzyć. Jeśli wskazany katalog docelowy istnieje ale nie jest katlogiem, należy zakończyć działanie z błędem.

    Jeśli plik do kopiowania nie istnieje, należy go pominąć i na samym końcu działania programu wypisać ostrzeżenie, że plik nie został odnaleziony.

  3. Skrypt który rozmawia z użytkownikiem. Użytkownik zadaje pytanie, skrypt losuje odpowiedź z pliku. Kiedy użytkownik napiszę kluczową frazę (np. that's enough) skrypt kończy rozmowę.

  4. Skrypt który czeka, aż zawartość określonej strony się zmieni. Jeśli zawartość się zmieniała, to skrypt wykonuje wskazaną komendę. Użytkownik może (ale nie musi) określić jak często skrypt ma sprawdzać kopie lokalną.

    Przykład użycia:

    # Przykładowa strona (zapisana do zmiennej dla wygody)
    WEBSITE=http://www.cs.put.poznan.pl/ksiek/SOP/resources/embrace_change.php
    
    # Sprawdza czy strona się zmieniła z domyślną częstotliwością.
    # Jeśli strona się zmieni to otwiera ją w przeglądarce internetowej.
    ./diditchange "$WEBSITE" firefox "$WEBSITE"
    
    # Sprawdza czy strona się zmieniła raz na 5 sekund.
    # Jeśli strona się zmieni to otwiera ją w przeglądarce internetowej.
    ./diditchange "$WEBSITE" -t 5 firefox "$WEBSITE"
    

    Jeśli nie można połączyć się ze stroną skrypt powinien wyświetlić błąd i zakończyć działanie.

    Przydatne polecenia: wget, sleep.

  5. Skrypt-predykat który sprawdza porę dnia. Możliwe pory dnia: early, late, day, night, morning, lunchtime, evening

    Przykłady użycia:

    if ./its late
    then
       ...
    fi
    
    while ./its lunchtime
    do
       ...
    done
    

    Jeśli użytkownik poda porę dnia nieznaną skryptowi należy wyświetlić komunikat o błędzie.

  6. Skrypt który dodaje dyrektywę interpretera dla skryptów znalezionych we wskazanych katalogach. Do skryptów o rozszerzeniu .sh dodawane jest #!/bin/bash, do skryptow o rozszerzeniu .py ‘#!/usr/bin/env python’. Inne rodzaje skryptów są pomijane.

    Przykład użycia: ./addshebang repository/scripts "/tmp/work in progress"

    Jeśli plik już posiada dyrektywę interpretera, wypisz wiadomość do użytkownika i zignoruj plik.

Na pocieszenie

Nobody really knows what the Bourne shell’s grammar is.
Even examination of the source code is little help.

http://en.wikipedia.org/wiki/Tom_Duff