# Skrypty powłoki

[← Spis treści](/filogenetyka/readme.md)

## 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:

```
#!/bin/bash

# Pierwszy skrypt

echo "Witaj Świecie!"
pwd
ls
# Tworzenie katalogu i pliku tekstowego
mkdir TMP
touch TMP/tekst.txt
# Umieszczenie tekstu w pliku
echo "Tekst w pliku" > TMP/tekst.txt
# Wypisanie zawartości pliku
cat TMP/tekst.txt
```

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:

```
chmod u+x pierwszy_skrypt.sh 
```

Teraz można skrypt uruchomić:

```
$: ./pierwszy_skrypt.sh 
Witaj Świecie!
/home/student/skrypty
pierwszy_skrypt.sh
Tekst w pliku
```

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ą:

```
#!/bin/bash

# Przykłady wykorzystania zmiennych

#Tworzymy dwie zmienne przechowujące łańcuchy znaków:
plik=tekst.txt
# Tu w łańcuchu znaków znajdują się spacje, dlatego konieczne są cudzysłowy
tekst="To jest tekst, który wpiszemy do pliku"

# Odwołania do wartości zmiennych
echo "Nazwa pliku: $plik"
echo $tekst
echo "Wpisuję do pliku:  $tekst" > $plik
echo "Zawartość pliku:"
cat $plik
```

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ń:

```
#!/bin/bash

# Zmienne przechowujące liczby
liczba1=12
liczba2=6

# Wykonujemy działanie arytmetyczne - wyrażenie zamknięte w $[]
suma=$[liczba1+liczba2]
echo "suma: $liczba1 + $liczba2 = $suma"

# inny sposób - wyrażenie zamknięte w $(())
iloczyn=$((liczba1*liczba2))
echo "iloczyn: $liczba1 * $liczba2 = $iloczyn"

# Jeszcze inny sposób - polecenie let
# Uwaga - brak znaku $ przy nazwach zmiennych do których się odwołujemy
let roznica=liczba1-liczba2
echo "różnica: $liczba1 - $liczba2 = $roznica"

# Można oczywiście mieszać zmienne i liczby a wynik niekoniecznie
# trzeba przypisywać do zmiennej

echo "kolejne obliczenia: $liczba1 + 20 = $[liczba1 + 20]"
```

## 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 `$@`.

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.

```
#!/bin/bash

# wszystkie argumenty przekazywane :
echo "Argumenty: $@"

# Wywołujemy poszczególne argumenty
echo "argument 1: $1"
echo "argument 2: $2"

# Argumenty, które mają konkretne znaczenie lepiej 
# przypisać do zmiennych których nazwy tłumaczą ich znaczenie
plik=$1
tekst=$2

# Teraz posługujemy się już utworzonymi zmeinnymi
echo $tekst > $plik
echo "Zawartość pliku $plik"
cat $plik
```

Teraz go uruchom z dwoma parametrami:

```
$: ./argumenty.sh argumenty.txt "Tekst przekazany jako argument"

Argumenty: argumenty.txt Tekst przekazany jako argument
argument 1: argumenty.txt
argument 2: Tekst przekazany jako argument
Zawartość pliku argumenty.txt
Tekst przekazany jako argument
```

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 `$@` 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`):

```
#!/bin/bash

# Tworzenie zmiennej tablicowej
sekwencje=(atp1 atp6 matR cox1)

# Drukowanie na raz całej zawartości zmiennej tablicowej
echo ${sekwencje[@]}

# Drukowanie kolejnych wartości
echo ${sekwencje[0]}
echo ${sekwencje[1]}
echo ${sekwencje[2]}
echo ${sekwencje[3]}
```

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.

Wynik:

```
atp1 atp6 matR cox1
atp1
atp6
matR
cox1
```

### 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`)

