Omówienie poleceń powłoki bash służących do zarządzania dostępem do obiektów


Omówienie poleceń powłoki bash służących do zarządzania dostępem do obiektów

Bash jest działającym w środowisku Unixowym interpretatorem poleceń. Nazwa jest akronimem z ang. Bourne Again SHell (Steve Bourne to autor poprzednika Basha - programu sh).

Bash obsługuje standardowe konstrukcje sh, takie jak for, while, case, czy if.. Umożliwia wykonywanie skryptów w taki sam sposób jak zwykłych poleceń. Dostępne są wcześniej wpisane polecenia (tzw. historia) i rozbudowane funkcje edycji linii poleceń. Zawiera tez liczne wbudowane polecenia (ang. builtins), pozwalające m.in. na kontrole procesów, obliczanie wartości wyrażeń arytmetycznych, definiowanie aliasow itd.

Głowna procedura

Oto jak w największym skrócie wygląda działanie jednego egzemplarza interpretatora. Algorytm jest niemal identyczny dla dowolnego źródła poleceń, niezależnie czy jest nim skrypt, argument wywołania, czy tez palce użytkownika. Niewielkie modyfikacje obejmują głownie sposób wczytywania i wykonywania poleceń.

Ogolone algorytmy większości wyróżnionych procedur opisane są poniżej.

{

wykonaj wstepna inicjalizacje;

obsluz parametry wywolania;

inicjalizuj;

wczytaj startowe pliki inicjalizacyjne;

while (nie koniec) {

if (wczytaj polecenie)

if (jest polecenie do wykonania) {

wykonaj polecenie;

posprzataj;

}

else koniec;

}

zakoncz dzialanie;

}

Przebieg inicjalizacji wstępnej

Oto schemat czynności podejmowanych przez interpretator zaraz po uruchomieniu. Większość podejmowanych tu akcji ma na celu dostosowanie się Basha do otoczenia, w którym działa.

{

zapamietaj id uzytkownika;

if (uid uzytkownika != euid uzytkownika || gid uzytkownika != egid uzytkownika)

ustaw tryb uprzywilejowany;

if (zdefiniowano zmienna srodowiskowa POSIXLY_CORRECT lub POSIX_PEDANTIC)

ustaw tryb zgodnosci ze standardem Posix;

inicjalizuj lokalne zmienne robocze;

if (nazwa wywolania jest "sh")

ustaw tryb maksymalnej zgodnosci z sh;

}

Analiza parametrów wywolania

Po dokonaniu wstępnych czynności inicjalizacyjnych Bash przystępuje do analizy parametrów wywolania. Uwzględniane są zarówno pełne nazwy opcji, jak i flagi.

{

arg_index = 1;

while (arg_index != argc && argv[arg_index] zaczyna sie od '-') {

for (i=0; i<liczba dlugich nazw opcji; i++)

if (argv[arg_index] rowny dlugiej nazwie[i])

if (typ opcji==Int)

ustaw wartosc opcji na 1;

else

ustaw wartosc opcji na nastepny parametr;

else koniec dlugich nazw opcji;

arg_index++;

}

ustaw opcje jednoliterowe;

if (ustawiono opcje wykonania polecenia podanego jako parametr)

ustaw odpowiedni parametr jako zrodlo polecen;

if (spelnione warunki interakcji)

ustaw flage interakcji z uzytkownikiem;

}

Właściwa inicjalizacja

Tutaj wykonywana jest rzeczywista, ciężka praca inicjalizacyjna. Tworzone i/lub inicjalizowane są struktury do przechowywania najróżniejszych informacji. Ostatecznie ustalane jest źródło wykonywanych później poleceń. Ustawiane są zmienne środowiskowe - np. dla znaku zachęty (ang. prompt), badane możliwości edycyjne terminala (końcówki?) itd.

{

ustaw buforowanie stdout i stderr;

inicjalizuj i sortuj tablice wbudowanych polecen;

ustaw przechwytywanie i obsluge sygnalow;

wypelnij strukture informacji o uzytkowniku i komputerze;

wlacz funkcje obslugujaca tylde (katalog domowy) w sciezkach;

inicjalizuj srodowisko;

utworz struktury do przechowywania plikow;

rozpocznij obsluge procesow;

utworz ogolne struktury do wczytywania polecen;

if (terminal == emacs)

ustaw brak edycji linii;

ustaw znaki zachety;

wczytaj startowe pliki inicjalizacyjne;

if (jest juz polecenie do wykonania) {

wykonaj polecenie;

zakoncz dzialanie;

}

if (tryb interakcji z uzytkownikiem)

ustaw obsluge poczty;

wczytaj historie;

if (podano plik jako parametr)

if (plik istnieje && jest skryptem)

ustaw plik jako zrodlo polecen;

else

ustaw buforowane wejscie jako zrodlo polecen;

przypisz reszte parametrow do zmiennych srodowiskowych $1...$n;

}

Analiza wpisywanych przez użytkownika poleceń zajmuje się parser wygenerowany na podstawie odpowiedniej gramatyki przez FLEXa i Bisona. Nie będziemy się tu zajmować dokładną analiza parsera, zwłaszcza, ze ma on ponad 4000 linii, a spora jego cześć to automat skończony i mnóstwo wypełnionych liczbami tablic. Sami autorzy Basha przyznają, ze parser jest bardzo duży i mało efektywny, w związku z czym zapowiadają, ze w wolnej chwili napiszą go ręcznie. Parser Basha jest na tyle skomplikowany, ze na jednej z poświeconych Unixowi konferencji Tom Duff stwierdził: Nikt nie wie, jaka naprawdę jest gramatyka Basha. Niewiele daje nawet analiza samych źródeł.

Parser jest słabo odgraniczony od reszty programu. Wywoływany jest w wielu miejscach, on sam również wielokrotnie korzysta z procedur Basha (przykładami może być chociażby cześć operacji na napisach czy tez obliczanie wyrażeń arytmetycznych). Komunikacja miedzy parserem a reszta interpretatora odbywa się głownie przez zmienne globalne - większość funkcji parsera nie zwraca żadnych istotnych parametrów.

Głównym zadaniem parsera jest odczytanie poleceń użytkownika z wejścia i utworzenie z nich struktury COMMAND (opisanej poniżej). Na zmiennej global_command (typu COMMAND) parser zapamiętuje strukturę analizowanych poleceń.

Wejście dla parsera jest dostarczane przez Basha i jego obsługa jest niemal niezależna od rzeczywistego źródła danych. Po odpowiedniej dla danego typu wejścia inicjalizacji (with_input_from_stdin(), with_input_from_buffered_stream(), with_input_from_string()) dalej parser używa wyłącznie ogólnych funkcji odczytu danych.

Do odczytu danych ze standardowego wejścia używana jest biblioteka readline umożliwiająca edycje linii danych i obsługująca wiele klawiszy edycyjnych w rożnych standardach (m.in. emacs i vi), a także zapewniająca obsługę historii.

Wczytywanie poleceń Basha na najniższym poziomie odbywa się poprzez buforowany strumień z synchronizacja. Mechanizm ten może działać w połączeniu z plikami, można tez go używać do emulowania czytania znaków z wejścia i zwracania ich poprzez odpowiedniki getc() i ungetc(). Funkcje i struktury danych obsługujące strumienie zdefiniowane są w plikach INPUT.C oraz INPUT.H.

Struktury danych

Dwie istotne struktury to BUFFERED_STREAM oraz BASH_INPUT korzystająca z unii INPUT_STREAM. Pierwsza jest nieco podobna do standardowej struktury FILE, ale ma swoje własne buforowanie i synchronizacje. Zdefiniowana jest również tablica strumieni buffers[] składająca się z BUFFERED_STREAM.

typedef struct BSTREAM {

int b_fd;

char *b_buffer; /* bufor przechowujacy otrzymane znaki */

int b_size; /* wielkosc bufora (maksymalnie 8kB) */

int b_used; /* ile bufora zajete */

int b_flag; /* flagi - B_EOF, B_ERROR lub B_UNBUF */

int b_inputp; /* wskaznik bufora - indeks */

} BUFFERED_STREAM;

Druga przechowuje dane potrzebne bezpośrednio do obsługi wejścia. Zawiera m.in. dwa wskaźniki do funkcji (typ Function to funkcja bez parametru zwracająca znak) - pierwsza z nich ma służyć do pobierania znaku, druga do zwracania. Jak widać z poniższej unii, miejscem, z którego Bash pobiera znaki, może być plik, zwykły strumień znaków lub właśnie strumień buforowany.

