Skip to content

wmlynik/unix-shell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sprawozdanie z projektu UXP

Skład zespołu

  • Paweł Kutyła - kierownik
  • Władysław Młynik
  • Konrad Jurczyński-Chu

Treść zadania

Napisać prosty interaktywny interpreter poleceń (shell) posiadający minimum następujące funkcje:

  • Możliwość uruchamiania programów w postaci potoku arbitralnej długości: „proga | progb | progc”.
  • Interpreter powinien uruchamiać dowolne standardowe programy wraz z argumentami.
  • Możliwość przekierowania we/wy do plików: „>”, „<”; łączenie konstrukcji przekierowania z potokami
  • Podstawowe operacje wbudowane: cd, pwd, echo
  • Obsługa cytowania, tj. ‘ … ‘ (pojedyncze zwykłe cudzysłowy) .
  • Obsługa zmiennych shella i środowiskowych (np. PATH; HOME, itp.); poprzez konstrukcję „=” oraz - „export” – ta funkcja nie jest wymagana, ale jej realizacja ułatwia testowanie i demonstrację - funkcjonalności, zwłaszcza w odniesieniu do niektórych wariantów.
  • Pozostałych funkcji typowego shell-a (konstrukcje warunkowe i sterujące, funkcje, aliasy, tablice, rozwijanie wzorców plików, itd.) nie trzeba realizować, chyba że wynikają z zadanego wariant (niżej).
  • W przypadku wątpliwości, co do zachowania i składni, należy bazować na shellu „sh/bash”.
  • Dozwolone, a nawet wskazane, jest użycie jednego ze standardowych narzędzi systemu Unix służących do realizacji analizy leksykalnej i syntaktycznej.
  • Wymaganie dodatkowe: W12 - wzorce nazw plików: tj. *, […], \ .

Interpretacja treści zadania

(tj. doprecyzowanie treści), w tym definicje i opisy wymaganych i dodatkowych funkcji.

  1. Możliwość uruchamiania potoków arbitralnej długości - dowolne wywołania programów (zgodne z dalszymi wymaganiami) rozdzielone znakiem | uruchamiają wszystkie programy jednocześnie, przekazując wyjście programu przed znakiem | na wejście programu z prawej strony znaku |. Można tworzyć dowolnej długości potoki (dowolna liczba wywołań programów oddzielonych znakiem |)

  2. Możliwość uruchamiania programów z podaniem ścieżki względnej lub bezwzględnej lub w przypadku podania nazwy bez ścieżki, uruchamianie programów z katalogów w zmiennej PATH; do programów przekazywane są argumenty oddzielone od nazwy programu i siebie nawzajem dowolną liczbą białych znaków

  3. Na końcu wywołania programu (po wszystkich argumentach) można wpisać znak > lub <, a następnie nazwę lub ścieżkę do pliku. w przypadku znaku > wyjście programu jest przekazywane do tego pliku, w przypadku znaku < wejście programu jest wczytywane z tego pliku. Znak < lub > musi być oddzielony od poprzedzającej części polecenia dowolną liczbą białych znaków. Po znaku < lub > mogą ale nie muszą pojawić się białe znaki. Można użyć obydwu przekierowań dla jednego programu, w dowolnej kolejności. W przypadku łączenia z potokami, kiedy jeden koniec potoku jest zastąpiony przekierowaniem, drugi jest zamykany.

  4. Polecenia cd, pwd i echo są poleceniami wbudowanymi, nie uruchamiają zewnętrznych programów.

  • cd zmienia bieżący katalog na podany
  • pwd wypisuje bieżący katalog
  • echo wypisuje swoje argumenty, oddzielone spacją
  1. W tekście zamkniętym w pojedynczych cudzysłowach ('...') żadne znaki nie są traktowane specjalnie z wyjątkiem:
  • znak ' - kończy cytowany tekst, jeśli nie jest poprzedzony przez \
  • znak \ - sprawie że następny znak nie jest traktowany specjalnie, użyteczne kombinacje: ', \\
  1. Polecenie ZMIENNA=wartość, jeśli nie jest cytowane, przypisuje wartość do zmiennej powłoki ZMIENNA. ZMIENNA może być dowolnym ciągiem liter i cyfr, zaczynającym się od litery. Polecenie export ZMIENNA ustawia zmienną środowiskową ZMIENNA na wartość zmiennej powłoki ZMIENNA. Polecenie export ZMIENNA=wartość ustawia zmienną środowiskową ZMIENNA na podaną wartość.
  2. W12 - W argumentach mogą występować wyrażenia, zastępowane tworząc ścieżkę istniejącego pliku. jeśli jest więcej pasujących plików, są przekazywane jako osobne argumenty. jeśli nie ma pasującego pliku, nie są zastępowane:
  • * - zastępowane dowolnym ciągiem znaków innych niż /
  • [...] - zastępowane dowolnym znakiem wewnątrz nawiasów kwadratowych, mogą być podane zakresy znaków, np. a-z, znaki specjalne (spacja, ], , *, -, itp muszą być poprzedzone znakiem \). Dowolny znak, łącznie ze znakami specjalnymi, poprzedzony znakiem \ jest traktowany dosłownie.

