===== 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/9699919799/utilities/V3_chap02.html#tag_18_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/9699919799/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
fi
Instrukcje ''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. 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 ''[ $X = $Y ]'' będzie działać jeśli ''$X'' i ''$Y'' rozwiną się każde do jednego słowa, ale jeśli któreś będzie puste bądź będzie zawierać spację, to spowoduje błąd składniowy. === Testy arytmetyczne === ^ Wyrażenie ^ Kiedy prawdziwe ^ |''[ "$X" -eq "$Y" ]''|liczba ''"$X"'' jest równa ''"$Y"''| |''[ "$X" -ne "$Y" ]''|liczba ''"$X"'' jest różna ''"$Y"''| |''[ "$X" -lt "$Y" ]''|liczba ''"$X"'' jest mniejsza ''"$Y"''| |''[ "$X" -le "$Y" ]''|liczba ''"$X"'' jest mniejsza bądź równa ''"$Y"''| |''[ "$X" -gt "$Y" ]''|liczba ''"$X"'' jest większa ''"$Y"''| |''[ "$X" -ge "$Y" ]''|liczba ''"$X"'' jest większa bądź równa ''"$Y"''| === Testy plików (wybór) === ^ Wyrażenie ^ Prawdziwe kiedy plik ''"$X"'' istnieje i… ^ |''[ -e "$X" ]''| (po prostu istnieje) | |''[ -s "$X" ]''|nie jest pusty | |''[ -f "$X" ]'' \\ ''[ -d "$X" ]'' \\ … | jest zwykłym plikiem \\ jest katalogiem \\ … | |''[ -r "$X" ]'' \\ ''[ -w "$X" ]'' \\ ''[ -x "$X" ]''|użytkownik może odczytywać plik \\ użytkownik może zapisywać do pliku \\ użytkownik może wykonywać plik | === Negowanie, grupowanie=== ''**!** //arg//'' neguje //arg//. \\ ''//arg1// **-a** //arg2//'' oznacza //arg1// oraz //arg2// \\ ''//arg1// **-o** //arg2//'' oznacza //arg1// lub //arg2// \\ nawiasy okrągłe ''( … )'' pozwalają grupować, ale trzeba je escape'ować żeby bash nie traktował ich jako znaki specjalne. Przykład: 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
done
W 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 $(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
done
Przykł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/9699919799/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 | 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. ==== 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 ~~