Skrypty powłoki

Skrypty w Linuksie

Po co pisać skrypty?

Podczas pracy w konsoli często wykonujemy wielokrotnie po kolei te same komendy, czasem nieco zmienione na przykład poprzez podanie innych plików wejściowych czy parametrów uruchamianych programów. Między innymi w takich wypadkach lepiej jest umieścić je w skrypcie, czyli pliku tekstowym, który uruchamia po kolei wpisane w nim komendy. Umieszczanie poleceń w skryptach ma jeszcze tę zaletę, że stanowi on dobrą dokumentację wykonywanych operacji. Pisanie skryptów jest w zasadzie programowaniem. Będziemy tu używać powłoki Bash. Poza możliwościami uruchamiania różnych poleceń/programów, z czym mieliśmy dotychczas do czynienia, udostępnia ona także wiele elementów charakterystycznych dla języków programowania jak zmienne, kolekcje elementów czy pętle. Tutaj pokażę tylko kilka podstawowych elementów programowania w bash-u.

Pierwszy skrypt

Utwórz plik tekstowy o nazwie pierwszy_skrypt.sh. Umieść w nim kilka linii:
1
#!/bin/bash
2
3
# Pierwszy skrypt
4
5
echo "Witaj Świecie!"
6
pwd
7
ls
8
# Tworzenie katalogu i pliku tekstowego
9
mkdir TMP
10
touch TMP/tekst.txt
11
# Umieszczenie tekstu w pliku
12
echo "Tekst w pliku" > TMP/tekst.txt
13
# Wypisanie zawartości pliku
14
cat TMP/tekst.txt
Copied!
Przed uruchomieniem pliku musimy jeszcze dokonać pewnej modyfikacji. Samo przedłużenie .sh (które zresztą jest opcjonalne) nie sprawia, że plik jest uruchamialny, najpierw trzeba mu nadać odpowiednie prawa:
1
chmod u+x pierwszy_skrypt.sh
Copied!
Teraz można skrypt uruchomić:
1
$: ./pierwszy_skrypt.sh
2
Witaj Świecie!
3
/home/student/skrypty
4
pierwszy_skrypt.sh
5
Tekst w pliku
Copied!
Zauważ, że plik uruchamiamy podając przed nazwą ./, jeśli skrypt znajduje się w bieżącym katalogu. Jeśli nie, to podajemy do niego pełną ścieżkę (są jeszcze inne rozwiązania jak np. umieszczanie skryptu w katalogach, w których domyślnie powłoka szuka programów/skryptów).
Uruchomiony skrypt, jak widać, wykonał po kolei umieszczone w nim polecenia. Tajemniczo może wyglądać pierwsza linia. Znak # wskazuje na linię komentarza - czyli wszystko poza nim nie jest interpretowane przez powłokę. W tym przypadku, pierwszej linii skryptu, w połączeniu ze znakiem !, wskazuje jakiej powłoki (języka) użyć do uruchomienia poleceń w skrypcie. Skoro jednak już wspomniałem o komentarzach w skryptach to parę słów na ten temat. W komentarzach zwykle umieszczamy informacje na temat tego jaka jest ogólna funkcja skryptu, jak go używać, jak skrypt działa, czemu służą poszczególne polecenia itp. Przy pisaniu skryptów warto poświęcić trochę czasu aby go prawidłowo opisać w komentarzach. Po pierwsze sprzyja to dokumentacji pracy, po drugie jeśli po pewnym czasie wrócimy do skryptu, nie będziemy tracić czasu na przypominanie sobie ,,co autor miał na myśli''.

Zmienne