Krótki opis funkcjonalny API

najlepiej w punktach: tj. Podać i krótko opisać docelową formę prototypów głównych funkcji I definicje struktur danych.

Główne funkcje:

  • void run_line(char *line){.c};
    Przyjmuje linię z poleceniem, przetwarza ją i wykonuje.

  • string_arr_t split_tokens(char *line){.c};
    Rozdziela linię na tokeny, oddzielając je białymi znakami, które nie są poprzedzone znakiem \ ani nie są częścią cytowania. Nie usuwa znaków \ ani cytowania.

  • string_arr_t expand_wildcards(char *pattern){.c};
    Obsługuje wzorce nazw plików (np. *, [...]), zwracając tablicę pasujących nazw plików.

  • command_arr_t parse_line(string_arr_t tokens){.c}; Interpretuje tokeny jako elementy poleceń, obsługując cytowanie oraz znak .

  • void execute_line(command_arr_t commands);
    Wykonuje wszystkie polecenia w potoku lub wykonuje pojedyncze polecenie, jeśli jest tylko jedno.

  • void execute_program(char* program, int argc, char **argv, int input_fd, int output_fd){.c};
    Uruchamia program z podanymi argumentami oraz odpowiednimi deskryptorami wejścia i wyjścia.

  • bool execute_builtin(char* program, int argc, char **argv, int input_fd, int output_fd){.c};
    Wykonuje polecenie wbudowane, zwraca true, jeśli takie polecenie istnieje.

  • pipe_t create_pipe(){.c};
    Tworzy nowy potok.

  • void set_env_variable(char *name, char *value){.c};
    Ustala zmienną środowiskową.

  • void set_shell_variable(char *name, char *value, shell_var_t *shell_vars){.c};
    Ustala zmienną powłoki.

Struktury

typedef struct {
    int size;
    char **data;
} string_arr_t;


typedef struct {
    int size;
    command_t *data;
} command_arr_t;


typedef struct {
    int input_fd;
    int output_fd;
} pipe_t;


typedef struct {
    bool is_set_shell_var;
    char *program;
    int argc;
    char **argc;
    char *redirect_in;
    char *redirect_out;
    char *shell_var_name;
    char *shell_var_value;
} command_t;


typedef struct {
    char *name;
    char *val;
    shell_var_t *next;
} shell_var_t;

Proces przetwarzania poleceń

  • Polecenie jest wczytywane przy użyciu funkcji getline.

  • Polecenie wpisane przez użytkownika trafia do funkcji run_line(char *line), gdzie rozpoczyna się proces parsowania.

  • Funkcja split_tokens(char *line) dzieli linię na tokeny, uwzględniając zasady cytowania i specjalne znaki. Wynikiem jest tablica tokenów przechowywana w strukturze string_arr_t. Odbywać się to będzie poprzez przeglądanie linii znak po znaku i wyszukiwanie białych znaków, które nie są poprzedzone znakiem \ ani nie znajdują się w pojedynczym cudzysłowie.

  • Funkcja expand_wildcards(char *pattern) zastępuje wzorce nazw plików odpowiednimi ścieżkami, jeśli istnieje pasujący plik, co pozwala na dynamiczne generowanie listy argumentów. Wykorzystana będzie funkcja glob ze standardowej biblioteki C.

  • Tokeny trafiają do funkcji parse_line(string_arr_t tokens), która tworzy strukturę command_arr_t, interpretując ich znaczenie (np. identyfikacja potoków, przekierowań, zmiennych). Rozpoznawane są specjalne tokeny jak |, >, ZMIENNA=wartość. Wykorzystując fakt, że funkcja split_tokens pozostawia cytowanie, można odróżnić tokeny specjalne od argumentów poleceń je zawierających, np. | oznacza potok, a ‘|’ jest traktowane jako argument polecenia. Na tym etapie cytowanie jest usuwane.

  • Każdy element command_t zawiera komplet informacji o jednym poleceniu: nazwę programu, argumenty, deskryptory wejścia/wyjścia lub informacje o ustawieniu zmiennej powłoki: nazwę i wartość zmiennej.

  • Funkcja execute_line(command_arr_t commands) uruchamia wszystkie polecenia w kolejności wynikającej z parsowania. Tworzy potoki za pomocą pipe() i synchronizuje procesy za pomocą waitpid(). Najpierw próbując uruchomić polecenie wbudowane przy użyciu funkcji execute_builtin, jeśli nie ma takiego polecenia wbudowanego, funkcja zwraca false i uruchamiany jest program przy użyciu funkcji execute_program. Funkcjom tym przekazywane są deskryptory plików potoków lub przekierowań.

Składania

