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