===== Wykonywanie poleceń z pliku ===== ==== Tryb (nie)interaktywnym ==== Powłoka uruchomiona bez argumentów działa w **trybie interaktywnym**. \\ Powłoka uruchomiona z nazwą pliku jako argumentem działa w **trybie nieinteraktywnym**. W trybie interaktywnym powłoka czyta polecenia ze standardowego wejścia, w trybie nieinteraktywnym z podanego pliku. \\ W trybie nieinteraktywnym powłoka nie wypisuje na ekran znaku zachęty i niektórych komunikatów diagnostycznych. ~~Zadanie.#~~ Umieść w pliku ''//plik//'' w osobnych liniach polecenie ''echo hello world'' oraz polecenie ''date''. Wykonaj ''sh //plik//''. ~~Zadanie.#~~ Umieść w pliku ''//plik//'' polecenie ''readlink /proc/$$/exe''. Ustaw uprawnienia wykonywania dla tego pliku (np. ''chmod +x //plik//''). Wykonaj ten plik przez ''.%%/%%//plik//''. Potem uruchom inną powłokę (np. ''csh'' lub ''zsh'') i znów wykonaj ''.%%/%%//plik//''. Jaka powłoka wykonuje domyślnie skrypty? ==== Shebang ==== Jeżeli pierwsza linia pliku ''//plik//'' zaczyna się od ''**#!**//prog//'', to uruchamiając ''//plik// //arg//...'' zostanie wykonane ''//prog// //plik// //arg//...''. \\ Taka pierwsza linia ma nazwę [[https://pl.wikipedia.org/wiki/Shebang|shebang]] (i jest respektowana zarówno przez powłoki, jak i np. jądro Linuksa). \\ **Shebang musi zaczynać się od pełnej ścieżki do pliku wykonywalnego** i powinien móc zawierać opcje. \\ Jeśli plik ''//plik//'' zaczyna się od ''#!//prog// //a1//...'', to wykonanie ''//plik// //a2//...'' uruchomi ''//prog// //a1//... //plik// //a2//...''. ~~Zadanie.#~~ Wpisz do pliku ''//plik//'' kolejno:
* ''#!/bin/ls -la'' * ''#!/usr/bin/bat'' * ''#!/usr/bin/env cowsay'' * ''#!/bin/rm'' Nadaj uprawnienia wykonania dla ''//plik//''u i wykonaj go (z każdym kolejnym shebangiem). Skrypty powłoki zawierają zwykle shebang **''#!/bin/sh''** (lub ''#!/bin/bash'', jeśli skrypt korzysta z rozszerzeń Basha) i tradycyjnie nadaje im się rozszerzenie **''.sh''**. \\ W wielu językach skryptowych tradycyjnie używa się shebanga (np. python, perl, ruby). ~~Zadanie.#~~ Napisz skrypt ''hello_world.sh'' który po uruchomieniu wypisze na ekranie ''hello world''. ===== Komentarze w skryptach ===== Komentarze zaczynają się od ''#'' (znajdującego się poza cudzysłowami, niepoprzedzonego odwróconym ukośnikiem, etc.).# to jest komentarz echo to nie jest komentarz # a to już jest echo to \# "też # nie jest" ${0#bash} komentarz===== Parametry pozycyjne (argumenty) i specjalne ===== [[https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_05|Parametry]] to (omawiane wcześniej) zmienne oraz parametry pozycyjne i specjalne. \\ Parametry pozycyjne i specjalne można rozumieć jako specjalne zmienne które można tylko odczytywać i których nazwy zaczynają się od czegoś innego niż litera bądź podkreślnik. ==== Parametry pozycyjne ==== Parametry pozycyjne to argumenty wykonania skryptu lub funkcji. Do pierwszych dziewięciu można odnosić się przez: \\ **''$1'', ''$2'', ..., ''$9''** \\ kolejne muszą używać notacji z nawiasami wąsatymi: \\ ''${1}'', ''${2}'', …, ''${9}'', **''${10}'', ''${11}'', ''${12}'', …, ''${42}'', …** ++++ Przykłady |
user@host ~ $ cat script.sh #!/bin/sh function funkcja(){ echo $1 $3 $2 } echo $1 $3 $2 funkcja sierotka ma rysia user@host ~ $ ./script.sh ala ma kota ala kota ma sierotka rysia ma user@host ~ $++++ Komendą **''[[https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#shift|shift]]''** można usunąć pierwszy parametr i zmniejszyć pozostałym numery o jeden. [[https://www.gnu.org/software/bash/manual/html_node/Positional-Parameters.html|Dokumentacja Basha]] ~~Zadanie.#~~ Napisz skrypt który wyświetli drugi i czwarty argument. ==== Parametry specjalne ==== Odnoszące się do parametry pozycyjnych: | **''$#''** | ilość parametrów pozycyjnych | | ''$*'' \\ ''$@'' | parametry pozycyjne poddane dzieleniu na tokeny | | ''"$*"'' | parametry pozycyjne sklejone w jeden token | | **''"$@"''** | parametry pozycyjne, każdy osobnym tokenem | ++++ Przykłady |
user@host ~ $ bash -s "the_first_argument" "the second argument" user@host ~ $ echo $# 2 user@host ~ $ ls $* ls: nie ma dostępu do 'the_first_argument': Nie ma takiego pliku ani katalogu ls: nie ma dostępu do 'the': Nie ma takiego pliku ani katalogu ls: nie ma dostępu do 'second': Nie ma takiego pliku ani katalogu ls: nie ma dostępu do 'argument': Nie ma takiego pliku ani katalogu user@host ~ $ ls $@ ls: nie ma dostępu do 'the_first_argument': Nie ma takiego pliku ani katalogu ls: nie ma dostępu do 'the': Nie ma takiego pliku ani katalogu ls: nie ma dostępu do 'second': Nie ma takiego pliku ani katalogu ls: nie ma dostępu do 'argument': Nie ma takiego pliku ani katalogu user@host ~ $ ls "$*" ls: nie ma dostępu do 'the_first_argument the second argument': Nie ma takiego pliku ani katalogu user@host ~ $ ls "$@" ls: nie ma dostępu do 'the_first_argument': Nie ma takiego pliku ani katalogu ls: nie ma dostępu do 'the second argument': Nie ma takiego pliku ani katalogu++++ Pozostałe: | **''$0''** | w skrypcie nazwa uruchamianego skryptu; w innym razie nazwa powłoki | | **''$$''** | identyfikator procesu powłoki | | **''$?''** | kod wyjścia ostatnio uruchomionej komendy | | **''$!''** | identyfikator ostatnio przeniesionego w tło procesu | | ''$-'' | ustawione opcje powłoki (patrz: ''man set'') | ++++ Przykłady |
user@host ~ $ sleep 1m & [1] 21738 user@host ~ $ sleep 1s user@host ~ $ echo $! 21738 user@host ~ $ echo $? 0 user@host ~ $ false user@host ~ $ echo $? 1 user@host ~ $ python <<< 'exit(123)' user@host ~ $ echo $? 123 user@host ~ $ echo $$ 21731 user@host ~ $ ps PID TTY TIME CMD 21731 pts/9 00:00:00 bash 21738 pts/9 00:00:00 sleep 21742 pts/9 00:00:00 ps++++ [[https://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html|Dokumentacja Basha]] ~~Zadanie.#~~ Napisz skrypt który wyświetli w kolejnych liniach: * wartość parametru ''$0'', * swój PID, * ilość argumentów, * listę argumentów. ~~Zadanie.#~~ Uruchom skrypt z poprzedniego zadania podając w różny sposób ścieżkę do niego. ~~Zadanie.#~~ Wykorzystując komendę ''readlink -f'' lub ''realpath'' (które dostając jako argument ścieżkę do pliku zwracają jego bezwzględną ścieżkę po rozwinięciu symlinków) napisz skrypt który wyświetli ścieżkę do katalogu w którym się znajduje. ===== Wyrażenia warunkowe i pętle ===== [[https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html|Dokumentacja Basha]] ==== if ==== Instrukcja warunkowa ma składnię:
if polecenie1 then kod wykonany jeśli polecenie1 zwróciło prawdę (wartość 0) elif polecenie2 then kod wykonany jeśli polecenie2 zwróciło prawdę else kod wykonany jeśli polecenie1 i polecenie2 nie zwróciło prawdy fiInstrukcje ''if'' i ''elif'' sprawdzają wartość **zwracaną** przez polecenie (a nie tekst wypisywany na standardowe wyjście czy wartość zmiennej). \\ Gałęzie ''elif'' i ''else'' są opcjonalne, ''elif'' może pojawiać się wielokrotnie. Żadna gałąź nie może zostać pusta; jeżeli potrzebna jest gałąź bez instrukcji, można wstawić w nią komendę **''[[https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_17|:]]''** lub ''[[https://pubs.opengroup.org/onlinepubs/9799919799/utilities/true.html|true]]''. Przykład bez używania znaków nowej linii:
if grep -q ^user: /etc/passwd; then echo "'user' present"; elif grep -q ^student: /etc/passwd; then echo "'user' absent, 'student' present"; else echo "neither present"; fi
~~Zadanie.#~~
Napisz skrypt który spróbuje usunąć plik o nazwie ''plik'' i jeśli usunięcie
pliku się powiedzie, to wyświetli napis ''udało się'', a jeśli nie to wyświetli
''nie udało się''.
~~Zadanie.#~~
Zmień skrypt tak, by zamiast pliku o nazwie ''plik'' usuwał plik podany jako
argument skryptu.
==== test ====
Polecenie **''test''** pozwala wykonywać m. inn. porównania zmiennych czy
sprawdzać istnienie / rodzaj pliku.
\\
Poza nazwą ''test'' to polecenie można wywołać przez **''[''**.
\\
Jedyna różnica między ''test'' i **''[''** polega na tym, że używając ''[''
trzeba dodać na koniec '']''.
\\
Np. ''test "ala" != "ala"'' jest identyczne z ''[ "ala" != "ala" ]''.
=== Testy łańcuchów znaków ===
^ Wyrażenie ^ Kiedy prawdziwe ^
|''[ "$X" ]'' \\ ''[ -n "$X" ]''| ''"$X"'' ma niezerową długość |
|''[ -z "$X" ]''| ''"$X"'' jest puste (ma zerową długość) |
|''[ "$X" = "$Y" ]''|Łańcuchy tekstu ''"$X"'' i ''"$Y"'' są identyczne |
|''[ "$X" != "$Y" ]''|Łańcuchy tekstu ''"$X"'' i ''"$Y"'' są różne |
Uwaga, pułapka: wyrażenie
test \( -z "$X" -o "$X" -le 4 \) -a ! -s "$Y" -a \( "$Z" -le 2 -o "$Z" -ge 12 \)
=== Zadania ===
~~Zadanie.#~~
Napisz skrypt który sprawdzi czy został wywołany z dokładnie z dwoma argumentami
i czy pierwszy z nich jest (arytmetycznie) mniejszy od drugiego. Jeśli nie,
wyświetl właściwy komunikat o błędzie i wyjdź ze skryptu (komendą ''exit'').
~~Zadanie.#~~
Napisz skrypt który sprawdzi czy pierwszy argument jest zwykłym plikiem do
którego bieżący użytkownik ma prawa zapisu. Jeśli nie, wyświetl odpowiedni
komunikat i wyjdź. Jeśli tak, dopisz bieżącą datę do tego pliku.
~~Zadanie.#~~
Napisz skrypt który sprawdzi czy pierwsza linia pliku (podanego jako argument)
ma treść ''#!/bin/sh'' i czy plik jest wykonywalny. Jeśli tak jest, wyświetl
''//nazwa pliku// jest wykonywalnym skryptem powłoki''.
~~Zadanie.#~~
Zmodyfikuj powyższy skrypt tak, by w razie potrzeby nadawał prawa dostępu i
dopisywał shebang do wskazanego pliku.
==== [[ … ]] i (( … )) – rozszerzenie Basha ====
Bash wprowadza dwie dodatkowe konstrukcji powłoki do testów / porównań:
* ''%%[[%% … ]]'' jako konstrukcja działająca jak ulepszone polecenie ''test'',
* ''%%((%% … ))'' do arytmetyki.
Wyrażenie arytmetyczne wewnątrz ''%%((%% … ))'' jest wyliczane i jeśli zwróci 0,
to ''%%((%% … ))'' zwraca fałsz, w przeciwnym razie prawdę.
\\
Wewnątrz ''%%((%% … ))'' można używać m. inn. operatorów ''=='',''!='', ''<'', ''<='',
''>'', ''>='' i ''… ? … : …''.
Działanie ''%%[[%% … ]]'' jest dość złożone. Ponad to co umie komenda ''['',
wyrażenie w podwójnych kwadratowych nawiasach:
* ''['' dzieli wyniki podstawień na słowa, ''%%[[%%'' zostawia je bez zmian, ++przykład|user@host ~ $ X="1 -eq 2" user@host ~ $ [ $X ] && echo T || echo F F # wykona porównanie 1 z 2 user@host ~ $ [ "$X" ] && echo T || echo F T # sprawdzi czy ciąg znaków '1 -eq 2' jest niepusty user@host ~ $ [[ $X ]] && echo T || echo F T # sprawdzi czy ciąg znaków '1 -eq 2' jest niepusty user@host ~ $ [[ "$X" ]] && echo T || echo F T # sprawdzi czy ciąg znaków '1 -eq 2' jest niepusty++ * wewnątrz ''%%[[%%'' **nie** są wykonywane dopasowania do nazw plików, ++przykład|
user@host ~ $ [[ *.txt ]] && echo T || echo F T # sprawdzi czy ciąg znaków '*.txt' jest niepusty user@host ~ $ [ *.txt ] && echo T || echo F -bash: [: nums.txt: oczekiwano operatora jednoargumentowego F # bash podstawił nazwy plików '*.txt', więc [ dostało złe argumenty++ * Operator ''='' i ''!='' dopasowują tekst do wzorca, a nie porównują teksty, ++przykład|
user@host ~ $ [ abc.log = *.log ] && echo T || echo F F user@host ~ $ [[ abc.log = *.log ]] && echo T || echo F T user@host ~ $++ * Dodano operator ''=~'' dopasowujący do wyrażenia regularnego, ++przykład|
user@host ~ $ [[ abc.txt =~ ^a.*\.txt$ ]] && echo T || echo F T++ * Operatory ''-a'' i ''-o'' zastąpiono przez ''&&'' i ''||'', a nawiasów okrągłych nie trzeba escape'ować, np:\\ ''if %%[[%% -f file && ( ! -s file || ! -w file ) ]]; then echo "'file' exists and is not empty or not writeable"; fi'' ==== case ==== Instrukcja wyboru ma składnię:
case wartość in wzorzec1) polecenia1 ;; wzorzec2) polecenia2 ;; *) polecenia3 ;; esac//Wartość// jest kolejno dopasowywana do //wzorzec1//, //wzorzec2//, … i przy znalezieniu pierwszego dopasowania wykonywane są odpowiednie polecenia. Dalsze dopasowania nie są brane pod uwagę. \\ Polecenia są dowolnym ciągiem poleceń zakończonym **'';;''**((Bash rozszerza składnię o '';&'' które przechodzi do komend z kolejnego wzorca i '';;&'' które dopasowuje kolejne wzorce.)). \\ Nie ma specjalnej składni na domyślne dopasowanie – używa się po prostu wzorca ''*''. Przykład bez używania znaków nowej linii:
case $(LANG= date "+%A") in Monday) echo "Ugh...";; S*day) tput bel; echo "Weekend!";; *) echo "a day.";; esac
~~Zadanie.#~~
Napisz skrypt który sprawdzi rozszerzenie pliku (podanego jako argument) i jeśli
plik ma rozszerzenie:
* ''pdf'' to wykona ''pdftotext //plik// -'',
* ''zip'' to wykona ''unzip -l //plik//'',
* jakiekolwiek inne, to wykona ''cat //plik//''.
===== Pętle =====
[[https://www.gnu.org/software/bash/manual/html_node/Looping-Constructs.html|Dokumentacja Basha]]
We wszystkich pętlach działają słowa kluczowe **''break''** i **''continue''**
do, odpowiednio, wyjścia poza pętlę i rozpoczęcia następnego przebiegu pętli.
==== for ====
Pętla ''for'' ma składnię:
for zmienna in wartość1 wartość2 … do polecenia doneW kolejnych przebiegach pętli kolejne //wartości// z listy są przypisywane do zmiennej //zmienna//. Po wyjściu z pętli //zmienna// ma ostatnią z przypisanych w pętli wartości. \\ Pominięcie ''in //wartość1// //wartość2// …'' spowoduje przyjęcie za listę wartości ''"$@"''. Często w miejsce listy wartości podaje się podstawienie które jest rozkładane na wiele wartości, np: \\ ''for NUM in {1..3} {7..9}'' → ''for NUM in 1 2 3 7 8 9'' \\ ''for NUM in $(seq -w 001 3)'' → ''for NUM in 001 002 003'' \\ ''for USERNAME in $(getent passwd | cut -f1 -d:)'' → ''for USERNAME in root student ...'' \\ ''for FILE in "$DIR"/*'' → ''for FILE in 'my dir/file 1' 'my dir/file 2' 'my dir/subdir%%'%%'' \\ ''for FILE in $(grep -il 'ldap' /etc/*.conf 2>/dev/null)'' → ''for FILE in /etc/autofs.conf /etc/ldap.conf ...''((Uwaga na spacje w nazwach plików! Ta wersja nie jest na nie odporna.)) Przykład bez używania znaków nowej linii:
for X in {1..3}; do echo -n "Przebieg $X: "; date +%N; done
~~Zadanie.#~~
Napisz skrypt, który dla każdego pliku podanego jako argument wyświetli: jego
nazwę, jego pierwszą linię i jego ostatnią linię.
~~Zadanie.#~~
Napisz pętlę która wyświetli wszystkie potęgi dwójki o wykładnikach od 1 do
wybranej wartości. \\ (Wartość potęgi można wyliczyć np. wyrażeniem
''$%%((2**%%//wykładnik//))''.)
==== for – rozszerzenie Basha ====
Bash wspiera też składnię ''for'' wyglądającą i działającą jak w C:
for ((i=0; i<5; i++)) do polecenia donePrzykład bez używania znaków nowej linii:
for ((i=3;i<=15;i+=3)); do for ((j=0;j
==== while i until ====
Pętla ''while'' ma składnię:
while polecenie1
do
polecenia2
done
Bliźniacza pętla ''until'' ma składnię:
until polecenie1
do
polecenia2
done
Pętla ''while'' wykonuje //''polecenia2''// do momentu aż //''polecenie1''//
zwraca prawdę. \\
Pętla ''until'' wykonuje //''polecenia2''// do momentu aż //''polecenie1''//
zwraca fałsz.
Przykład bez używania znaków nowej linii:
X=7; until [ $X -eq 1 ]; do [ $((X%2)) -eq 1 ] && X=$((3*X+1)) || X=$((X/2)); echo $X; done
~~Zadanie.#~~
Napisz pętlę która wyświetli wszystkie potęgi dwójki mniejsze niż podana liczba.
~~Zadanie.#~~
Napisz skrypt który utworzy nowy plik ''myProg_//NUM//.log'' o najniższej
możliwej wartości ''//NUM//'', a następnie przypisze jego nazwę do zmiennej
''LOG'' i umieści w tym pliku bieżącą datę.
===== Wczytywanie wartości podawanych przez użytkownika =====
==== read ====
Komenda
''**[[https://pubs.opengroup.org/onlinepubs/9799919799/utilities/read.html|read]]**
[//zmienna//]...'' wczytuje jedną linię ze standardowego wejścia, a następnie:
* (rozszerzenie Basha) jeżeli nie ma argumentów, ''read'' wpisuje linię do zmiennej REPLY,
* ''read'' dzieli wczytaną linię na słowa,
* każdej kolejnej zmiennej (podanej w liście argumentów) przypisuje kolejne słowo,
* jeśli słów jest mniej niż zmiennych, pozostałe zmienne są ustawiane na pustą wartość,
* jeśli słów jest więcej niż zmiennych, pozostałe słowa są doklejane do ostatniej zmiennej.
++++ Przykład |
user@host ~ $ read X Y Z
jeden "dwa trzy"
user@host ~ $ echo "X='$X' Y='$Y' Z='$Z'"
X='jeden' Y='"dwa' Z='trzy"'
user@host ~ $ read X Y Z
jeden
user@host ~ $ echo "X='$X' Y='$Y' Z='$Z'"
X='jeden' Y='' Z=''
user@host ~ $ read X Y Z
jeden dwa trzy cztery pięć
user@host ~ $ echo "X='$X' Y='$Y' Z='$Z'"
X='jeden' Y='dwa' Z='trzy cztery pięć'
user@host ~ $ read
jeden dwa trzy cztery pięć
user@host ~ $ echo "'$REPLY'"
' jeden dwa trzy cztery pięć'
++++
|Komenda ''read'' jest często wykorzystywana we wzorcach:||
|
polecenie | while read X
do
...
done
1. ''polecenie'' jest wykonywane współbieżnie z pętlą \\ 2. wyniki polecenia mogą mieć spacje w linii \\ Uwaga: tylko jedna z komend z potoku (domyślnie pierwsza) działa
pierwszoplanowo, przez co przypisania zmiennych wewnątrz pętli
(działającej w tle) nie będą widoczne dalej w skrypcie|
while read X Y
do
...
done << EOF
wartoscX1 wartoscY1
wartoscX2 wartoscY2
EOF
ustawia się po parę/trójkę/… zmiennych naraz|
| Do poprawnego działania należy zapewnić żeby nic we wnętrzu pętli nie czytało ze standardowego wejścia||
~~Zadanie.#~~
Napisz skrypt który dopisuje do pliku o nazwie ''//log//'' linię tekstu wczytaną
ze standardowego wejścia poprzedzoną bieżącą datą.
~~Zadanie.#~~
Napisz skrypt który wczytuje liczby (znajdujące się w osobnych liniach) do
momentu aż nie pojawi się pusta linia i następnie wyświetla najmniejszą z
wczytanych liczb.
~~Zadanie.#~~
Napisz skrypt który odczytuje ze standardowego wejścia linie złożone z dwóch
liczb i po przeczytaniu każdej linii wypisuje iloczyn liczb z tej linii.
Skrypt ma działać do czasu aż odczytywanie z wejścia nie napotka końca pliku.
==== select – rozszerzenie Basha ====
__Pętla__ ''[[https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html#index-select|select]]'' ma składnię identyczną jak pętla for:
select ZMIENNA in wartość1 wartość2 …
do
polecenia
done
Ta pętla:
- wyświetla numerowaną listę wartości,
- czeka na wpisanie linii wejścia przez użytkownika,
- przeczytaną linię przypisuje zmiennej ''REPLY''
- jeśli linia jest jednym z numerów, do ''//ZMIENNA//'' przypisuje odpowiadającą ''//wartość//…''
- wykonuje ''//polecenia//''
- wraca do kroku 2
~~Zadanie.#~~
Przeanalizuj i przetestuj poniższy kod. Zmień go tak, by dopiero wpisanie ''q'' przerywało pętlę.
select FILE in /etc/logrotate.d/*
do
[[ $FILE ]] || continue
echo "===$FILE==="
cat "$FILE"
break
done
~~META:
language = pl
~~