Wykorzystanie zostanie składnia podobna do składni powłoki bash, i zasadniczo została opisana w sekcji interpretacja treści zadania. Elementy składni i ich przykłady użycia:

  • Przypisanie zmiennej: ZMIENNA=wartość, ZMIENNA=’war tość’
  • Wykonanie programu lub polecenia wbudowanego: program arg1 arg2, program arg1, /bin/program arg1 arg2, ./program arg1, ‘./pro gram’ ‘arg 1’
  • Wykonanie programu lub polecenia wbudowanego z potokiem: program1 arg1 arg2 | ‘prog ram2’ arg3 arg4
  • Wykonanie programu lub polecenia wbudowanego z przekierowaniem do pliku: program arg1 arg2 > plik, plik > ‘pro gram’, plik1 > program1 arg1 | /bin/program2 arg2 > plik2
  • Wykorzystanie wzorców nazw plików: /katalog/, katalog/.txt, plik_[a-z]*-[1-3].txt
  • Cytowanie: ‘pro gram’, ‘\\katalog\\pl ik’, ‘cy \’ tat’

Opis i analiza poprawności stosowanych

struktur danych, metod komunikacji, metod synchronizacji, moduły wraz z przepływem sterowania i danych między nimi.

Struktury danych

  • shell_var_t - do przechowywania zmiennych powłoki wykorzystana zostanie lista jednokierunkowa z powodu nieznanej z góry ilości zmiennych. Składa się z nazwy zmiennej, wartości zmiennej oraz wskaźnika na następną zmienną.
  • command_t - do przechowywania informacji o komendzie. Zawiera zmienną informującą czy komenda jest ustawieniem zmiennej powłoki. Na jej podstawie niektóre zmienne, które w danym przypadku nie mają zastosowania, mogą mieć wartość null.
  • W pozostałych przypadkach wykorzystano proste struct-y do przechowywania danych.

Metody komunikacji

  • Zgodnie z zadaniem do komunikacji między procesami wykorzystane zostaną potoki nienazwane.

Metody synchronizacji

  • Przewiduje się wykorzystanie funkcji wait() lub waitpid() do synchronizacji. Ponieważ nie przewiduje się możliwości uruchamiania programów w tle, ww. proste metody są wystarczające.

Moduły

  • Nie przewiduje się podziału projektu na moduły.

Koncepcja realizacji współbieżności

Podstawowa powłoka wśród wielu dostępnych rozwiązań jest jednowątkowa, więc na tym etapie projektu nie została podjęta dokładna decyzja na temat potencjalnej realizacji wielowątkowości w projekcie i jej szczegółach. W kolejnych iteracjach wymaganie to może zostać uwzględnione, jeśli wystąpi potrzeba jej realizacji. W przypadku wieloprocesowości powłoka może uruchamiać wiele procesów, szczególnie w przypadku realizacji potoków, gdzie każdy program stanowi oddzielny proces, a jego wyjście jest przekierowywane na wejście kolejnego procesu. Potoki, w naszym przypadku nienazwane, umożliwiają komunikację między procesami, korzystając z mechanizmów pipe() i fork(). Proces nadrzędny tworzy potoki i przekazuje deskryptory plików do procesów potomnych. Po uruchomieniu procesów, proces nadrzędny czeka na ich zakończenie za pomocą waitpid() lub wait().

Zarys koncepcji implementacji

Głównym językiem programowania wykorzystywanym w ramach projektu będzie język C. Dodatkowo ważną częścią projektu będzie CI&CD, w ramach którego wykorzystane zostanie narzędzie Jenkins i język Groovy. Wstępnie jako bibliotekę używaną do testowania będzie GoogleTest, jednak wybór ten może ulec zmianie o czym wspomniano w dalszej części sprawozdania. Jako narzędzie do code review wykorzystane zostanie narzędzie Gerrit. System do budowania użyty w projekcie to Bazel. Docelowe działanie oprogramowania będzie sprawdzane na systemach Linuxowych oraz macOS.

Zarys koncepcji testów

Grupy testów

Testy w ramach projektu przeprowadzone zostaną na różnych poziomach dla poszczególnych funkcjonalności. Pierwszym poziomem, a zarazem najliczniejszym, będą testy modułowe (jednostkowe) przeprowadzane dla pojedynczych modułów. Kolejną grupą testów będą testy integracyjne, w których zakres będzie wchodzić badanie wzajemnego współdziałania modułów. Grupa ta będzie mniej liczna, jednak stanowić będzie integralną część projektu. Ostatnim zbiorem testów będą testy akceptacyjnego, które zostaną przeprowadzone manualnie wraz z wymaganiami dostarczonymi w wymaganiach projektu i sprawozdaniu wstępnym. Wstępnie wybrana biblioteka do testowania to GoogleTest, jednak wybór ten może ulec zmianie w zależności od potrzeb i potencjalnych integracji.

Ciągła integracja i ciągłe dostarczanie

W ramach projektu zastosowanie będzie podejście CI&CD z wykorzystaniem narzędzia Jenkins. Testy modułowe (jednostkowe) będą wykonywane zawsze przed integracją z główną gałęzią projektu i tylko 100% przechodzenie testów będzie pozwalało na integrację z wcześniej wspomnianym branchem. Testy integracyjne będą wykonywane tylko podczas budowania paczki w procesie ciągłego wdrażanie.

About

Simple shell for UNIX-like systems

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors