===== SED i AWK =====
==== Przygotuj się do laboratorium ====
  * [[.:lab_filters|Powtórzyć zasady pisania filtrów i skryptów powłoki sh]]
  * Daniel Robbins - //Sed by example//: {{ sed-by-example-1.pdf |część 1}}, {{ sed-by-example-2.pdf |część 2}}, {{ sed-by-example-3.pdf |część 3}} (pierwotnie było dostępne [[https://www.ibm.com/developerworks/linux/library/l-sed1/|pod tym adresem]])
    * Wersja polska ([[http://gentoo-handbook.lugons.org/doc/pl/articles/l-sed1.xml|źródło]]): {{ sed-by-example-1-pl.pdf |część 1}}, {{ sed-by-example-2-pl.pdf |część 2}}, {{ sed-by-example-3-pl.pdf |część 3}}
  * Daniel Robbins - //Awk by example//: {{ awk-by-example-1.pdf |część 1}}, {{ awk-by-example-2.pdf |część 2}}, {{ awk-by-example-3.pdf |część 3}} (pierwotnie było dostępne [[https://www.ibm.com/developerworks/linux/library/l-awk1/|pod tym adresem]])
    * Wersja polska ([[http://gentoo-handbook.lugons.org/doc/pl/articles/l-awk1.xml|źródło]]): {{ awk-by-example-1-pl.pdf |część 1}}, {{ awk-by-example-2-pl.pdf |część 2}}, {{ awk-by-example-3-pl.pdf |część 3}}
==== Wiedza ====
=== 1. SED i AWK ===
  * SED i Awk są narzędziami strumieniowego przetwarzania tekstu.
  * Ich główną zaletą jest możliwość czytania ze standardowego wejścia i wyświetlanie rezultatów na standardowym wyjściu -- jeżeli nie wiesz dlaczego to jest zaleta, przypomnij sobie działanie narzędzi textutils z [[lab_filters|Podstawy skryptów i filtrów]].
    * Dane mogą być wczytywane również z pliku.
    * Podczas pracy nie modyfikują żadnych plików.
  * Posiadają szereg instrukcji manipulujących łańcuchami znaków (wstawianie, zastępowanie, itp).
  * Instrukcje mogą być podawane z linii komend (w przypadku małych ilości poleceń) lub czytane z pliku (co zwiększa czytelność w przypadku dużej ilości poleceń).
  * Obsługują wyrażenia regularne!
SED:
  * ang. **S**tream **ED**itor - edytor strumieniowy.
  * Pomimo swej prostoty umożliwia wykonanie złożonych operacji na łańcuchach znaków.
  * Program przyjmuje polecenia do wykonania z pliku lub z wiersza poleceń (jak zdefinować instrukcje do wykonania z linii poleceń a jak z pliku? - zob. manual).
  * Program wczytuje dane wejściowe linia po linii i **dla każdej linii wykonuje wszystkie** instrukcje!
AWK:
  * Nazwa pochodzi od pierwszych liter nazwisk jego autorów: //Alfreda V. **A**ho, Petera **W**einbergera i Briana **K**ernighana//
  * Jest to program interpretujący język skryptowy, służący do przetwarzania wyrażeń regularnych.
  * Składnia języka jest bardzo podobna do składni języka C.
  * Program wczytuje dane wejściowe rekord po rekordzie i dla każdego rekordu wykonuje wszystkie polecenia.
=== 2. Wyrażenia regularne ===
  * ang. //regular expressions//, w skrócie //regex// lub //regexp//
  * Są **wzorcami** opisującymi łańcuchy znaków.
  * Umożliwiają odnalezienie określonej regularności w danej sekwencji znaków.
Definiowanie wyrażeń regularnych:
  * Wyrażenia regularne składają się dwóch podstawowych zestawów znaków:
    * Znaki kontrolne: ''. ? * + ^ | $ () [] {} \''
    * Zwykłe znaki: pozostałe
  * Kolejność znaków w wyrażeniu jest **istotna**.
  * Wielkość liter w wyrażeniu jest także **istotna**.
  * Wyrażenie **g** opisuje wszystkie łańcuchy znaków zawierające literę **g** np. //**g**itara// (ale nie //Gitara//), //za**g**adka//, //lo**g**//.
  * Wyrażenie **ba** opisuje wszystkie łańcuchy znaków zawierające podciąg **ba** np. //**ba**ca//, //za**ba**wa//, //ża**ba**//.
  * Kropka **.** określa dowolny znak oprócz znaku końca wiersza np. **a.c** pasuje do: //aac//, //abc//, ale nie pasuje do //ac//.
  * Nawias kwadratowy pozwala na określenie listy, zakresu znaków np. a[abc]c pasuje do napisów zawierających podciągi: //aac//, //abc//, //acc// - czyli pomiędzy znakami **a** i **c** może wystąpić tylko i wyłącznie jeden znak z podanych w nawiasie. **Uwaga**, należy zauważyć że powyższe wyrażenie pasuje również do: //aabc//, //acccc//, itp. Powyższe wyrażenie jest równoznaczne z **a[a-c]c** poprzez zdefiniowane zakresu za pomocą **-** myślnika. Istnieje także możliwość listowania zakresów: **[a-z0-9]** - oznacza dowolną małą literę od **a** do **z** lub dowolną cyfrę.
  * Negacja zakresu ^. Znak ^ umieszczony jako pierwszy wewnątrz nawiasu kwadratowego oznacza jego negację np. **[^a-c]** oznacza wszystkie znaki oprócz **a**, **b**, **c**.
  * Znak ^ ma także drugie znaczenie (żeby nie było tak prosto ^_^). Oznacza początek łańcucha znaków, czyli  **^ab** określa wszystkie łańcuchy znaków rozpoczynające się od **ab** np. //**ab**akus//, natomiast nie pasuje do np: //żaba//.
  * Podobne znaczenie do ^ ma znak **$** oznaczający koniec łańcucha znaków.
  * Okrągłe nawiasy () służą do grupowania części wyrażeń (wykorzystywane do np. określania liczby powtórzeń danego wzorca).
  * Powtórzenia: liczba powtórzeń określa ilość wystąpienia danego wzorca w badanym łańcuchu znaków. Liczbę powtórzeń możemy zdefiniować w następujący sposób:
    * Gwiazdka * - zero lub więcej wystąpień poprzedzającego ją znaku np: **a*** pasuje do //ab//, //aab//, //bab//, //baaab//, ale też do: //b//.
    * Plus + - co najmniej jedno wystąpienie poprzedzającego znaku np: **a+** pasuje do //ab//, //aab//, //bab//, //baaab//, ale **nie** do: //b//.
    * Znak zapytania ? po symbolu oznacza najwyżej jedno (być może zero) wystąpienie poprzedzającego wyrażenia.
    * Nawias klamrowy {} pozwala na dokładne określenie minimalnej i maksymalnej liczby powtórzeń:
      * {2,5} - minimalnie 2 a maksymalnie 5,
      * {3} - dokładnie 3 razy,
      * {5,} - co najmniej 5 razy,
      * {,6} - co najwyżej 6 razy.
  * Pionowa kreska (ang. pipeline) | to operator OR np. jeśli napiszemy a|b|c oznacza to, że w danym wyrażeniu może wystąpić a lub b lub c.
  * Jeżeli chcemy użyć jednego ze znaków kontrolnych jako zwykłego to poprzedzamy go backslash-em **\** np: **^\$** definiuje wszystkie łańcuchy znaków rozpoczynające się od znaku dolara ''$''.
Przykłady:
  * **Kod pocztowy** -- polski kod pocztowy składa się z pięciu cyfr i myślnika wstawionego po drugiej cyfrze. Przy pomocy wyrażeń regularnych wzorzec kodu pocztowego może być zdefiniowany następująco:
    * **^[0-9][0-9]-[0-9][0-9][0-9]$**
    * **^[0-9]{2}-[0-9]{3}$**
  * **Liczby w Lotto** -- w głównej grze Lotto losowane są liczby od 1 do 49. Przy pomocy wyrażenia regularnego można sprawdzić czy dana liczba może być wylosowana:
    * **^[1-4]?[0-9]{1}$**
    * **^[1-4]{,1}[0-9]{1}$**
  * **Adres MAC** -- 48-bitowy numer karty sieciowej. Najczęściej zapisuje się go w postaci heksadecymalnej. Po każdych dwóch znakach może występować (ale nie musi) separator (najczęściej są to: dwukropek, myślnik, spacja)
    * Przykładowy format adresu MAC to:
      * 1F:34:C6:78:90:AB
      * 1F-34-C6-78-90-ab
      * 1f 34 c6 78 90 AB
      * 1F34C67890AB
    * Jak krok po kroku zbudować wyrażenie regularne akceptujące powyższe zapisy?
      - **[A-Fa-f0-9]{2}** - pierwsze dwa znaki (8 bitów bez separatora),
      - **[-: ]?** - separatory występujące co najwyżej jeden raz,
      - **[A-Fa-f0-9]{2}** - kolejne części adresu (ostatnie 40 bitów)
      - Wyrażenie składające się z wyr. 2 i 3 trzeba powtórzyć 5 razy: **([-: ]?[A-Fa-f0-9]{2}){5}**
      - Czyli całość może wyglądać następująco: **^[A-Fa-f0-9]{2}([-: ]?[A-Fa-f0-9]{2}){5}$**
==== Ćwiczenia ====
=== I. Wyrażenia regularne ===
Jeżeli chcesz pobawić się wyrażeniami regularnymi to warto odwiedzić: [[https://regexcrossword.com/|Regex Crossword]] :-)
Jeżeli po prostu chcesz przetestować i zrozumieć wyrażenia regularne to w sieci dostępnych jest wiele webowych serwisów, które pozwalają na wpisanie wyrażenia, wyjaśniają jego poszczególne elementy i wyświetlają dopasowania na tekście, np.:
  * [[https://regexr.com/|RegExr]]
  * [[https://regex101.com/|regex101]]
=== II. SED ===
Przeczytaj manual do programu. Zwróć uwagę na polecenia, ich składnię oraz sposób adresacji poleceń.
  - Wyświetl plik **/etc/passwd** przy pomocy //sed//.
  - Zamień separator - dwukropek - w pliku ''/etc/passwd'' na spację.
  - Wyświetl **tylko** loginy użytkowników zapisanych w pliku ''/etc/passwd''
  - Wyświetl 4, 7, 10 i 13 linię pliku ''/etc/passwd''
  - Wyświetl określone przedziałem (np. od 3. do 5. włącznie) linie pliku ''/etc/passwd''.
  - Wyświetl linie pliku ''/etc/passwd'' opisujące osoby mające login zaczynający się na 'z'.
  - Wyświetl linie pliku ''/etc/passwd'' opisujące osoby mające login zaczynający się na 'w' lub 'z'.
  - Jak przy pomocy sed zaimitować polecenie ''grep -v''? np. dla frazy 'lo' (''grep -v lo /etc/networks'')
  - Jak zamienić w pliku wszystkie słowa ''root'' na twój login (przetestuj na pliku ''/etc/passwd'')?
  - Jak zamienić słowo 'root' na twój login w pliku, ale tylko w wierszach, w których występuje 'www'? A jak tam gdzie nie występuje?
  - Jak usunąć z pliku puste linie? 
  - Jak zamienić przy pomocy sed wszystkie litery 'r' na 'k'?
  - W jaki sposób zakodować szyfrem ROT13 plik przy pomocy sed (szyfr zamienia litery na występujące 13 liter dalej, np. a<->n, b<->o, itd.)?
  - Przy pomocy polecenia sed zakomentuj linijkę link-local w pliku ''/etc/networks''.
  - W jaki sposób przy użyciu sed wstawić kolumnę X po pierwszym znaku wiersza (dodatkowy znak X w każdym wierszu)? A jak po piątym?
  - Jak przy pomocy sed powtórzyć 3 razy pierwsze dwie litery każdego wiersza w pliku?
  - Wylistuj wszystkie wiersze pliku ''/etc/mime.types'' zaczynające się od ''video'' i wyświetl ich numer.
  - Napisz polecenie sed imitujące polecenie ''cut -d: -f2''.
  - Napisz polecenie sed imitujące ''cat -n''.
  - Napisać **skrypt** programu //sed// który ustawi powłokę startową na ''/bin/tcsh'' dla wszystkich użytkowników posiadającym folder domowy ''/nonexistent'' (zmodyfikuje odpowiednio plik ''/etc/passwd''). Skrypt ma wydrukować całą zawartość zmienionego pliku na ekranie wraz z zaznaczeniem zmienionych linii - tak jak jest to przedstawione poniżej:
----------------------------
linia w której nastąpiła zmiana powłoki
----------------------------
  - Napisać **skrypt** programu //sed// wyświetlający linię zawarte w pliku ''/etc/passwd'' w odwrotnej kolejności.
=== III. AWK ===
  - Przeczytać manual do programu zwrócić uwagę na:
    * wbudowane zmienne,
    * wbudowane funkcje,
    * wbudowane instrukcje sterujące.
  - Jak //awk// interpretuje spacje?
  - Co określają następujące zmienne: ''FS'', ''RS'', ''NF'', ''NR'', ''OFS'', ''ORS''?
  - Kiedy wykonywane są instrukcje zawarte w blokach ''BEGIN'' oraz ''END'' w przypadku kiedy zródło danych składa się z wielu plików?
  - Zapisać dowolną stronę internetową (może być także ta) w formacie HTML. Wyświetlić jej zawartość przy pomocy polecenia //cat// a następnie przy pomocy programu //awk// usunąć wszystkie znaczniki HTML z treści. Rezultat należy wyświetlić na ekranie.
  - Do powyższego zadania dopisać element filtru, który usunie wszystkie puste linie, oraz te które posiadają tylko białe znaki.
  - **Zadanie1**:
    - Przeanalizować poniższy skrypt (plik **userlist**): 
#!/bin/sh
who | awk '{print $1}' | sort | uniq | xargs -i"{}" grep -e "^{}:" /etc/passwd | awk -f awkuserlist
 oraz plik **awkuserlist** (skrypt programu //awk//) który jest w nim wykorzystany: 
BEGIN {
    FS=":"
    print "";
}
{
    match($5, "^[^, ]*");
    imie=substr($5, RSTART, RLENGTH);
    match($5, " [^, ]*");
    nazwisko=substr($5, RSTART+1, RLENGTH-1);
    login=$1;
    uid=$3;
    gid=$4;
    home=$6;
    shell=$7;
    print "";
    print ""imie"";
    print ""nazwisko"";
    print ""login"";
    print ""uid"";
    print ""gid"";
    print ""home"";
    print ""shell"";
    print "";
}
END {
    print "";
}
    - Jaki jest rezultat wykonania skryptu **userlist**?
      * Uwaga, na serwerze spk ten skrypt nie działa (dane użytkowników nie są przechowywane w /etc/passwd) - w takiej sytuacji zastąp fragment generujący aktualną listę obecności (''who | awk '{print $1}' '') przez fragment, który wprost zadaje przykładowe loginy z /etc/passwd (np. ''echo -e 'root\ngames\nman' '')
    - Dopisać skrypt programu //awk//, który przeanalizuje strumień wyjściowy skryptu **userlist** i przekształci go w następujący sposób:
      * Format wejściowy:
     wartosc1
     wartosc2
     wartosc3
     wartosc4
      * Format wyjściowy:
-------------------------------------------
dana1: wartosc1
dana2: wartosc2
dana3: wartosc3
dana4: wartosc4
-------------------------------------------
      * Przykład:
        * Dla wejścia:
     Jan
     Kowalski
     jkowalski
     1
     1
     /home/users/jkowalski
     /bin/bash
     Kraków
        * Skrypt powinien wygenerować wyjście:
-------------------------------------------
imie: Jan
nazwisko: Kowalski
login: jkowalski
uid: 1
gid: 1
home: /home/users/jkowalski
shell: /bin/bash
miasto: Kraków
-------------------------------------------
  - **Zadanie 2**:
    * **BibTeX** to narzędzie służące do formatowania bibliografii. Operuje ono na danych zawartych w plikach o rozszerzeniu "bib" zawierających dane bibliograficzne. Przykład pliku: {{publikacje.bib|}}.
    * Poszczególne wpisy bibliograficzne mają następującą postać: 
@rodzaj{klucz,
  author = {wartość},
  title = {wartość},
  year = wartość,
  other = {wartość}
}
    * Zadanie polega na stworzeniu skryptu ''bash'' o nazwie ''szukaj'', który przy użyciu ''awk'' wybierze z pliku ''publikacje.bib'' tylko te wpisy bibliograficzne, które odpowiadają zapytaniu użytkownika. Skrypt powinien obsługiwać trzy opcje (''-a'', ''-t'', ''-k''):
      * ''./szukaj -a Nalepa'' - wyszuka wszystkie publikacje autorstwa Nalepy (te wpisy bibtexowe, w których polu ''author'' występuje ''Nalepa''),
      * ''./szukaj -t slowo'' - wyszuka wszystkie publikacje zawierające w tytule ciąg ''slowo'',
      * ''./szukaj -k slowo'' - wyszuka wszystkie publikacje zawierające ciąg ''slowo'' w dowolnym polu.
  - **Zadanie 3**: Napisz skrypt programu //awk//, który policzy i wyświetli średnią ocen dla każdego studenta.
    * Lista studentów oraz ocen jest zapisana w pliku, którego nazwę podajemy jako parametr uruchomienia programu //awk// (nazwa pliku nie może być zakodowana wewnątrz skryptu).
    * Format pliku zawierającego listę studentów oraz ocen jest następujący:
login_1:ocena_1,ocena_2,ocena_3
login_2:ocena_1,ocena_2,ocena_3,ocena_4
    * Założenia:
      - ''login_n'' - jest loginem danego studenta
      - Liczba studentów nie jest określona, definiuje ją tylko i wyłącznie liczba linii w pliku.
      - Plik zawiera tylko linie w powyższym formacie - przyjmujemy jako aksjomat i nie weryfikujemy tej kwestii.
      - ''ocena_n'' - oznacza ocenę, liczbę ze zbioru ''{2.0, 3.0, 3.5, 4.0, 4.5, 5.0}'' (nie ma przymusu sprawdzania poprawności - przyjmujemy że oceny są wpisane poprawnie).
      - Liczba ocen dla każdego studenta nie jest określona i może być różna.
      - Spacje w pliku nie wpływają na sposób jego przetwarzania.
    * Jako rezultat, skrypt powinien wyświetlić informację o uzyskanej średniej ocenie przez każdego studenta w następującym formacie Srednia ocena dla Login: X gdzie ''X'' to wartość średniej oceny z dokładnością do dwóch miejsc po przecinku.
    * Skrypt powinien być w całości wykonywany przez awk - nie powinien używać żadnych poleceń zewnętrznych.
    * Przykład:
      * Dla wejścia: spock:4.0, 4.5, 4.5, 5.0, 5.0, 2.0
scotty : 4.5, 3.0, 4.0, 3.5, 5.0, 2.0, 5.0, 5.0, 4.5
sulu:3.5     ,  3.0, 5.0, 4.5, 2.0, 3.5, 4.5, 3.0, 2.0, 4.5, 4.0, 5.0, 2.0, 5.0, 3.5, 4.5, 5.0, 5.0, 4.5, 4.0, 5.0, 3.5, 5.0, 5.0, 5.0, 4.5, 5.0, 3.5, 3.5, 4.0, 4.0, 3.5, 4.0, 5.0, 3.0, 3.0
chekov:2.0, 4.5, 5.0     , 5.0, 4.0, 5.0, 5.0, 2.0, 3.0, 4.5
yar:3.0, 2.0, 5.0,   2.0, 3.0, 5.0, 3.0, 4.5, 5.0, 5.0, 5.0, 5.0, 4.5, 3.5, 2.0, 3.5, 4.0, 3.5, 2.0
enterprise:3.5, 2.0, 3.0, 3.0   
riker    :   3.0, 3.0, 3.0, 2.0, 4.0, 2.0, 5.0, 5.0, 4.0, 2.0, 5.0, 3.5, 3.0, 2.0, 2.0, 4.5, 4.5, 5.0, 3.5, 4.5, 3.5, 4.5, 3.0, 5.0, 5.0, 3.0, 3.5, 3.5, 5.0, 5.0, 4.5, 3.0, 2.0, 5.0, 5.0, 3.0, 4.5, 3.0
picard : 3.0  , 3.0, 3.0, 4.0, 3.5, 4.0, 3.0, 5.0, 5.0, 4.5, 4.0
laforge:    4.0, 4.5, 5.0, 3.5, 4.5, 4.5, 4.5, 2.0, 3.0, 3.0, 3.5
yoda:3.5, 4.0, 4.5, 4.5, 2.0
worf    :2.0, 5.0, 4.0, 4.0, 3.5, 4.5    , 5.0
starfleet:5.0, 4.5, 3.5, 3.0, 4.0, 3.0, 4.0, 2.0, 3.0, 2.0, 5.0, 2.0, 3.5, 4.5, 4.5, 4.0, 3.0, 3.0, 3.5, 3.0, 3.0, 3.5, 2.0, 3.0, 3.5, 4.0, 4.5, 5.0, 3.5, 5.0, 3.5, 3.5, 3.5, 3.0, 5.0, 3.5, 4.5, 3.0, 4.5, 4.5, 5.0, 4.5, 3.5, 2.0, 3.0, 3.0, 4.0, 5.0, 5.0
kirk:4.0, 3.0, 5.0, 5.0, 5.0, 2.0, 2.0, 4.0
uhura   :   3.5, 2.0, 5.0, 5.0, 5.0   , 4.0, 5.0, 5.0, 4.0, 4.5, 3.0, 3.0, 3.5, 4.0, 3.5, 4.0, 5.0, 4.0, 3.0, 4.5, 4.5, 5.0, 3.5, 4.0, 4.5, 5.0, 5.0, 5.0, 3.0, 5.0, 4.0, 4.0, 5.0, 4.5, 5.0, 3.0, 4.0, 5.0, 4.5, 2.0, 4.5, 5.0, 4.0, 3.0, 3.0, 4.5, 2.0, 2.0, 3.0, 3.5, 5.0
      * Skrypt powinien wygenerować wyjście: Srednia ocen dla spock: 4.17
Srednia ocen dla scotty: 4.06
Srednia ocen dla sulu: 4.01
Srednia ocen dla chekov: 4
Srednia ocen dla yar: 3.71
Srednia ocen dla enterprise: 2.88
Srednia ocen dla riker: 3.72
Srednia ocen dla picard: 3.82
Srednia ocen dla laforge: 3.82
Srednia ocen dla yoda: 3.70
Srednia ocen dla worf: 4
Srednia ocen dla starfleet: 3.68
Srednia ocen dla kirk: 3.75
Srednia ocen dla uhura: 4.03
Plus za zrobienie co najmniej 15 poleceń. 
"linux SED i AWK odpowiedzi - gr2"
maciej.szelazek@uj.edu.pl