typedef union {

FILE *file; /* odczyt z pliku */

char *string; /* odczyt z ciagu znakow */

int buffered_fd; /* odczyt z buforowanego strumienia, zdefiniowanego wyzej */

} INPUT_STREAM;

typedef struct {

int type;

char *name;

INPUT_STREAM location;

Function *getter;

Function *ungetter;

} BASH_INPUT;

Tworzenie strumienia

Do utworzenia nowego strumienia na najniższym poziomie służy funkcja make_buffered_stream(), która przydziela pamięć dla strumienia i inicjuje pola jego struktury. Nie powinna być wywoływana z zewnątrz, jest raczej przeznaczona do wykorzystania przez inne funkcje. Jej argumentami są: deskryptor dla tworzonego strumienia, bufor i jego rozmiar, przy czym pamięć dla bufora musi być zaalokowana wcześniej. Funkcja zwraca wskaźnik do utworzonej struktury.

static BUFFERED_STREAM* make_buffered_stream (int fd, char *buffer,

int bufsize)

{

BUFFERED_STREAM *bp;

zaalokuj pamiec;

zniszcz stary i wstaw nowy element do buffers[fd];

bp->b_fd = fd;

bp->b_buffer = buffer;

bp->b_size = bufsize;

bp->b_used = 0;

bp->b_inputp = 0;

bp->b_flag = 0;

if (bufsize == 1)

bp->b_flag |= B_UNBUFF;

return (bp);

}

Utworzone strumienie można kopiować funkcja copy_buffered_stream() o następującym nagłówku:

static BUFFERED_STREAM * copy_buffered_stream (BUFFERED_STREAM *bp);

Przydziela ona tylko pamięć dla nowej struktury, kopiuje pola i zwraca wskaźnik do nowo utworzonej kopii, albo NULL jeżeli bp jest pustym wskaźnikiem.

Możliwe jest również duplikowanie deskryptorów podobnie, jak robi to funkcja dup2(fd1, fd2). Służy do tego funkcja duplicate_buffered_stream(). Jeżeli docelowy strumień istnieje, jest niszczony, a następnie do nowo utworzonego kopiowany jest strumień źródłowy.

duplicate_buffered_stream (int fd1, int fd2)

{

if (fd1 == fd2)

return 0;

m = max (fd1, fd2);

zniszcz stary i wstaw nowy element do buffers[fd];

if (strumien istnieje w tablicy)

free_buffered_stream (strumien);

buffers[fd2] = copy_buffered_stream (buffers[fd1]);

if (buffers[fd2])

buffers[fd2]->b_fd = fd2;

if (fd2 uzywane jako wejscie dla Basha) {

/* w tablicy moze nie istniec taki element */

if (!buffers[fd2])

fd_to_buffered_stream (fd2);

}

return (fd2);

}

Kolejna funkcja przyporządkowuje deskryptorowi pliku strumień buforowany. Nazywa się ona fd_to_buffered_stream() i w razie potrzeby tworzy oraz zwraca strumień dla zadanego deskryptora, albo NULL w przypadku niemożności utworzenia go.

BUFFERED_STREAM* fd_to_buffered_stream (int fd)

{

if (i-wezel dla fd nie istnieje) {

close (fd);

return ((BUFFERED_STREAM *)NULL);

}

if (nie można wykonać seek na strumieniu)

rozmiar = 1;

else

ustaw rozmiar bufora;

przydziel pamięć dla bufora wewnetrznego;

return (make_buffered_stream (fd, bufor, rozmiar));

}

Otwieranie / zamykanie strumienia

Wykorzystując powyższe funkcje można otworzyć strumień podobnie jak plik, podając jego nazwę. Służy do tego funkcja open_buffered_stream(), zwracająca wskaźnik do strumienia:

BUFFERED_STREAM* open_buffered_stream (char *file)

{

fd = open (file, O_RDONLY);

if (fd == -1)

return ((BUFFERED_STREAM *)NULL);

return (fd_to_buffered_stream (fd));

}

Otwiera ona plik (który musi wcześniej istnieć) do czytania i tworzy strukturę strumienia powiązaną z deskryptorem tego pliku.

