Uruchamianie programów jest podstawową czynnością wykonywaną przez użytkownika komputera; warto więc od początku zrozumieć parę pojęć z tym związanych.
Program rozumiemy tu jako plik wykonywalny, a więc taki, który system potrafi uruchomić, np. w reakcji na działanie użytkownika. Działaniem tym może być np. wypisanie nazwy tego pliku w linii komend shella (interpretera komend) lub (w środowisku okienkowym) kliknięcie na ikonę powiązaną z danym programem.
Procesem z kolei jest instancja działającego programu, która powstała w wyniku jego uruchomienia. W danej chwili może istnieć i działać wiele różnych procesów, będących wynikiem uruchomienia tak tego samego, jak i różnych programów, przez wielu różnych użytkowników. Dlatego m. in. należy rozróżniać pomiędzy procesem a programem. Każdy proces posiada przydzielony przez system, jednoznaczny (w danej chwili czasu) numeryczny identyfikator (PID) i wykonuje się w zasadzie niezależnie od innych procesów, działających na tym samym komputerze; może się jednak z nimi komunikować, za pośrednictwem całego szeregu mechanizmów komunikacji międzyprocesowej zapewnionych przez system operacyjny.
Aby plik mógł być uruchomiony jako program, muszą być spełnione dwa warunki:
Pierwszy warunek został omówiony w rozdziale o plikach; drugi warunek jest
spełniony, gdy plik programu powstał w wyniku kompilacji i linkowania kodu źródłowego
(napisanego w jakimś języku programowania, takim jak C, Fortran, Pascal, ...) przez odpowiedni
kompilator (mówimy wówczas o programie binarnym). Jest on spełniony również, jeżeli plik zawiera
tekst instrukcji w języku interpretowanym (takim jak język shella sh
, Perl, Python, ...),
program interpretujący ten język jest właściwie zainstalowany na systemie, a treść pierwszej linijki
pliku zbudowana jest według wzoru
#! /bin/sh
tj. zawiera jako dokładnie dwa pierwsze znaki sekwencję #!
, a po nich następuje
pełna ścieżka dostępu do programu interpretującego. Taki program nazywa się często
skryptem.
Programy binarne przeznaczone do wspólnego użytku zainstalowane są w katalogach ogólnosystemowych,
takich jak /bin
(programy niezbędne do uruchomienia systemu), /usr/bin
(większość narzędzi przeznaczonych dla użytkowników), /usr/local/bin
(programy spoza
,,oficjalnej" zawartości systemu operacyjnego), /usr/X11R6/bin
(programy związane
ze środowiskiem okienkowym X Window System), /usr/dt/bin
(programy związane
ze środowiskiem graficznym CDE stosowanym w systemie Sun Solaris). Każdy plik spełniający
warunki opisane powyżej może być jednak uruchomiony (niezależnie od jego lokalizacji) poprzez podanie
w linii komend specyfikacji ścieżki dostępu (zamiast tylko nazwy pliku); np. uruchomienie programu
o nazwie np. mojprog
znajdującego się w katalogu aktualnym (nie będącym jednym z
katalogów systemowych) wymaga na ogół podania komendy w postaci
./mojprog
Nazwa uruchamianego pliku jest generalnie bez znaczenia, w szczególności Unix nie przywiązuje wagi
do tzw. końcówki nazwy programu (nie spotyka się programów zapisanych w plikach o nazwach kończących
się na .EXE
, .BAT
czy .COM
).
Najprostszym sposobem stworzenia procesu przez użytkownika jest uruchomienie (legalnego) pliku
wykonywalnego, przez podanie jego nazwy (lokalizacji) w linii komend. W wielu przypadkach,
w zależności od uruchamianego programu można też (lub należy) podać opcje dla programu oraz
argumenty -- będące często (choć nie zawsze) nazwami plików które mają być przetworzone. Dla większości
programów obowiązuje zasada, że bezpośrednio po nazwie (lokalizacji) programu podajemy ew. opcje
uruchomienia; mają one najczęściej postać skrótów jednoliterowych poprzedzonych znakiem -
(minus), np. ls -l
, przy czym w wypadku podawania kilku opcji jednocześnie można
je grupować: np. zamiast ls -l -A
można równoważnie napisać ls -lA
.
Ewentualne argumenty podawane są po opcjach, np. ls -Al /tmp
.
Należy wiedzieć, że zawartość linii komend podlega pewnej interpretacji i ,,obróbce'' przez shell (interpreter komend) zanim opcje i argumenty zostaną przekazane uruchomionemu programowi. W dużym uproszczeniu: treść linii komend jest dzielona na ,,słowa'' (sekwencje znaków oddzielone ciągami spacji), i każde ,,słowo'' będzie potraktowane jako oddzielny argument (lub grupa opcji); po czym następuje interpretacja pewnych znaków specjalnych (metaznaków). Przytoczę tu jedynie najprostsze (i najczęściej przydatne) przykłady zastosowania rozwinięć metaznaków:
*
, ?
oraz sekwencje postaci [...]
stosowane są
do tworzenia wzorców nazw plików; w takim wzorcu, *
zastępuje dowolny
ciąg (zera lub więcej) znaków, ?
dowolny (ale dokładnie jeden) znak,
np. [bpdt]
dowolny pojedynczy znak spośród wymienionych (a więc tu: b, p, d lub t),
np. [b-k]
dowolny pojedynczy znak z podanego zakresu (tu: od b do k). Zakres znaków
wyznaczony jest tu przez zakres wartości ich kodów ASCII; warto więc wiedzieć, że w kodowaniu
ASCII wszystkie cyfry występują w porządku (0-9), poprzedzając wszystkie wielkie litery (A-Z),
występujące w porządku alfabetycznym, które z kolei poprzedzają wszystkie małe litery (a-z),
również występujące w porządku alfabetycznym. Bezpośrednio po znaku [
, znak ^
ma znaczenie dopełnienia (podanego zbioru lub zakresu znaków), tak np. [^a-z]
oznacza dowolny znak nie bedący małą literą./
) nie jest nigdy
wynikiem rozwinięcia, tzn. wynikiem interpretacji wzorca nie zawierającego znaku /
mogą być wyłącznie nazwy plików z katalogu aktualnego. Można jednak tworzyć również wzorce
zawierające /
; będą one interpretowane zgodnie z ogólnymi regułami specyfikowania lokalizacji
plików (p. Pliki i systemy plików).
~
) interpretowane są jako katalog
,,domowy'' aktualnego użytkownika (sam znak ~
lub sekwencja ~/
), lub
jako katalog ,,domowy'' użytkownika o nazwie (loginie) która następuje po ~
, np.
~rjb
: ścieżka do katalogu domowego użytkownika rjb
. Znak ~
występujący w pozycji innej niż na początku słowa nie podlega specjalnej interpretacji.
$
zastępowana jest przez wartość zmiennej shella,
której nazwa następuje po $
; np. polecenie echo $PATH
wypisuje aktualną
wartość zmiennej PATH
(a nie napis $PATH
).
"
, '
(cudzysłów i apostrof) oraz \
służą do ,,ochrony''
innych metaznaków przed interpretacją przez shell, w przypadku gdy powinny one być przekazane
do uruchamianego programu w postaci dosłownej. Znak \
,,chroni'' znak po nim następujący;
para apostrofów '...'
zapobiega interpretacji wszelkich metaznaków (z wyjątkiem samego apostrofu);
para cudzysłowów "..."
,,chroni'' spacje i metaznaki tworzące wzorce nazw plików,
natomiast nie zapobiega interpretacji znaku $
.
Inny użyteczny trik to komenda1 `komenda2`
, działa on następująco: jako pierwsza zostanie wykonana
komenda2
(może ona oczywiście zawierać również argumenty), wynik jej wykonania -- to, co normalnie
pojawiłoby się na ekranie, tj. zawartość standardowego strumienia wyjściowego -- zostanie umieszczony
w linii komend wywołującej komenda1
, która następnie zostanie wykonana (z zastosowaniem
pozostałych reguł rozwijania).
Shell umożliwia również edycję linii komend, oraz odwoływanie się do komend wpisanych uprzednio (klawisz ,,strzałka w górę''), z możliwością ich edycji (modyfikacji) przed ponownym wykonaniem. Wśród innych udogodnień ułatwiających pracę interakcyjną warto wymienić tzw. autouzupełnianie (nazw komend i argumentów); w szczegółach jego działanie różni się w zależności od używanej wersji shella i opcji konfiguracyjnych, ale zazwyczaj pozwala ono na użycie klawisza ,,Tab'' po wpisaniu części nazwy komendy, lub nazwy pliku mającej być argumentem, czego wynikiem jest uzupełnienie tej nazwy o dalsze znaki na tyle, na ile jest to jednoznacznie możliwe (tj. jeżeli do początkowych kilku znaków pasuje nazwa tylko jednego faktycznie istniejącego pliku, to pojawi się ona na linii komend w całości).
W istocie shell jest interpreterem dość zaawansowanego języka programowania, potrafiącym wykonywać programy
bezpośrednio z plików tekstowych, zwanych skryptami shella. Język ten zawiera w zasadzie wszystkie
elementy przydatne do tworzenia nawet dość rozbudowanych programów (zmienne, pętle, instrukcje warunkowe,
możliwość definicji funkcji ...); bardziej szczegółowe jego omówienie wykracza jednak poza zakres
niniejszych notatek. Wspomnę jedynie, że składnia języka shella różni się dość istotnie w zależności
od typu shella; używany jako domyślny do pracy interakcyjnej w naszej sieci tcsh
nie jest
szczególnie godny polecenia (w dość powszechnej opinii) do tworzenia bardziej rozbudowanych skryptów;
do tego celu zazwyczaj używa się bardziej standardowego sh
, lub będącego domyślnym
w większości instalacji Linuxa shella bash
.
Z bardziej zaawansowanych właściwości shella omówię tu pobieżnie jedynie dwie: kontrolę zadań, oraz
przekierowania i potoki. Kontrola zadań stwarza możliwość uruchamiania procesów ,,w tle'', tj. z natychmiastowym
powrotem do shella i możliwością wydawania kolejnych poleceń bez czekania na zakończenie uruchomionego
właśnie procesu, ,,zawieszania'' wykonania aktualnie działającego procesu oraz żonglowania na rozmaite
sposoby grupą zadań uruchomionych z aktualnej instancji shella. Jest to szczególnie przydatne
w przypadku uruchamiania długotrwałych procesów, które wykonują się bez potrzeby interakcji z użytkownikiem,
oraz przy uruchamianiu programów które np. tworzą własne okna graficzne i poprzez nie komunikują się
interakcyjnie z użytkownikiem. Uruchomienie procesu w tle dokonuje się przez zakończenie linii komend
znakiem &
, postawionym po wszystkich argumentach do polecenia i ew. przekierowaniach
(p. poniżej). Do ,,zawieszenia'' procesu aktualnie działającego ,,w pierwszym planie'' służy zazwyczaj
sekwencja klawiszy Ctrl-z
; w odróżnieniu od procesu uruchomionego ,,w tle'', proces
zawieszony wstrzymuje działanie i czeka na ewentualne ,,odwieszenie''. Listę zadań związanych z aktualnym
shellem (zawieszonych i uruchomionych w tle) można obejrzeć komendą jobs
; każde zadanie
z tej listy będzie opatrzone numerem identyfikacyjnym, z którego można skorzystać do wydawania poleceń
zmieniających stan danego zadania. Polecenie fg %n
(gdzie n
jest tym numerem
identyfikacyjnym) przenosi dane zadanie do ,,pierwszego planu'', zarazem je wznawiając (o ile było ono
zatrzymane); natomiast bg %n
wznawia wykonywanie zadania zawieszonego, pozostawiając je
,,w tle''.
Mechanizm przekierowania i potoków pozwala z kolei na proste wykonywanie nawet dość złożonych operacji
przetwarzania danych poprzez łączenie w ,,łańcuszek'' szeregu prostych komend, z których każda wykona
jeden z etapów przetwarzania przekazując wynik następnej w celu wykonania koleknek operacji. Korzysta
tu się z faktu, że z każdym procesem unixowym związane są trzy tzw. standardowe strumienie wejścia-wyjścia:
standardowe wejście (stdin
), standardowe wyjście (stdout
) i standardowy
strumień błędu (stderr
). Domyślnie, stdin
utożsamiony jest z klawiaturą
terminala, a stdout
i stderr
-- z ekranem. Tzn. np. że dane wyprowadzone
przez program na stdout
pojawią się na ekranie użytkownika. Większość standardowych
programów narzędziowych, stanowiących podstawowe wyposażenie systemu unixowego, potrafi czytać dane
wejściowe zarówno z plików (z dysku), jak i ze strumienia stdin
, natomiast wyniki przetwarzania
wyprowadza domyślnie na strumień stdout
. Strumień stderr
służy natomiast
do wypisywania komunikatów nie będących ,,normalnym'' wynikiem przetwarzania danych, tj. komunikatów
o błędach i ew. ostrzeżeń o sytuacjach mniej lub bardziej ,,anomalnych''.
Dla przykładu, komendasort
służy do sortowania (domyślnie: alfabetycznego) linijek tekstu stanowiących dane wejściowe. Gdy się ją wywoła z argumentami będącymi nazwami plików, danymi do sortowania będzie zawartość tychże; w przypadku wywołania bez argumentów (nie będących opcjami, za pomocą których można zadać bardziej złożone kryteria sortowania), komendasort
oczekuje, że dane do przetworzenia pojawią się w standardowym strumieniu wejściowym. W obu tych przypadkach, wynik sortowania pojawi się nastdout
.
Mechanizm o którym mowa polega na udostępnieniu możliwości przedefiniowania standardowych strumieni, na dwa
sposoby: bądź przez połączenie ich z plikami, bądź przez połączenie stdout
jednego procesu
z stdin
innego. Konstrukcja komenda ... > plik
powoduje, że zawartość
stdout
wywołanej komendy, zamiast na ekranie, pojawi się w pliku o podanej nazwie (w miejscu
wielokropka umieścić można dowolne opcje i inne argumenty wywołania); podobnie komenda ... < plik
spowoduje, że zawartość podanego pliku będzie stanowiła stdin
wywołanej komendy. Z kolei
pisząc komenda1 ... | komenda2 ...
sprawimy, że dane wyprowadzone przez komenda1
na jej stdout
staną się zawartością stdin
dla komenda2
.
Wszystkich tych rodzajów przekierowania można też użyć łącznie, np.:
komenda1 < plik1 | komenda2 > plik2
.
komenda .. > plik
, a plik
już istnieje,
to jego wcześniejsza zawartość zostanie zastąpiona przez zawartość stdout
wywołanej
komendy -- tj. utracona! Istnieje druga postać przekierowania stdout
:
komenda ... >> plik
, przy której zawartość stdout
wywołanej komendy nie
zastępuje ew. wcześniejszej zawartości podanego pliku, lecz zostanie dopisana do jej końca.
stdout
nie ma wpływu na powiązanie stderr
z ekranem,
komunikaty o błędach pojawią się i tak na ekranie terminala użytkownika. Większość narzędzi
unixowych nie wypisuje żadnych komunikatów w przypadku, gdy ich działanie przebiega zgodnie
z oczekiwaniami.
komenda1 ... | komenda2 ... | komenda3 ...
. Shell nie czeka z uruchomieniem
kolejnych poleceń występujących w potoku przetwarzania na zakończenie poprzednich, będą one
działały równolegle w miarę napływania danych.
Czytanerazy.