BASH:
-----

praca z katalogami =>
dirs                    - wyświetla, jakie katalogi są na stosie, stos liczymy od lewej licząc od 0
pushd                   - wstawia katalog na stos
pushd +n/-n             - przechodzi do wskazanego katalogu ze stosu (licząc od zera!)
popd                    - usuwa katalog z wierzchołka stosu, czyli nr 0
popd +n/-n              - usuwa ze stosu wskazany katalog
!!                      - wykonaj ostatnio wydane polecenie
!n                      - wykonaj polecenie o nr "n" z historii poleceń
!-n                     - wykonaj polecenie "n" numerów wstecz względem bieżącego
!string                 - wykonaj polecenie rozpoczynające się od "string"
!?string?               - wykonaj polecenie z historii zawierające "string"
!string:s/stary/nowy/   - wykonaj polecenie zaczynające się od "string" po uprzednim podstawieniu
                         "nowego" w miejsce "starego"
fc -l                   - wyświetli ostatnie 16 linii pliku historia bash'a
fc -l -4                - wyświetli 4 ostatnie polecenia z historii bash'a

zmienne powłoki =>
zmienna=wartość (przypisanie zmiennej wartości), echo $zmienna wyświetla wartość
${zmienna:='wartość'} (jeżeli zmienna jest nie ustawiona, lub pusta, przypisz jej 'wartość'), np. ${file_type:='doc'}
To samo co w/w ale w inny sposób: if [ !file_type ] then file_type=doc fi
zmienne tablicowe:
np.     miasta[0]=Gdynia
       miasta[1]=Sopot
       miasta[2]=Gdańsk