Z kolei do zamykania strumienia służy odpowiednik bibliotecznej funkcji close() funkcja o nazwie close_buffered_fd(), zwracająca wynik wykonania close(), jeżeli strumień dla danego deskryptora nie istnieje, a wynik otrzymany z wywolania close_buffered_stream() w przeciwnym przypadku.

int close_buffered_fd (int fd)

{

if (nie ma strumienia odpowiadajacego fd)

return (close (fd));

return (close_buffered_stream (buffers[fd]));

}

Funkcja close_buffered_stream() zwalnia pamięć przydzielona strumieniowi oraz zamyka związany z nim deskryptor pliku:

int close_buffered_stream (BUFFERED_STREAM *bp)

{

if (!bp)

return (0);

fd = bp->b_fd;

zwolnij pamięć zajęta przez strumień;

return (close (fd));

}

Operacje na strumieniu

Funkcja b_fill_buffer() czyta znaki aż do zapełnienia bufora podanego jako parametr i zwraca pierwszy znak z bufora albo koniec pliku:

static int b_fill_buffer (BUFFERED_STREAM *bp)

{

do {

bp->b_used = read (bp->b_fd, bp->b_buffer, bp->b_size);

} while (bp->b_used < 0 && errno == EINTR);

if (bp->b_used <= 0) { /* w buforze nic nie ma */

bp->b_buffer[0] = 0;

if (bp->b_used == 0) /* nic nie przeczytano */

bp->b_flag |= B_EOF;

else /* read() zwrocil blad */

bp->b_flag |= B_ERROR;

return (EOF);

}

ustaw poczatek bufora;

return (pierwszy znak z bufora);

}

Do wczytania jednego znaku służy odpowiednik getc() - udające funkcje dość nieładnie zdefiniowane makro z parametrem będącym strumieniem, które zwraca pierwszy znak z bufora i przesuwa wskaźnik lub, jeżeli bufor jest pusty, wywołuje funkcje b_fill_buffer():

#define bufstream_getc(bp) \

(bp->b_inputp == bp->b_used || !bp->b_used) \

? b_fill_buffer (bp) \

: bp->b_buffer[bp->b_inputp++]

Istnieje oczywiście również odpowiednik ungetc() wykonujący odwrotna operacje: wpisujący znak z powrotem do bufora i przesuwający wskaźnik bufora w druga stronę. Jest to funkcja bufstream_ungetc(), której parametry to znak do zwrócenia i strumień:

static int bufstream_ungetc(int c, BUFFERED_STREAM *bp)

{

if (c == EOF || bp->b_inputp == 0)

return (EOF);

bp->b_buffer[--bp->b_inputp] = c;

return (c);

}

Funkcja sync_buffered_stream() przesuwa w tył wskaźnik w pliku o deskryptorze bfd, aby zsynchronizować jego pozycje w pliku z tym, co dotąd zostało przeczytane:

int sync_buffered_stream (int bfd)

{

BUFFERED_STREAM *bp;

bp = buffers[bfd];

if (!bp)

return (-1);

chars_left = bp->b_used - bp->b_inputp; /* ile znakow do odczytania */

if (chars_left)

/* o tyle cofany jest wskaznik w pliku */

lseek (bp->b_fd, -chars_left, SEEK_CUR);

/* co oznacza, ze w buforze z powrotem nic nie ma */

bp->b_used = bp->b_inputp = 0;

return (0);

}

Używana przez Basha funkcja with_input_from_buffered_stream() wiąże wejście z plikiem o deskryptorze będącym parametrem, czytanie odbywa się przez buforowany strumień. Wywołuje inicjująca wejście/wyjście funkcje parsera init_yy_io() podając jej w miejsce wskaźników do funkcji pobierających i zwracających znak funkcje będące nieco obudowanymi (nawiasami { } i inna nazwa) wywołaniami bufstream_getc() i bufstream_ungetc():