```
#!/bin/bash

# Dwa sposoby przypisania do zmiennej wyniku działania polecenia
# lista plików w katalogu /usr/bin zaczynających się od z
pliki=`ls /usr/bin/z*`
# linie zawierające `plik` w bieżącym skrypcie
linie=$(grep plik $0)

# Drukowanie zawartości zmiennych
echo "**** Znalezione pliki:"
echo "$pliki"
echo "**** Znalezione linie:" 
echo "$linie"
```

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`

```
#!/bin/bash

# Zmienna nr
nr=1

# Pętla 'for', która odczytuje argumenty przekazane do skryptu
# przechowywane w zmiennej tablicowej $@
# Kolejne argumenty są przypisywane zmiennej 'argument'
for argument in $@
do
	# Drukowanie wartości
	echo "Argument $nr = $argument"
	# Zwiększanie wartości zmiennej 'nr' o 1
	nr=$((nr+1))
done
```

Teraz wywołaj skrypt z odpowiednimi parametrami:

```
$: ./petla_argumenty.sh atp1.fasta atp6.fasta atp8.fasta matR.fasta
Argument 1 = atp1.fasta
Argument 2 = atp6.fasta
Argument 3 = atp8.fasta
Argument 4 = matR.fasta
```

Zamiast argumentów można użyć innej zmiennej tablicowej:

```
#!/bin/bash

# Zmienna nr
nr=1

# Tworzymy zmienną tablicową:
sekwencje=(atp1 atp6 atp8 matR)

# Pętla 'for', która odczytuje argumenty ze zmiennej tablicowej 
# Kolejne wartości są przypisywane zmiennej 'argument'
for sekwencja in ${sekwencje[@]}
do
	# Drukowanie wartości
	echo "Sekwencja $nr = $sekwencja"
	nr=$((nr+1))
done
```

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ą.

```
atp1	JX287332	ATAAATGACTAGAATTTGTTTTTCAATTGGAAGTGGTGCATATTGCGGTTGTTTCAGTAC
atp4	KX270773	TCAGAAGCATCTTCCATAGTCGTCTTGGTATTGGATCCGCTCTTCTGTTAGCATATTAAT
atp6	KU043163	AAACAAACGTTTTCCCCTCGCAACTCGGTTACTTTTACTTTTTTGTTATTTCGTAATCCC
atp8	KX270795	ATGCCTCAACTGGATAAATTCACTTATTTTACACAATTCTTCTGGTCATGCCTTTTCCTC
atp9	KX270765	TCAAAATACGAATGAAATCAAAAAGGCCATCATTAGGGCAAACAATGCGATCGCTTCGGT
```

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`)

```
#!/bin/bash
# Nazwa katalogu na pliki wynikowe
katalog="sekwencje"

# Tworzymy katalog dla plików wynikowych
mkdir $katalog

# Pętla, która odczytuje dane z pliku i przypisuje je do zmiennych
while read nazwa numer sekwencja
do
	# Tworzymy nazwę pliku wynikowego: nazwa sekwencji + '.fasta`
	file=${nazwa}.fasta
	# Drukowanie nazwy pliku i odczytanych danych z linii
	echo "$file: $nazwa - $numer - $sekwencja"
	# Zapisanie opisu sekwencji do pliku
	echo ">$numer" > $katalog/$file
	# Dołączenie do pliku sekwencji
	echo $sekwencja >> $katalog/$file
# Dane pochodzą z pliku 'dane.tsv'	
done < dane.tsv
```

Kolejna pętla odczytuje wszystkie pliki `fasta` z katalogu (`odczyt_plikow.sh`)

```
#!/bin/bash

katalog="sekwencje"

# Pętla, która odczytuje wszystkie pliki 'fasta' w katalogu
# oraz drukuje ich zawartość
for file in $katalog/*.fasta
do 
	echo "Plik: $file"
	cat $file
done
```

### 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.

[← Spis treści](/filogenetyka/readme.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ggoralski.gitbook.io/filogenetyka/cwiczenia/15-skrypty.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