Zmienne pozwalają przechować a następnie wykorzystać pewne wartości, np. liczby, łańcuchy znaków.
Na początek utwórz plik zmienne.sh z taką zawartością:
1
#!/bin/bash
2
3
# Przykłady wykorzystania zmiennych
4
5
#Tworzymy dwie zmienne przechowujące łańcuchy znaków:
6
plik=tekst.txt
7
# Tu w łańcuchu znaków znajdują się spacje, dlatego konieczne są cudzysłowy
8
tekst="To jest tekst, który wpiszemy do pliku"
9
10
# Odwołania do wartości zmiennych
11
echo "Nazwa pliku: $plik"
12
echo $tekst
13
echo "Wpisuję do pliku: $tekst" > $plik
14
echo "Zawartość pliku:"
15
cat $plik
Copied!
Zauważ, że przy tworzeniu zmiennych nie używamy znaku $ przy ich nazwie (plik, tekst), natomiast kiedy się do nich odwołujemy, umieszczamy znak $ na początku ($plik, $tekst). Przy znaku = nie można wstawiać spacji. Znak $ czasem znajduje się w innym miejscu, co można zobaczyć np. na kolejnym przykładzie w którym pokażę jak używać zmiennych przechowujących liczby do prostych obliczeń:
1
#!/bin/bash
2
3
# Zmienne przechowujące liczby
4
liczba1=12
5
liczba2=6
6
7
# Wykonujemy działanie arytmetyczne - wyrażenie zamknięte w $[]
8
suma=$[liczba1+liczba2]
9
echo "suma: $liczba1 + $liczba2 = $suma"
10
11
# inny sposób - wyrażenie zamknięte w $(())
12
iloczyn=$((liczba1*liczba2))
13
echo "iloczyn: $liczba1 * $liczba2 = $iloczyn"
14
15
# Jeszcze inny sposób - polecenie let
16
# Uwaga - brak znaku $ przy nazwach zmiennych do których się odwołujemy
17
let roznica=liczba1-liczba2
18
echo "różnica: $liczba1 - $liczba2 = $roznica"
19
20
# Można oczywiście mieszać zmienne i liczby a wynik niekoniecznie
21
# trzeba przypisywać do zmiennej
22
23
echo "kolejne obliczenia: $liczba1 + 20 = $[liczba1 + 20]"
Copied!

Parametry (argumenty) przekazywane do skryptu i zmienne specjalne

Przy uruchamianiu skryptu, można przekazać parametry, takie jak np. nazwy plików z danymi, nazwy sekwencji, nazwy analizowanych gatunków itd. Podajemy je po nazwie skryptu, oddzielone spacjami. Jeśli przekazany parametr (np. tekst) zawiera spacje to całość obejmujemy cudzysłowami. Kolejne parametry przyjmują w skrypcie nazwy od $1 do $9. Nie oznacza to, że nie można ich przekazać więcej, ale można się do nich odwołać w inny sposób, np. używając pętli. Wszystkie parametry są przechowywane w [email protected].
W skrypcie można się posługiwać domyślnymi nazwami ($1..$2) ale dla przejrzystości kodu lepiej ich wartość przypisać do zmiennych o nazwach, które odpowiadają ich funkcji. Na przykład jeśli parametr jest nazwą pliku, to zienna może nazywać się plik albo plik_wejsciowy.
Utwórz skrypt argumenty.sh, nadaj mu uprawnienia wykonywalności.
1
#!/bin/bash
2
3
# wszystkie argumenty przekazywane :
4
echo "Argumenty: [email protected]"
5
6
# Wywołujemy poszczególne argumenty
7
echo "argument 1: $1"
8
echo "argument 2: $2"
9
10
# Argumenty, które mają konkretne znaczenie lepiej
11
# przypisać do zmiennych których nazwy tłumaczą ich znaczenie
12
plik=$1
13
tekst=$2
14
15
# Teraz posługujemy się już utworzonymi zmeinnymi
16
echo $tekst > $plik
17
echo "Zawartość pliku $plik"
18
cat $plik
Copied!
Teraz go uruchom z dwoma parametrami:
1
$: ./argumenty.sh argumenty.txt "Tekst przekazany jako argument"
2
3
Argumenty: argumenty.txt Tekst przekazany jako argument
4
argument 1: argumenty.txt
5
argument 2: Tekst przekazany jako argument
6
Zawartość pliku argumenty.txt
7
Tekst przekazany jako argument
Copied!
Sprawdź zawartość pliku argumenty.txt.
Istnieje wiele innych zmiennych specjalnych i środowiskowych, które się mogą przydać, np:
    $0 - nazwa własna skryptu
    $HOME - ścieżka do katalogu domowego bieżącego użytkownika
    $USER - nazwa bieżącego użytkownika

Zmienne tablicowe

Być może zwróciłeś uwagę, że powyższa zmienna [email protected] przechowuje wiele wartości na raz. Jest to przykład zmiennej tablicowej.
Zmienne tablicowe przydają się gdy chcemy przechować wiele elementów (które mogą odpowiadać wielu zmiennym). Za chwilę wrócimy do nich przy okazji pętli. Na razie prosty przykład jak utworzyć taką zmienną i wypełnić ją danymi (zmienne_tablicowe.sh):
1
#!/bin/bash
2
3
# Tworzenie zmiennej tablicowej
4
sekwencje=(atp1 atp6 matR cox1)
5
6
# Drukowanie na raz całej zawartości zmiennej tablicowej
7
echo ${sekwencje[@]}
8
9
# Drukowanie kolejnych wartości
10
echo ${sekwencje[0]}
11
echo ${sekwencje[1]}
12
echo ${sekwencje[2]}
13
echo ${sekwencje[3]}
Copied!
W nawiasach kwadratowych znajduje się indeks (numer) wartości. Zauważ, że liczby zaczynają się od 0 a nie 1. Wartość o indeksie 1 jest więc drugą a nie pierwszą. Znak @ lub * oznacza wszystkie wartości.

Zmienne i wynik polecenia

Zmienna może przechowywać wynik działania polecenia. W takim wypadku polecenie należy albo umieścić w parze odwrotnych apostrofów `` albo w parze nawiasów ze znakiem dolara $().
Sprawdź na przykładzie jak to działa (polecenia.sh)
1
#!/bin/bash
2
3
# Dwa sposoby przypisania do zmiennej wyniku działania polecenia
4
# lista plików w katalogu /usr/bin zaczynających się od z
5
pliki=`ls /usr/bin/z*`
6
# linie zawierające `plik` w bieżącym skrypcie
7
linie=$(grep plik $0)
8
9
# Drukowanie zawartości zmiennych
10
echo "**** Znalezione pliki:"
11
echo "$pliki"
12
echo "**** Znalezione linie:"
13
echo "$linie"
Copied!
Przy okazji sprawdź jak zmienia się sposób wyświetlania zawartości zmiennych jeśli ich nazwy nie znajdą się w cudzysłowach.

Pętle i czytanie wartości z pliku tekstowego

Pętle pozwalają na wielokrotne wykonywanie tych samych czynności, często ze zmienionymi parametrami. Nie będę tu gruntownie omawiał pętli, pokażę tylko kilka przykładów pętli, które można zastosować w dalszych etapach kursu.

Pętla operująca na argumentach

