Prowadzący: dr inż. Sebastian Bielski
Kontakt: bolo@mif.pg.gda.pl
Konsultacje:   (gmach główny)

Podstawy systemów operacyjnych i programowania - informacje i materiały:

Programowanie

Pętle, ilość powtórzeń nieokreślona

1. Powtórzenia wykonywane, gdy spełniony jest dany warunek logiczny (warunek sprawdzany przed wykonaniem).
Pascal
while (warunek) do
begin
instrukcje;
end;

Przykład:
k:=0;
while (k>=0) and (k<=10) do
begin
writeln('podaj k');
readln(k);
end;

C++
while (warunek) {
instrukcje; }

Ten sam przykład:
k=0;
while (k>=0 && k<=10) {
cout << "podaj k" << endl;
cin >>k;}


2. Powtórzenia wykonywane, gdy spełniony jest dany warunek logiczny (warunek sprawdzany po wykonaniu).
Pascal
repeat
instrukcje;
until (warunek)

Przykład:
k:=0;
suma:=0;
repeat
k:=k+1;
suma:=suma+k;
until suma>=100;

C++
do {
instrukcje; }
while (warunek);

Ten sam przykład:
k=0; suma=0;
do {
k=k+1; suma+=k;}
while (suma < 100);

Zwróćmy uwagę na fakt, że w Pascalu powtórzenia przestają się wykonywać w momencie, gdy warunek zostanie spełniony (czyli wykonują się, gdy warunek jeszcze nie jest spełniony), natomiast w C++ powtórzenia przestają się wykonywać w momencie, gdy warunek przestaje być spełniony (czyli wykonują się, gdy warunek jeszcze jest spełniony).

Zadania
1. Napisać program, który oblicza iloczyn wektorowy 2 wektorów 3-elementowych.
2. Napisać program, który odczytuje z klawiatury zmienną typu znakowego tak długo, dopóki wartość tej zmiennej jest inna niż 'a' lub 'A'.
3. Napisać program, który będzie obliczać i wypisywać wartość n! dla kolejnych liczb całkowitych tak długo, dopóki wynik będzie się mieścił w zakresie longint (czyli nie przekroczy 2147483647).


Linux
Uruchamiając terminal, rozpoczynamy pracę w konkretnym katalogu. Zazwyczaj jest to katalog główny danego użytkownika. W trakcie pracy możemy wielokrotnie zmieniać katalog, w którym chcemy pracować. Katalog, "w którym aktualnie jesteśmy", nazywamy katalogiem bieżącym. Do sprawdzenia, co jest w danym momencie katalogiem bieżącym, używamy komendy
pwd

Katalog bieżący zmieniamy, stosując polecenie cd:
cd nazwa_katalogu_docelowego

Określając nazwę katalogu docelowego, możemy stosować ścieżkę bezwzględną lub względną. Ścieżka bezwzględna wskazuje dokładną lokalizację pliku lub katalogu. Ścieżka ta rozpoczyna się od katalogu głównego "/". Przykład wykorzystania ścieżki bezwzględnej:
cd /home/FS2007/

Scieżka względna określa położenie docelowego katalogu lub pliku względem katalogu bieżącego. Przykłady użycia ścieżki względnej:
cd ../FS2007/tmp/   (przechodzimy do katalogu tmp, który znajduje się w katalogu FS2007, który z kolei znajduje się w katalogu nadrzędnym w
stosunku do katalogu bieżącego.)

cd ./abc/cde/
lub
cd abc/cde/ (przechodzimy do katalogu cde, który jest podkatalogiem katalogu abc, który z kolei jest podkatalogiem katalogu bieżącego)

Do wyświetlania informacji o plikach i katalogach znajdujących się w katalogu bieżącym (lub w innych katalogach) służy polecenie
ls

Jego ważna opcja to -l
ls -l