odwołanie się do konkretnej wartości tablicy: echo ${miasta[1]}, bez {} wyjdzie błędna wartość!
wyświetlenie całej tablicy: echo $miasta[*], jeśli [@] każdy element tablicy jako oddzielny napis w ""
wyświetla ilość elementów tablicy (przydaje się do pętli): echo ${#miasta[*]}
definicja zmiennej typu całkowitego (integer) i przypisanie jej wartości początkowej: typeset -i licznik=1
nadanie zmiennej atrybutu "tylko do odczytu" (read only) i przypisanie wartości: typeset -r mode=update

cytowanie =>
'' - wszystko wewnątrz pojedyńczego apostrofu jest traktowane jako stała tekstowa, żadne znaki specjalne nie są interpretowane
"" - znaki specjalne są interpretowane ($`\, nie obejmuje *?'), np.$zmienna podstawi wartość zmiennej,
    ochrona spacji (podczas interpretacji przez shell nadmiarowe spacje są wycinane, a w "" pozostają nietknięte)
\  - shell nie interpretuje pojedyńczego znaku za ukośnikiem, nie działa pomiędzy''
    na końcu linii \ oznacza złamanie wiersza i kontynuację napisu/polecenia w kolejnym poniższym wierszu
`` - wykonuje polecenie shella/unixa i wstawia jego wynik

debugowanie =>
* shell wyświetla każdy przeczytany wiersz przed jego wykoaniem:
 sh -v skrypt.sh
* shell wyświetli każde wykonywane polecenie PO dokonaniu wszystkich podstawień w wierszu polecenia:
 sh -x skrypt.sh
* shell wyświetla wiersz skryptu przed i po dokonaniu podstawień:
 sh -vx skrypt.sh
* wszystkie opcje przydatne przy debugowaniu można ustawić z użyciem "set", np. set -x,
 opcje aktualnie ustawione są zawarte w zmiennej $-:
 echo $-
* można ustawić wewnątrz skryptu włączanie debugowania, np.:
 if [ "${DEBUG} = "ON" ] then set -x/-v fi     # wiersz ten powinien być drugim w skrypcie po !# i komentarzach wstępnych
 aby debugować skrypt z tą instrukcją przed uruchomieniem skryptu:
 DEBUG=ON      /       DEBUG=OFF
 export DEBUG
 lub w/w uprościć aliasem:
 alias debug='DEBUG=ON"; export DEBUG'
* w przypadku vi/vim z poziomu edytora debuguje się tak:
 :w
 :!sh -vx %    # znak % jest zastępowany przez vi nazwą bieżącego pliku

zmienne specjalne shella =>
$#      - liczba argumentów
$0      - nazwa skryptu shell'a
$1,$2,... - argumenty pozycyjne przekazywane do shella
$*      - rozwijanie do: "$1 $2 $3 ..."
$@      - rozwijanie do "$1" "$2" "$3" ...
$-      - opcje shella z polecenia set
$?      - kod powrotu ostatniego polecenia
$$      - nr procesu bieżącego shella
$!      - nr procesu ostatniego zadania wsadowego
Instrukcja shella "getopts" pozwala na porządkowanie argumentów, np.
program.sh -abcdef
rozrzuci na
program.sh -a -b -c -d -e -f
Następnie przy pomocy case decydujemy, jak prrogram reaguje na każdy arrgument, również niewłaściwy
i jego brak.
Za pomocą polecenia "sety" można przypisywać wartości zmiennym $1,$2,$3...
Aby zmiennym tym przypisać nazwy wszystkich plików z bieżącego katalogu:
set - *

Przy przetwarzaniu opcji i argumentów wprowadzonych do skryptu z linii komend wykorzystuje się:
- zmienne specjalne $* i $@
- funkcję bash'a "opts"
- przesuwanie argumentów z wykorzystaniem funkcji "shift"
(szczegóły: "UNIX, programowanie w shellu" str.206-217

test ( pełen wykaz "man test") =>
-r plik         - prawda, jeżeli plik istnieje i ma prawa do czytania
-w              -                                            pisania
-x              -                                            wykonywania
-f              - prawda, jeżeli plik istnieje, i jest zwykłym plikiem
-d              -                                               katalogiem
-p              -                                       nazwanym potokiem (FIFO)
-s              -                               i ma rozmiar większy od 0
-z s1           - prawda, jeżeli długość napisu s1 wynosi 0
-n s1           -                                  jest różna od 0
s1 = s2         - prawda, jeżeli napisy s1 i s2 są identyczne
s1 != s2        -                               nie są identyczne
s1              - prawda, jeżeli napis s1 nie jest pusty
n1 -eq/-ne/-gt/-ge/-lt/-le n2 - prawda, jeżeli liczby n1 i n2 są równe/nie_równe/większe/większe_lub_równe/mniejsze/mniejsze_lub_równe
test = [ warunek ]
np.
test "( -s plik ) -a ( -r plik )"; echo $? - spr. czy "plik" ma rozmiar >0 i ma prawo do czytania
test -w nazwa -o -p nazwa; echo $?         - spr. czy "nazwa" ma prawo do pisania i czy jest nazwanym potokiem
if [ -r plik ]                             - spr. czy plik istnieje i ma prawo do czytania

expr =>
peratory arytmetyczne expr w-g priorytetu (tylko argumenty CAŁKOWITE!)
=       - równa się
!=      - nie równa się
><      - większe/mniejsze niż
>=<     - większe/mniejsze lub równe
+       - dodawanie
-       - odejmowanie
*       - mnożenie (trzeba poprzedzać \ żeby nie było podstawiania plików!!!, np. i = `$1 \* 15`
**      - potęgowanie, "x" do "y"
/       - dzielenie
%       - reszta z dzielenia
bc      - do dzielenia liczb rzeczywistych trzeba użyć zewnętrznego programu bc
         np. echo"5/2" | bc -l

$((...)) => obliczanie wyrażenia arytmetycznego (tylko bash!)
np.
       while [ licznik_pętli -le 25 ]
               do
                       echo ${licznik_pętli}
                       licznik_pętli=$((licznik_pętli +1))
               done

operacje na stringach =>
zmienna="string"                - przypisanie zmiennej stringu (łańcucha txt)
echo $string                    - wyświetlenie zmiennej
echo${#string}                  - zwraca długość stringu (ilość liter}
string3=$string1$string2        - dodanie dwóch stringów do siebie
znajdowanie pozycji substringu:
    string1="Bash jest fajny"
    slowo="fajny"
    expr index "$string1" "$slowo"
    wynik = 11 (pozycja w długim stringu, od której zaczyna się szukane słowo)
wycinanie pod-stringu ze stringu:
       zdanie="Debian jest najlepszym Linuxem"
       wycięcie słowa "Debian":
       echo ${zdanie:0:6}
wycięcie pod-stringu od zadanej pozycji:
       echo ${zdanie:7} => "jest najlepszym linuxem"
       zamiana fragmentu stringu:
       echo ${zdanie/Debian/PLD} => "PLD jest najlepszym Linuxem"
usuwanie fragmentu stringu:
       echo ${zdanie/najlepszym} => "Debian jest Linuxem"
       usuwanie pojedyńczych znaków ze stringu:
       numer="721-049-185"
       echo ${numer/-}  => usunięcie pierwszego wystąpienia znaku
       echo ${numer//-} => usunięcie wszystkich wystąpień znaku
       w/w nie modyfikuje stringu, a jedynie wyświetla rezultat,
       żeby zmodyfikować na stałe string, trzeba zmiennej przypisać rezultat:
       echo $numer
       numer=${numer//-}
zamiana liter duże/małe w stringu:
       twierdzenie1="debian to unix"
       twierdzenie2="BSD TO UNIX"
       echo ${twierdzenie1^^}       => "DEBIAN TO UNIX"
       echo ${twierdzenie2,,}       => "bsd to unix"
       echo ${twierdzenie1^}        => "Debian to unix"
       echo ${twierdzenie1,}        => "debian to unix"
       echo ${twierdzenie2,,[to]}   => "BSD to UNIX"
       echo ${twierdzenie1^^[unix]} => "debian to UNIX"

konstrukcje sekwencyjne =>
;       - ogranicznik polecenia
()      - grupowanie poleceń do wykonania w podshellu
{}      -                                   bieżącym shellu, nawias zamykający musi być poprzedzony znakiem ";"!!!
       np. { polecenie_1; polecenie_2 | grep;} | wc
``      - podstawianie poleceń, np. `date`
&&      - sprawdzenie kodu powrotu, i w przypadku prawdy wykonanie polecenia
       np. polecenie_1 && polecenie_2 - wykonaj polecenie_2 jeśli polecenie_1 zakończyło się powodzeniem (kod powrotu 0)
||      -                                         fałszu
       np. polecenie_1 || polecenie_2 - wykonaj polecenie_2 jeśli polecenie_1 zakończone niepowodzeniem (kod powrotu 1 lub -1)

konstrukcje warunkowe =>

IF-THEN-ELSE
if [ warunek testu ]
       then
               lista_poleceń
       else
               lista_poleceń
fi

IF-THEN-ELIF-THEN-ELSE-FI (spr. najpierw pierwszy warunek, potem drugi, i gdy żaden nie jest spełniony wykonuje instrukcje po ELSE)

np.
    if [ -t 0 ]        #jeśli standardowym wyjściem jest terminal
            then
                    echo "Error Message"
            else       #polecenie jest wykonywane w drugim planie
                    echo "Error Message" | mail ${LOGNAME}
    fi
    if [ "$PATH" ]
            then
                    echo $PATH
            else
                    echo "No PATH is specified"
    fi
    if [ -d ${zmienna} ]
            then
                    przetwarzaj katalog ${zmienna}
    elif [ -f ${zmienna} ]
            then
                    przetwarzaj plik ${zmienna}
    else
            echo "Błąd!"
    fi
       if [ $(whoami) = 'root'; then
               echo "Jesteś adminem!"
       fi

CASE-ESAC (wartość/spr ważniejsze, muszą być na początku, im dalsze tym mniej ważne)
case $zmienna in
       wartość_1)
               czynności_1
               ;;
       wartość_2)
               czynności_2
               ;;
       wartość_3|wartość_4)
               czynności_3
               ;;
       *)
               czynność_domyślna j. żaden z w/w warunków nie był spełniony
esac

Przetwarzanie parametrów przekazanych do procedury przy pomocy pętli CASE:
case $# in
       0)
       echo "Enter file name: "
       read argument1
       ;;
       1)
       argument1=$1
       ;;
       *)
       echo "Invalid number of arguments..."
       echo "Syntax: command filename"
       exit1
       ;;
esac
# tutaj rozpoczyna się główne przetwarzanie

Skrypt, który zależnie od miesiąca, odpala skrypt/program dla tego miesiąca:
current_month=`date + '%m'`
case ${current_month} in
       01)
       January         # TU: January to nazwa skryptu odpalanego, jeżeli jest styczeń
       ;;
       02)
       February        # j/w
[...]
       12)
       December
       ;;
       *)
       echo" "Problem with date command"
       ;;
esac
lub inaczej w/w:
case $current_date in
       01 | Jan | January)
       January
       ;;
       02 | Feb | February)
       ;;
[...]
esac

Pętla FOR w stylu języka C:

for ((initialize ; condition ; increment));
do
       [commands]
done

for item in [list];
do
       [commands]
done

UWAGA: Jeśli nie podano [list] wartości, bash domyślnie przyjmie $*, czyli wszystkie parametry przekazane do skryptu w linii komend!!!

np.
for ((i=0 ; i<10 ; i++))
do
       echo "Wi-taj Przyjacielu!"
done

to samo co w/w inaczej:
for i in {1..10};
do
       echo "Wi-taj Przyjacielu!"
done

For jak ls:
for i in ~/*;
do
       echo $i
done

zamiana/poprawienie jednego słowa w wielu plikach:
for file in wzorzec_pliku    # np. .bash*
do
       ed $file <<!
       g/shell/s//Shell/g
       w
       q
       !
done

Pętla odliczająca w dół:
for ((i=10 ; i>0 ; i--));
do
       echo $i
done

Np. wykonanie czynności na wszystkich plikach w zadanych katalogach:
for dir in katalog1 katalog2 katalog3
do
       cd $dir
               for file in *
               do
                       if [ -f $file ]         # sprawdzenie czy plik, czy katalog, działamy tylko na plikach tu
                       then
                               process $file   # wykonujemy czynność na pliku
                       fi
               done
       cd $dir
done

for file in plik1 plik2 plik3...
do
       line < $file
done | wc > header_count

Pętla WHILE:

while [condition];
do
       [commands]
done

np.
num=1
while [ $num  -le 10 ];
do
       echo $(($num * 3))
       num=$(($num+1))
done
month=1
while [ ${month} -le 12 ]
do
       process ${month}
month=`expr $month + 1`
done

while [ "$variable' = something ]
do
       process $variable
done

ls |\
while [ -r $file ]
do
       process $file
done

Warunkowa pętla nieskończona WHILE:

while [ 1 ]
do
       if [ warunek zakończenia ]
       then
               break           # break - przerwanie pętli
       else
               przetwarzaj
       fi
done

while [ 1 ]
do
       echo "Enter file name:"
       read filename
       if [ -r $filename ]
               then process $filename
               else continue   # continue - kontynuacja pętli pomimo negatywnego testu
       fi
       echo "Processed file ${filename}"
done

Połączenie FOR i WHILE + potoki:

for file in rozdział?
do
       cat $file | while read line   # czytaj wiersz
       do
               process $line
       done
done

Pętla UNTIL:
(we WHILE najpierw sprawdza się warunek, w UNTIL po pierwszym przebiegu, czyli pętla UNTIL wykona się zawsze conajm,niej raz).

until [condition];
do
       [commands]
done

Pętle nieskończone (czasem przydatne):

for ((;;));
do
       [commands]
done

while [true];
do
       [commands]
done

SELECT (do tworzenia menu):

select zmienna_sterująca in [ słowo1 słowo2 słowoN]
do
       lista poleceń
done

Przykład menu:

PS3="Menu aplikacji konsolowych:"
select option in  "Poczta" "Newsy" "WWW" "Gopher" "SSH" "IRC" "mc"
do
       echo "Wybierz $option"
       case #REPLAY in
               1) echo "Poczta e-mail mutt" ;;
               2) echo "Newsy slrn" ;;
               3) echo "WWW Lynx" ;;
               4) echo "Gopher gopher" ;;
               5) echo "SSH Puchar" ;;
               6) echo "IRC catgirl PIRC" ;;
               7) echo "MidnightCommander" ;;
               8) echo "Wyjście z menu" exit ;; # w przykładzie jest bez "echa", więc z exitem trzeba sprawdzić...
       esac
done

Definicja FUNKCJI w bash:

funkcja () {
       kod funkcji
}
Odpala się funkcję poprzez wywołanie nazwy.
W nazwie nie może być znaków specjalnych.
Funkcje nie zwracają "własnego" kodu powrotu, a jedynie kod powrotu ostatniego polecenia w funkcji $?
Zmienne lokalne i globalne w funkcjach:
Np.
#! /bin/bash
V1='A'
V2='B'
moja_funkcja () {
       local V1='C' V2='D'
       echo "Wewnątrz moja_funkcja(): V1=$V1, V2=$V2"
}
echo "Przed wywołaniem moja_funkcja(): V1=$V1, V2=$V2"
moja_funkcja
echo "Po wywołaniu moja_funkcja(): V1=$V1, V2=$V2"
Wnioski:
- z poziomu funkcji można czasowo zmieniać zmienne globalne skryptu
- zmienne lokalne funkcji o tych samych nazwach, co globalne, na czas pracy funkcji "przykrywają" zmienne globalne

Funkcja rekurencyjna wywołująca sama-siebie:
Np. obliczanie silni
#! /bin/bash
silnia () {
       if [ $1 -le 1 ]; then
            echo 1
       else
            last=$(silnia $(( $1-1 )))
            echo $(( $1 * last ))
       fi
}
echo -n "5!= "
silnia 5
echo -n "4!= "
silnia 4
echo -n "3!= "
silnia 3
echo -n "2!= "
silnia 2
echo -n "1!= "
silnia 1
echo -n "0!= "
silnia 0


Automation With Bash
--------------------

Automating backups with bash script

  Taking backups is something that we all do on a regular basis so why not automate it? Take a look at the following backup.sh script:
#!/bin/bash

backup_dirs=("/etc" "/home" "/boot")
dest_dir="/backup"
dest_server="server1"
backup_date=$(date +%b-%d-%y)

echo "Starting backup of: ${backup_dirs[@]}"

for i in "${backup_dirs[@]}"; do
sudo tar -Pczf /tmp/$i-$backup_date.tar.gz $i
if [ $? -eq 0 ]; then
echo "$i backup succeeded."
else
echo "$i backup failed."
fi
scp /tmp/$i-$backup_date.tar.gz $dest_server:$dest_dir
if [ $? -eq 0 ]; then
echo "$i transfer succeeded."
else
echo "$i transfer failed."
fi
done

sudo rm /tmp/*.gzecho "Backup is done."

  So, you first created an array named backup_dirs that stores all directory names that we want to backup. Then, you created three other variables:
    * dest_dir: To specify the backup destination directory.
    * dest_server: To specify the backup destination server.
    * backup_time: To specify the date of the backup.

  Next, for all the directories in the backup_dirs array, [54]create a gzip compressed tar archive in /tmp, then [55]use the scp command to send/copy the backup to the
  destination server. Finally, remove all the gzip archives from /tmp.

  Here is a sample run of the backup.sh script:
kabary@handbook:~$ ./backup.sh
Starting backup of: /etc /home /boot
/etc backup succeeded.
etc-Aug-30-20.tar.gz 100% 1288KB 460.1KB/s   00:02
/etc transfer succeeded.
/home backup succeeded.
home-Aug-30-20.tar.gz 100% 2543KB 547.0KB/s   00:04
/home transfer succeeded.
/boot backup succeeded.
boot-Aug-30-20.tar.gz 100%  105MB 520.2KB/s   03:26
/boot transfer succeeded.
Backup is done.

  You may want to run take the backups every day at midnight. In this case, you can schedule the script to [56]run as a cron job:
kabary@handbook:~$ crontab -e
0       0       *       *       * /home/kabary/scripts/backup.sh