void with_input_from_buffered_stream (int bfd, char *name) {

INPUT_STREAM location;

location.buffered_fd = bfd;

/* upewnij sie, ze strumien istnieje */

fd_to_buffered_stream (bfd);

init_yy_io (buffered_getchar, buffered_ungetchar, st_bstream,

name, location);

Wykonywanie poleceń

Struktury danych

Podstawowa struktura przechowująca informacje o pojedynczym poleceniu jest struktura COMMAND. Jej definicja wygląda następująco:

typedef struct command {

enum command_type type; /* typ polecenia (por. tabela ponizej) */

int flags; /* dodatkowe flagi */

int line; /* numer linii, w ktorej rozpoczyna sie polecenie */

REDIRECT *redirects; /* przeadresowania dla niektorych polecen */

union { /* dodatkowe dane dla poszczegolnych polecen */

struct for_com *For;

struct case_com *Case;

struct while_com *While;

struct if_com *If;

struct connection *Connection;

struct simple_com *Simple;

struct function_def *Function_def;

struct group_com *Group;

struct select_com *Select;

} value;

} COMMAND;

Pole redirects to struktura zawierająca informacje o przeadresowaniach. Określa ona: typ przeadresowania (wejście, wyjście, wejście/wyjście, dołączenie do pliku itd.), przeadresowywany deskryptor oraz nazwę pliku lub deskryptor, na który ma się odbyć przeadresowanie.

Najciekawszym i najważniejszym polem struktury COMMAND jest unia value, przechowująca dane odpowiednie dla danego polecenia. Poniższa tabela przedstawia wszystkie obsługiwane przez Basha rodzaje poleceń i odpowiadające im parametry.

Rodzaj polecenia

Parametr

Lista parametrów

Polecenie

CONNECTION

connector
- połączenie

first, second -
połączone polecenia

CASE

word
-- warunek

clauses - lista kolejnych
warunków i poleceń do
wykonania

action (w clauses)
- do wykonania gdy
warunek spełniony

FOR, SELECT

name
- zmienna

maplist - lista parametrów
do podstawienia na zmienna

action - do wykonania
dla każdego podstawienia

IF

test
- warunek

true_case - gdy warunek
spełniony, false_case
- gdy niespełniony

WHILE

test
- warunek

action - do wykonywania
dopóki warunek spełniony

SIMPLE

words - argumenty dla
funkcji exec

FUNCTION_DEF

name
- nazwa

command - struktura
typu COMMAND

GROUP

command - struktura
typu COMMAND

Niektóre z opisanych w tabeli typów poleceń wymagają krótkiego komentarza:

CONNECTION

struktura umożliwiająca przechowywanie poleceń połączonych średnikiem lub spójnikiem logicznym.

FUNCTION_DEF

tutaj przechowywane są funkcje zdefiniowane przez użytkownika; po wczytaniu definicji funkcji Bash analizuje ja jak normalne polecenie i utworzona w ten sposób strukturę COMMAND przechowuje na zmiennej command

GROUP

struktura grupująca polecenia w strukturze command umożliwiająca współużytkowanie łączy (ang. pipes) przez grupę poleceń

SIMPLE

podstawowa struktura, zawierająca informacje o bezpośrednio wykonywanych poleceniach

Obsługa zmiennych środowiskowych

Struktury danych

Pojedyncze zmienne i funkcje zdefiniowane w środowisku Basha przechowywane są w strukturze variable, której definicja wygląda następująco:

typedef struct variable *DYNAMIC_FUNC ();

typedef struct variable {

char *name; /* Nazwa zmiennej lub funkcji. */

char *value; /* Wartość zmiennej lub wartosc zwracana przez funkcje */

DYNAMIC_FUNC *dynamic_value; /* Funkcja obliczajaca wartosc dla zmiennych dynamicznych */

DYNAMIC_FUNC *assign_func; /* Funkcja wywolywana przy przypisaniu wartosci na zmienna */

int attributes; /* Atrybuty (eksportowana, niewidoczna itd.) */

int context; /* Zasieg zmiennej lub funkcji */

struct variable *prev_context; /* Wartosc w poprzednim zasiegu */

} SHELL_VAR;

Oto nieco szerszy opis znaczenia niektórych pól tej struktury:

dynamic_value

funkcja zwracająca wartość zmiennej dynamicznej, tj. takiej, która zmienia się w czasie nie tylko w wyniku przypisania jej nowej wartości; przykładami takich zmiennych są: $SECONDS i $RANDOM

assign_func

funkcja wywoływana, gdy wartość zmiennej jest zmieniana w wyniku wywolania funkcji bind_variable

Zmienne środowiskowe przechowywane są w tablicy mieszającej z metoda łańcuchową (ang. hash with chaining). Tablice mieszające używane są zresztą do przechowywania wielu rożnych struktur Basha.

Inicjalizacja

Zmienne środowiskowe są inicjalizowane przy inicjalizacji samego Basha. Środowisko pobierane jest z otoczenia przez funkcje main i jako jedyny parametr przekazywane funkcji initialize_shell_variables(char** env), która wykonuje inicjalizacje. Przekazywane środowisko ma postać tablicy napisów postaci nazwa_zmiennej=wartosc lub nazwa_funkcji=() {definicja_funkcji}.

Inicjalizacja środowiska rozpoczyna się od utworzenia odpowiednich tablic mieszających (jednej dla zmiennych i jednej dla funkcji) i zdekodowania przekazanego środowiska według następującego algorytmu:

{

for (i=0; i<liczba napisów w przekazanym środowisku; i++) {

rozdziel napis[i] na nazwę i wartość;

if (wartość zawiera fragment "() {") { /* definicja funkcji */

przepisz nazwę i definicje do nowego napisu;

wywołaj parser dla napisu tak, jak dla polecenia uzytkownika;

if (funkcja o tej nazwie nie została zdefiniowana)

zglos blad;

}

else

przypisz wartość do zmiennej nazwa

}

}

Następnie sprawdzane jest istnienie niektórych zmiennych środowiskowych. Jeśli nie są zdefiniowane, Bash tworzy je ustawiając im domyślne wartości. Zmienne te to:

Kolejna czynnością jest ustawienie kilku zmiennych określających parametry samego Basha. Ustawiane są:

Funkcje do obsługi środowiska

Wiele funkcji do obsługi środowiska jest bardzo podobnych do siebie i najprawdopodobniej zostały zdefiniowane w kilku wersjach dla oszczędzenia autorom pisania kilku linijek w innych miejscach kodu, opisane wiec zostaną tu tylko te najważniejsze.

Do wyszukiwania zmiennych i funkcji służy cały zestaw funkcji zaczynających się od FIND_. Wszystkie one przeszukują odpowiednia tablice mieszającą, sprawdzając, czy jest w niej zmienna lub funkcja o poszukiwanej nazwie. Prawie wszystkie funkcje FIND_ wywołują funkcje FIND_VARIABLE lub FIND_FUNCTION, zaś wszystkie bez wyjątku pośrednio lub bezpośrednio korzystają z funkcji LOOKUP, która dostaje poszukiwaną nazwę, a zwraca odpowiedni element tablicy mieszającej (lub NULL gdy nazwy nie ma).
Tworzenie nowych zmiennych i zmienianie wartości istniejących tez opiera się na funkcji LOOKUP. Przy ustawianiu wartości sprawdzane jest, czy nazwa juz jest zdefiniowana, i wówczas modyfikowana jest odpowiednia wartość, zaś w przeciwnym razie do tablicy mieszającej dodawany jest odpowiedni element. W wypadku tworzenia/zmieniania funkcji przekazywana wartością jest struktura COMMAND - taka jak przy wykonywaniu poleceń. Do policzenia wartości zmiennej wołana jest funkcja evalexp, obliczająca wartość wyrażenia arytmetycznego i opisana szczegółowo poniżej.

Co ciekawe, przy każdej zmiennej pamiętane jest, ile razy była ona znajdowana w tablicy mieszającej, informacja ta nie jest jednak nigdzie wykorzystywana (można by np. na jej podstawie dynamicznie modyfikować listy w tablicy mieszającej, przesuwając częściej używane zmienne na początek).

Ciekawe rozwiązania

W tym rozdziale przedstawiamy odpowiedz na standardowe pytanie: jakie są najciekawsze rozwiązania i struktury danych?, przy czym przez najciekawsze niekoniecznie rozumiemy warte stosowania.

abstrakcja wejścia

Wejście jest całkiem niezłe odizolowane od reszty programu - większość funkcji mających cos wspólnego z wejściem działa na uogólnionej strukturze i wola ogólne funkcji obsługi.

obliczanie wyrażeń

Sprytne użycie kilkunastu funkcji obsługujących wyrażenia o kolejnych priorytetach oraz użycie rekurencji pozwoliły na obliczanie wartości wyrażeń bez potrzeby tworzenia dodatkowych rozbudowanych struktur.

przenośność

Autorzy stosują wiele metod, trickow, zaklęć itd. by uczynić Basha naprawdę przenośnym i niezależnym od wersji Unixa, a nawet od systemu operacyjnego. Badane są m.in. kolejność bajtów w liczbach wielobajtowych (ang. little-endian, big-endian), liczba i nazwy sygnałów, zgodność ze standardem POSIX, możliwości kontroli procesów, maksymalna długość ścieżki itd. itp. Wszystkie te informacje znajdowane są automatycznie przez Basha, bez potrzeby jakiejkolwiek ingerencji użytkownika.

setjmp i longjmp

Autorzy Basha namiętnie stosują te dwie instrukcje wszędzie gdzie tylko się da. Cala obsługa błędów i nie tylko implementowana jest za pomocą skakania po całym kodzie funkcji (często mającej kilkaset linii). Dość skutecznie utrudnia to analizę kodu. Ale być może programowanie dużych programów w Linuxie bez goto jest niemożliwe (samo goto z etykietami tez oczywiście występuje wielokrotnie).

struktura poleceń

Dość elegancko przechowywana jest struktura poleceń. Drzewo poleceń, używane nie tylko do natychmiastowego wykonywania zadań uzytkownika, ale także np. do przechowywania definicji funkcji, jest struktura elastyczna i przejrzysta.

tablice mieszające

Użycie tablic mieszających do przechowywania wszystkich niemal większych zestawów danych, które wymagają czegoś więcej niż sekwencyjne przeszukiwanie jest godne pochwały. Tablice mieszające są szybkie, implementacja nawet w linuxowym C dość czytelna, zaś rezygnacja ze struktur typu lista cykliczna połączona z drzewem AVL, które i tak są zazwyczaj przeszukiwane sekwencyjnie na wszelki wypadek niewątpliwie usprawnia program.

zagadki

Aczkolwiek zagadki w rodzaju Czy ta funkcja powinna znajdować się tutaj? albo Nie wierze, ze ten fragment jest kiedykolwiek wykonywany są wspólne dla całego kodu Linuxa, w Bashu występują w dużym natężeniu. Od czasu do czasu autorzy zaskakują przyjętym rozwiązaniem: Otwórz skrypt, ale najpierw zmień nasz deskryptor na duży losowy, z nadzieja, ze w skrypcie nie znajdzie się taki sam.

Patrycja Łukaszek

Kl. IIa SI

13



Wyszukiwarka

Podobne podstrony:
10, Zasilacz - urządzenie służące do dopasowania dostępnego napięcia do wymagań zasilanego urządzeni
głuchowski,inżynieria oprogamowania S, Narzędzia służące do zarządzania wymaganiami
Jak wyświetlić zawartość katalogu jako linki służące do pobrania plików, PHP Skrypty
PROTOKÓŁ Z ZEBRANIA ZARZĄDU Dostępność Skweru i Lista obecnośći 09 2012
SCIAGA reczna, Maszyna jest to urządzenie techniczne zawierające mechanizmy we wspólnym kadłubie słu
ADMINISTRACJA, opracowanie ksiazki Pr. Administracyjne, Administracja- wszelka zorganizowana działal
Dz U 03 61 552 sposób oznakowania miejsc służących do przechowywania lub zawierających substancj
Omówienie procesu emocjonalnego, Psychologia materiały do obrony UJ
,pytania na obronę inż,materiały służące do wykonywania rurociągów
2 5 Pojęcia służące do?dania dynamiki zjawisk kltury
2 4 Pojęcia służące do?dania związku osobowości i kultury
Karta usług - rejestracja sprzetu służącego do połowu ryb, OCHRONA ŚRODOWISKA
Jakie są metody i narzędzia badawcze służące do badania słuchu fonematycznego, Ćwiczenie percepcji s
SCIAGA MASZYNOZNASTWO Duzy, Maszyna jest to urządzenie techniczne zawierające mechanizmy we wspólnym
Fizyka1-wyk ady, KONDENSATOR ELEKTRYCZNY-układ dwóch okładek rozdzielony warstwą dielektryku,służąc
inne2, Bipolarny, Tranzystory są to trójkońcówkowe przyrządy półprzewodnikowe służące do wzmacniania
Maszyny i urządzenia służące do kotwienia, Materiały z Ostrowa
Podstawowe kategorie służące do opisu Różnic Indywidualnych, Psychologia różnic indywidualnych, psyc
Zestaw minisprawdzianów służących do sprawdzenia znajomości lektur dla klas 2

więcej podobnych podstron