Po wykonaniu powyższej komendy na ekranie zobaczymy szczegółowe dane o zasobach zamieszczonych w danym katalogu. Pierwszy znak pierwszej kolumny określa, czy dany element jest plikiem (-) lub katalogiem (d). Pozostałe znaki pierwszej kolumny zawierają informacje o atrybutach, czyli prawach dostępu. Prawa dostępu mówią o tym, co właściciel danego elementu (znaki 2-4), inni użytkownicy należący do tej samej grupy użytkowników co właściciel (znaki 5-7), oraz wszyscy pozostali użytkownicy (znaki 8-10), mogą zrobić z danym elementem. W danej trójce znaków pierwszy oznacza prawo do odczytu (r) lub jego brak (-), drugi znak oznacza prawo do zapisu, czyli do zmieniania danego elementu (w) lub jego brak, trzeci znak oznacza prawo "do wykonywania", czyli np. uruchamiania pliku będącego programem, (x), lub jego brak (-). Właściciel może zmieniać atrybuty komendą chmod.
Niekiedy interesują nas tylko niektóre spośród plików z danego katalogu. Na przykład, chcemy wyświetlić listę tych plików, których nazwy rozpoczynają się literą a:
ls a*  - gwiazdka reprezentuje dowolny ciąg znaków (również ciąg pusty - o zerowej długości).
Inny przykład - interesują nas tylko pliki z rozszerzeniem txt:
ls *.txt
Chcemy wyświetlić listę plików, których drugim znakiem w nazwie jest litera a, b lub c:
ls ?[abc]* - znak zapytania reprezentuje jeden dowolny znak (w naszym przykładzie pierwszy znak nazwy może być jakikolwiek), nawias [ ] zawiera listę znaków, spośród których jeden ma się pojawić na drugim miejscu w nazwie. Ponieważ są to znaki, które występują kolejno w alfabecie, można było w nawiasie podać przedział interesujących nas znaków:
ls ?[a-c]*
Chcemy wyświetlić listę plikówz rozszerzeniem txt, których nazwy rozpoczynają się cyfrą od 1 do 5, lub 7 lub 9:
ls [1-579]*.txt


Kopiowanie i przenoszenie plików
cp plik_zrodlowy plik_docelowy - kopiowanie
mv plik_zrodlowy plik_docelowy - przenoszenie, ewentualnie zmiana nazwy - jeśli plik źródłowy i docelowy są w tym samym katalogu.

Podając nazwy plików źródłowych i docelowych, korzysta się ze ścieżek bezwzględnych lub względnych.

Przekierowanie wyjścia
Standardowym wyjściem wielu komend jest ekran, np. po wpisaniu komendy ls listę plików zobaczymy właśnie na ekranie. Informacje "wychodzące" możemy przekierować np. do pliku albo do systemowego kosza. Robimy to stosując symbol przekierowania >
ls > plik
Informacje zostaną zapisane w pliku o nazwie plik. Jeśli taki plik już istniał, "stara" zawartość zostanie skasowana.

ls -l >> plik
Jeśli plik już istniał, "stara" zawartość nie zostanie skasowana, a nowe dane zostaną dopisane na końcu pliku.

Domyślnie przekazywane są informacje z kanału danych. Jest jeszcze kanał informacji o błędach
ls -l plik.txt 1> dane 2> bledy
ls -l plik.txt - generuje linię ze szczegółowymi informacjami na temat pliku plik.txt. Informacje te (kanał danych - nr 1) zostaną zapisane w pliku o nazwie dane. Ewentualne informacje o błędach, które miały miejsce w trakcie wykonania instrukcji ls -l plik.txt (przykładowo - plik.txt może nie istnieć), a zatem informacje z kanału błędów (nr 2), zostaną zapisane w pliku bledy.

Polecenie cat
służy między innymi do wypisania zawartości pliku do standardowego wyjścia.
cat plik.txt - plik zostanie wyświetlony na ekranie
cat plik.txt >> plik1.txt - zawartość pliku plik.txt zostanie dopisana do pliku plik1.txt.

Polecenie grep
służy między innymi do wyszukania w danych pochodzących np. z wyświetlenia pliku lub z polecenia ls linii, zawierających (lub nie) pewnien wzorzec
grep "informatyka" plik.txt - wypisze tylko te linie z pliku plik.txt, które zawierają słowo informatyka
grep -v "informatyka" plik.txt - wypisze tylko te linie z pliku plik.txt, które nie zawierają słowa informatyka
grep ^d plik.txt - wypisze tylko te linie z pliku plik.txt, które  zaczynają się znakiem d
grep d$ plik.txt - wypisze tylko te linie z pliku plik.txt, które kończą się znakiem d

Potoki
"wyjście" z jednego polecenia, może byc od razu użyte jako "wejście" do kolejnego polecenia
ls -l | grep ^d - spośród linii generowanych przez polecenie ls -l, wybrane zostają tylko te, które rozpoczynają się znakiem d
cat plik.txt | grep "informatyk" - por. informacje o poleceniach cat i grep

aliasy
Możemy stworzyć własne polecenia, które będą na przykład skrótami poleceń lub ciągów poleceń.
alias ls1="ls -ltr"
zdefiniowaliśmy nową instrukcję, której efekty są takie same jak ls -ltr. W aliasach można stosować potoki:
alias ins1="ls -l | grep ^d"
aliasy działają do momentu wylogowania się, lub do momentu anulowania instrukcją unalias
unalias ins1


Polecenie wc
Podaje informację na temat wielkości na przykład pliku.
wc -l plik.txt
wynikiem będzie liczba wierszy zawartych w pliku  plik.txt
ls -l | grep ^d | wc -l
wynikiem będzie liczba wierszy generowanych poleceniem ls -l | grep ^d

Polecenie cut
Załóżmy, że mamy plik 1.txt z zawartością
abx
cby
Zawartość tę można rozumieć jako dwa wiersze z dwiema kolumnami, rozdzielonymi literą b.
cat 1.txt | cut -db -f1
Na ekranie wyświetli się tylko pierwsza kolumna. -db oznacza, że jako separator wybrany jest znak b, -f1 oznacza, że chcemy wyświetlić tylko kolumnę nr 1. Plik wejściowy pozostanie nie zmieniony.
Plik 2.txt ma zawartość
qwe rty uio p[]
ad fh jl ;'
cat 2.txt | cut "-d " -f3-4
Jako separator wybraliśmy spację, chcemy zobaczyć kolumny od 3 do 4. Wynikiem będzie
uio p[]
jl ;'
Wynik możemy przekierować do nowego pliku:
cat 2.txt | cut "-d " -f3-4 > 2_new.txt

Polecenie tr
Załóżmy, że mamy plik zam.txt z zawartością
abc def xyz
def ijk mno
cat zam.txt | tr e q
Na ekranie zamiast e pojawi się q.
cat zam.txt | tr e " "
Na ekranie zamiast e pojawi się spacja.
cat zam.txt | tr def cvb
Na ekranie zamiast def pojawi się cvb.
cat zam.txt | tr [def] t
Każda z liter d, e, bądź f zmieni się w t.
cat plik | tr . ,
Przydatne np. w plikach z liczbami, aby zmienić separator części dziesiętnej z kropki na przecinek.
cat zam.txt | tr def cvb > zam1.txt
Zapisanie wyniku nie na ekranie, a w nowym pliku.

Instrukcja for - tworzenie pętli
for {zmienna sterująca} in {lista} ; do {instrukcja}; done
Przykład
for i in a b; do echo x; done
dwa razy wypisany zostanie "x" - dla każdej z dwóch wartości (a i b) zmiennej sterującej i.
for i in a b; do echo $i; done
wypisanie wartości zmiennej sterującej w kolejnych krokach (znak $ oznacza, że chodzi o wartość)
for i in 1 10; do echo $i; done
działa podobnie, jak powyżej
for i in `seq 1 10`; do echo $i; done
` ` - w tych apostrofach umieszczamy instrukcje, służące wygenerowaniu złożonej listy wartości zmiennej sterującej. Polecenie seq 1 10 oznacza generowanie kolejnych wartości od 1 do 10.
wyn=0; for i in `seq 1 10`; do wyn=$((${wyn}+${i})); done ; echo $wyn
Stworzyliśmy zmienną wyn i nadaliśmy jej wartość 0. W każdym kroku pętli wartość wyn zmieniamy w tej sposób, że do aktualnej warości wyn dodajemy aktualną wartość zmiennej sterującej i. Na końcu wyświetlamy ostateczną wartość wyn, która w tym przypadku jest sumą liczb od 1 do 10. Podwójne nawiasy (( )) zawierają wyrażenie arytmetyczne. wyn=$(( )) oznacza, że wyn przyjmuje wartość wyrażenia zawartego w nawiasach.
Zamiast (( )) można użyć [ ]
wyn=0; for i in `seq 1 10`; do wyn=$[${wyn}+${i}]; done ; echo $wyn
Poniżej nieco inne użycie tej instrukcji (bez określenia, że chodzi o wartość wyrażenia arytmetycznego)
wyn=0; for i in `seq 1 10`; do wyn=${wyn}+${i}; done ; echo $wyn
Lista może być wynikiem jeszcze innych instrukcji, np.
for i in `ls | grep ^a` ; do cp ${i} ${i}_back ; done
Wszystkie pliki rozpoczynające się literą a zostaną skopiowane, kopie będą miały nazwy takie jak oryginały, ale dostawiona zostanie końcówka _back.