Pierwszy przykład pokazuje jak coś zrobić z kolejnymi argumentami przekazywanymi do skryptu. Mogą to być np. nazwy plików z którymi będziemy coś robić (np. generować dla nich drzewa filogenetyczne). Na razie tylko wydrukujemy ich nazwy.
Skrypt petla_argumenty.sh
1
#!/bin/bash
2
3
# Zmienna nr
4
nr=1
5
6
# Pętla 'for', która odczytuje argumenty przekazane do skryptu
7
# przechowywane w zmiennej tablicowej [email protected]
8
# Kolejne argumenty są przypisywane zmiennej 'argument'
9
for argument in [email protected]
10
do
11
# Drukowanie wartości
12
echo "Argument $nr = $argument"
13
# Zwiększanie wartości zmiennej 'nr' o 1
14
nr=$((nr+1))
15
done
Copied!
Teraz wywołaj skrypt z odpowiednimi parametrami:
1
$: /petla-argumenty.sh atp1.fasta atp6.fasta atp8.fasta matR.fasta
2
Argument 1 = atp1.fasta
3
Argument 2 = atp6.fasta
4
Argument 3 = atp8.fasta
5
Argument 4 = matR.fasta
Copied!
Zamiast argumentów można użyć innej zmiennej tablicowej:
1
#!/bin/bash
2
3
# Zmienna nr
4
nr=1
5
6
# Tworzymy zmienną tablicową:
7
sekwencje=(atp1 atp6 atp8 matR)
8
9
# Pętla 'for', która odczytuje argumenty ze zmiennej tablicowej
10
# Kolejne wartości są przypisywane zmiennej 'argument'
11
for sekwencja in ${sekwencje[@]}
12
do
13
# Drukowanie wartości
14
echo "Sekwencja $nr = $sekwencja"
15
nr=$((nr+1))
16
done
Copied!
A teraz coś bardziej złożonego. Odczytywanie danych z pliku i wykorzystanie ich w pętli.
Najpierw utwórz plik tekstowy o nazwie dane.tsv. Przedłużenie tsv oznacza tab-separated values, więc dane oddziel tabulatorem a nie spacją.
1
atp1 JX287332 ATAAATGACTAGAATTTGTTTTTCAATTGGAAGTGGTGCATATTGCGGTTGTTTCAGTAC
2
atp4 KX270773 TCAGAAGCATCTTCCATAGTCGTCTTGGTATTGGATCCGCTCTTCTGTTAGCATATTAAT
3
atp6 KU043163 AAACAAACGTTTTCCCCTCGCAACTCGGTTACTTTTACTTTTTTGTTATTTCGTAATCCC
4
atp8 KX270795 ATGCCTCAACTGGATAAATTCACTTATTTTACACAATTCTTCTGGTCATGCCTTTTCCTC
5
atp9 KX270765 TCAAAATACGAATGAAATCAAAAAGGCCATCATTAGGGCAAACAATGCGATCGCTTCGGT
Copied!
W każdej linii znajduje się kolejno: nazwa sekwencji, numer GenBank z którego pochodzi fragment sekwencji i w końcu fragment sekwencji. Zwróć uwagę, żeby na dole pliku nie było pustej linii.
Teraz skrypt, który odczyta dane, a następnie na ich podstawie utworzy pliki z danymi (czytaj_z_pliku.sh)
1
#!/bin/bash
2
# Nazwa katalogu na pliki wynikowe
3
katalog="sekwencje"
4
5
# Tworzymy katalog dla plików wynikowych
6
mkdir sekwencje
7
8
# Pętla, która odczytuje dane z pliku i przypisuje je do zmiennych
9
while read nazwa numer sekwencja
10
do
11
# Tworzymy nazwę pliku wynikowego: nazwa sekwencji + '.fasta`
12
file=${nazwa}.fasta
13
# Drukowanie nazwy pliku i odczytanych danych z linii
14
echo "$file: $nazwa - $numer - $sekwencja"
15
# Zapisanie opisu sekwencji do pliku
16
echo ">$numer" > $katalog/$file
17
# Dołączenie do pliku sekwencji
18
echo $sekwencja >> $katalog/$file
19
# Dane pochodzą z pliku 'dane.tsv'
20
done < dane.tsv
Copied!
Kolejna pętla odczytuje wszystkie pliki fasta z katalogu (odczyt_plikow.sh)
1
#!/bin/bash
2
3
katalog="sekwencje"
4
5
# Pętla, która odczytuje wszystkie pliki 'fasta' w katalogu
6
# oraz drukuje ich zawartość
7
for file in $katalog/*.fasta
8
do
9
echo "Plik: $file"
10
cat $file
11
done
Copied!

Podsumowanie

Powyższe elementy pisania skryptów w Bash-u to jedynie czubek góry lodowej. Zachęcam do dalszego samodzielnego zgłębiania tej cennej umiejętności. W dalszej części kursu pokażę bardziej praktyczne przykłady jej wykorzystania.