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