W trakcie pracy system wykorzystuje tzw. zmienne środowiskowe. Wartości tych zmiennych wpływają na sposób działania wykorzystywanych programów czy poleceń. W celu wyświetlenia wszystkich używanych zmiennych środowiskowych (i ich wartości), stosujemy polecenie
env
Na przykład zmienna USER przechowuje nazwę użytkownika. Wartość zmiennej sprawdzamy następująco
echo $USER
Oprócz zmiennych środowiskowych istnieją zmienne powłokowe. Własne zmienne tworzymy następująco
zmienna=wartosc
Jeżeli dodatkowo użyjemy polecenia export, utworzymy zmienną w sposób bardziej ogólny z punktu widzenia działania powłoki (np. nowa zmienna
będzie "widziana" przy użyciu polecenia env)
export zmienna2=wartosc2
lub dwuetapowo
zmienna2=wartosc2
export zmienna2
zmiana wartości zmiennej
zm1=12
zm2=13
zm1=zm1+zm2 - zm1 będzie miała wartość nie 25 a będzie to tekst "zm1+zm2"
zm1=$zm1+zm2 - do wartości zm1 zostanie dodany, czy raczej dostawiony, ciąg znaków "zm2". Jeżeli w systemie istniałaby zmienna o nazwie zm1+zm2, pojawiłaby się niejednoznaczność, jak interpretować to polecenie. Aby uniknąć niejednoznaczności, można zastosować nawias {}:
zm1=${zm1}+zm2 - teraz na pewno do wartości zm1 zostanie dodany (dostawiony) ciąg znaków "zm2".
Uwaga: przy ćwiczeniu powyższych poleceń warto po każdym z nich sprawdzić aktualną wartość zmiennej zm1 (echo $zm1).
Zmienna ? jest przykład tzw. zmiennej specjalnej. Przechowuje ona tzw. kod powrotu ostatniego wykonywanego polecenia; wynosi on 0 - jeśli się polecenie się powiodło, lub inną wartość jeśli nie.
Stwórzmy w katalogu bieżącym plik abc.txt. Następnie użyjmy polecenia
ls -l abc.txt - polecenie to się powiedzie, bo plik istnieje, a zatem sprawdzając wartość zmiennej ?
echo $?
otrzymamy 0.
Z kolei, jeśli w katalogu bieżącym nie ma pliku abc111.txt, po użyciu poleceń
ls -l abc111.txt
echo $?
otrzymamy 1.

Instrukcja test
Służy do sprawdzania warunków logicznych. Składnia:
test warunek
Jeśli wynikiem sprawdzenia jest prawda, kod powrotu będzie równy 0. W przeciwnym przypadku - kod powrotu będzie równy 1. Przykład
test 2 -eq 3 - sprawdzamy czy 2 jest równe 3. Oczywiście, po użyciu polecenia echo $? otrzymamy 1 (bo 2 nie jest równe 3). Inne przykłady użycia polecenia test:
test -f abc - sprawdza, czy istnieje w katalogu bieżącym plik abc.
test -d abc - sprawdza, czy plik abc jest katalogiem.
test -r abc - sprawdza, czy użytkownik ma prawo do odczytu pliku abc.
test -w abc - sprawdza, czy użytkownik ma prawo do zapisu pliku abc.
test -x abc - sprawdza, czy użytkownik ma prawo do wykonania pliku (programu) abc.
test $a -eq 101 - sprawdza, czy wartość zmiennej a jest równa 101.
test $a -ne 101 - sprawdza, czy wartość zmiennej a jest różna od 101.
test $a -gt 101 - sprawdza, czy wartość zmiennej a jest większa od 101.
test $a -ge 101 - sprawdza, czy wartość zmiennej a jest większa od lub równa 101.
test $a -lt 101 - sprawdza, czy wartość zmiennej a jest mniejsza od 101.
test $a -le 101 - sprawdza, czy wartość zmiennej a jest mniejsza od lub równa 101.
Uwaga - tworzenie bardziej złożonych warunków
! - oznacza negację
-a - oznacza koniunkcję
-o - oznacza alternatywę
test 2 -eq 2 -a $a -eq 111 - sprawdza, czy jednocześnie prawdziwe są dwa warunki: 2 jest równe 2, wartość a wynosi 111.
test 2 -eq 7 -o $a -eq 1 - sprawdza, czy prawdziwy jest przy najmniej jeden z warunków: 2 jest równe 7, wartość a wynosi 1.
test ! -f abc - sprawdza, czy nie istnieje w katalogu bieżącym plik abc; ! -f abc jest zaprzeczeniem warunku -f abc
Taki sam efekt jak w powyższych przykładach można uzyskać bez pisania polecenia test, ale wówczas warunek logiczny musi być zawarty w nawiasie [ ]:
[ -f abc ]
[ 2 -eq 7 -o $a -eq 1 ]
itd.

Instrukcje warunkowe
&&
ins1 && ins2 - jeżeli po wykonaniu pierwszej instrukcji kod powrotu wynosi 0, wykona się druga instrukcja.
ls abc && rm abc - jeśli plik abc istnieje, zostanie skasowany.
||
ins1 || ins2 - jeśli pierwsza instrukcja wykona się prawidłowo, druga już się nie wykona. Jeśli natomiast po wykonaniu pierwszej instrukcji kod powrotu będzie różny od 0, wykona się druga instrukcja.
ls abcd || touch abcde - jeśli plik abcd istnieje, druga instrukcja się nie wykona, jeżeli natomiast plik nie istnieje, stworzony zostanie plik abcde.
if
if instr1; then instr2; fi
instr1 - powinna polegać na sprawdzaniu jakiegoś warunku logicznego.
if test ! -f c1.txt ; then touch c1.txt ; fi - jeśli nie istnieje plik c1.txt, to zostanie stworzony. Inny sposób na ten sam efekt:
if [ ! -f c1.txt ] ; then touch c1.txt ; fi
Jeszcze inny sposób:
ls c1.txt || touch c1.txt
if test -f c2.txt ; then mv c2.txt c21.txt ; fi - jeśli istnieje plik c2.txt, to jego nazwa zmieni się na c21.txt. Inny sposób:
if [ -f c2.txt ] ; then mv c2.txt c21.txt ; fi
Jeszcze inny sposób:
ls c2.txt && mv c2.txt c21.txt
if test -f c1.txt -o -f c21.txt ; then echo "OK" ; fi - jeżeli istnieje jeden z wymienionych plików - pojawi się napis OK.
if [ -f c1.txt -o -f c21.txt ] ; then echo "OK" ; fi  - to samo, co powyżej.
if test ! -x c3.out ; then chmod u+x c3.out ; fi - jeżeli użytkownik nie ma prawa wykonywania pliku c3.out, to prawo to zostanie mu dodane.

Skrypty
Proste "programy", pliki textowe składające się z komend powłokowych. Aby skrypt działał poprawnie, muszą być spełnione warunki:
1. Pierwszy wiersz pliku powinien wyglądać następująco:
#!/bin/bash
2. Użytkownik musi mieć prawo do wykonania pliku. Należy więc po stworzeniu pliku użyć polecenia:
chmod u+x nazwa_pliku

Skrypt, który wypisze na ekranie "dzien dobry" i poda nazwę użytkownika (wartość zmiennej USER):
#!/bin/bash
echo "dzien dobry"
echo "jestes zalogowany jako" $USER

Skrypt, który poprosi o podanie 2 liczb i wyświetli ich sumę:
#!/bin/bash
echo "podaj 1 liczbe"
read a
echo "podaj 2 liczbe"
read b
echo suma wynosi: $(($a+$b))

Skrypt, który wyświetli sume liczb podawanych w trakcie uruchamiania tego skryptu:
#!/bin/bash
echo suma wynosi: $(($1+$2))

Uwaga: powyższy skrypt uruchamiamy następująco:
./nazwa_ skryptu 12 13
(12 i 13 to przykładowe liczby). Skrypt wykorzystał 2 zmienne o nazwach 1 i 2, ich wartości odpowiadają wartościom kolejnych liczb podawanych na
etapie uruchamiania skryptu.

Skrypt, który skopiuje plik. Nazwy pliku źródłowego i pliku docelowego podawane się w trakcie uruchamiania skryptu:
#!/bin/bash
cp $1 $2

Uwaga: powyższy skrypt uruchamiamy następująco:
./nazwa_skryptu nazwa_pliku_1 nazwa_pliku_2