awk od podstaw
Po tej lekcji: kursant wyciąga awk-iem kolumny z dowolnego tekstu, filtruje linijki warunkami, liczy sumy i zestawienia i składa z tego czytelny raport.
Wstęp
Serwer muli. Pamięć prawie pełna. Pytanie: który użytkownik ją zjada? ps aux wypisuje kilkaset procesów, każdy z właścicielem i pamięcią. Trzeba je pogrupować po właścicielu i zsumować. W Excelu byś zrobił tabelę przestawną. W terminalu robisz to tak:
ps aux | awk 'NR>1 {mem[$1] += $6} END {for (u in mem) printf "%-10s %8.1f MB\n", u, mem[u]/1024}' | sort -k2 -rnroot 1190.3 MB
postfix 37.8 MB
www-data 30.0 MB
kuba 19.5 MBJedna linijka, tabela przestawna gotowa. Za kwadrans będziesz takie pisać z pamięci, a ludzie w biurze zaczną mówić, że „też umiesz w Excela, tylko na czarnym tle”.
Księgowa z kalkulatorem
Grep to sito, sed to korektorka. A awk to księgowa. Dostaje tabelkę, patrzy na kolumny, liczy, sumuje, a na koniec kładzie na biurko raport. Do tego ma zeszyt, w którym prowadzi własne notatki.
Nazwa wzięła się od pierwszych liter nazwisk twórców: Aho, Weinberger, Kernighan. Pierwsza wersja jest z 1977 roku. Czyli awk jest starszy niż większość ludzi, którzy na niego narzekają.
Awk to tak naprawdę mały język programowania. Każdy program w awku wygląda tak samo:
warunek { akcja }Dla każdej linijki: jeśli warunek pasuje, zrób akcję. Bez warunku akcja działa na każdej linijce. Bez akcji domyślnie wypisuje całą linijkę. Jak kartka z poleceniami dla księgowej: „każdą fakturę z Krakowa dopisz do sumy”.
Program zawsze w apostrofach, czyli w kopercie. Zaraz zobaczysz dlaczego.
Kolumny: $1, $2, $NF
Awk sam dzieli każdą linijkę na kolumny, po awkowemu pola. $1 to pierwsza kolumna, $2 druga, $0 cała linijka.
echo 'Ala ma kota' | awk '{print $3, $1}'
echo ' wiele spacji tu ' | awk '{print $2}'kota Ala
spacjiDruga linijka to ważna różnica względem cut. Domyślnie awk traktuje dowolnie dużo spacji i tabulatorów jak jeden separator. cut -d' ' przy podwójnej spacji zwariowałby i zwracał puste kolumny. Dlatego wyniki ps, df czy ls -l, gdzie kolumny są wyrównane spacjami, tniemy awkiem.
Przecinek w print wstawia między kolumny spację. Bez przecinka awk skleja: print $1 $3 da „root0”.
Dwie wbudowane zmienne, które musisz znać:
NFto liczba pól w bieżącej linijce. A$NFto ostatnie pole,$(NF-1)przedostatnie.NRto numer linijki, od 1.
W naszym auth.log z lekcji 45 adres IP jest czwarty od końca, niezależnie od tego, czy linijka ma „invalid user”, czy nie:
awk '/Failed/ {print $(NF-3)}' auth.log | sort | uniq -c | sort -rn 3 203.0.113.45
2 198.51.100.7
1 192.0.2.10Ten sam wynik co w haku z lekcji 45, bez regeksu na adres IP. Liczenie od końca to trik, który ratuje, kiedy początek linijki pływa.
-F: inny separator
-F mówi, czym są oddzielone kolumny. Na przykład dwukropkiem w /etc/passwd. Do ćwiczeń zrób plik konta.txt w tym samym formacie:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
eryk:x:1000:1000:Eryk:/home/eryk:/bin/bash
ania:x:1001:1001:Ania Nowak:/home/ania:/bin/zsh
stazysta:x:1002:1002::/home/stazysta:/bin/bashawk -F: '{print $1, $7}' konta.txt | head -3root /bin/bash
daemon /usr/sbin/nologin
sshd /usr/sbin/nologinA jeśli chcesz zmienić separator na wyjściu, jest zmienna OFS:
awk -F: -v OFS=';' '{print $1, $3, $7}' konta.txt | head -2root;0;/bin/bash
daemon;1;/usr/sbin/nologinWarunki: które linijki
Przed klamrą możesz napisać warunek. Porównania jak w matematyce: ==, !=, <, >, >=. Łączysz przez && (i) oraz || (lub).
awk -F: '$3 >= 1000 && $3 != 65534 {print $1}' konta.txteryk
ania
stazystaTo są ludzie. Wszystko poniżej UID 1000 to konta systemowe, a 65534 to nobody, który ma UID wyższy niż wszyscy, a prawa niższe niż wszyscy. Jak strażnik z największym numerem na identyfikatorze i bez klucza do czegokolwiek.
[Ubuntu] [RHEL] Na obu rodzinach zwykli użytkownicy dostają UID od 1000. Sprawdzisz to w /etc/login.defs, w linijce UID_MIN.
Warunek może też być wyrażeniem regularnym w ukośnikach, jak w grepie. Albo dopasowaniem jednej kolumny przez ~:
awk -F: '$7 == "/bin/bash" {print NR ": " $1}' konta.txt1: root
5: eryk
7: stazystaNapisy w awku zawsze w cudzysłowach, inaczej awk pomyśli, że to nazwa zmiennej. A awk -F: '$7 ~ /bash/' znaczy „siódma kolumna zawiera bash”.
Sumy: zeszyt księgowej
Zmienne w awku nie trzeba tworzyć. Pierwsze użycie i już jest, z wartością zero albo pustym napisem. Do tego dwa specjalne bloki: BEGIN działa raz przed pierwszą linijką, END raz po ostatniej. Księgowa przed pracą rysuje nagłówek tabelki, a po pracy podlicza.
Plik sprzedaz.csv:
data,sklep,produkt,sztuk,cena
2026-11-01,Krakow,kawa,3,24.50
2026-11-01,Warszawa,herbata,5,12.00
2026-11-02,Krakow,herbata,2,12.00
2026-11-02,Gdansk,kawa,4,24.50
2026-11-03,Krakow,kawa,1,24.50
2026-11-03,Warszawa,kawa,6,24.50awk -F, 'NR>1 {suma += $4*$5} END {printf "Utarg: %.2f zl\n", suma}' sprzedaz.csvUtarg: 427.00 zlNR>1 pomija nagłówek. Dla każdej linijki doliczamy sztuki razy cenę. Na końcu printf, który znasz z lekcji 44, tylko że tu w nawiasach nie trzeba, a argumenty oddziela przecinek. I zauważ: awk liczy na ułamkach. Bash z lekcji 38 przy 24.50 by się obraził.
Zestawienia: tablica jako szatnia
A teraz magia z haka. Tablice w awku działają jak słownik z lekcji 43, szatnia z nazwiskami. Kluczem może być dowolny napis.
awk -F, 'NR>1 {utarg[$2] += $4*$5} END {for (s in utarg) printf "%-10s %8.2f\n", s, utarg[s]}' sprzedaz.csv | sort -k2 -rnWarszawa 207.00
Krakow 122.00
Gdansk 98.00Dla każdej linijki księgowa idzie do wieszaka z nazwą sklepu i dokłada do niego kwotę. Wieszaka nie ma? Awk sam go stawia. W END pętla for (s in utarg) przechodzi po wszystkich wieszakach. Kolejność jest przypadkowa, dlatego na końcu sort.
Ten sam schemat liczy wystąpienia:
awk -F: '{licz[$7]++} END {for (p in licz) print licz[p], p}' konta.txt | sort -rn3 /usr/sbin/nologin
3 /bin/bash
1 /bin/zshPamiętasz zadanie z lekcji 7, ranking powłok przez cut | sort | uniq -c | sort -rn? Tu jest to samo, jednym programem.
Mały raport od początku do końca
Wszystko razem: nagłówek w BEGIN, filtr, licznik, podsumowanie w END.
awk -F: '
BEGIN { printf "%-10s %6s\n", "KONTO", "UID" }
$3 >= 1000 && $3 != 65534 { printf "%-10s %6d\n", $1, $3; n++ }
END { print "Razem ludzi:", n }
' konta.txtKONTO UID
eryk 1000
ania 1001
stazysta 1002
Razem ludzi: 3Program może mieć wiele linijek. Kiedy urośnie, przenieś go do pliku raport.awk i uruchamiaj przez awk -F: -f raport.awk konta.txt.
Jak to działa pod spodem
Awk to interpreter, jak Bash, tylko wyspecjalizowany w tabelkach. Cykl wygląda tak:
- Wykonaj blok
BEGIN. - Wczytaj rekord, domyślnie jedną linijkę. Ustaw
$0iNR. - Pokrój go na pola według separatora
FS(to właśnie ustawia-F). Ustaw$1,$2, ... iNF. - Sprawdź po kolei wszystkie pary „warunek { akcja }”.
- Wróć do punktu 2. Po ostatniej linijce wykonaj
END.
Zmienne są jednocześnie liczbami i napisami. "12" + 3 daje 15, a do $5 z napisem „24.50” można normalnie dodać liczbę. Jak pudełko, które samo zgaduje, czy trzymać w nim śrubki, czy kartkę.
I ważne: „awk” to nie jeden program. To język, który ma kilka implementacji.
- [RHEL] komenda
awkto GNU awk, czyligawk. - [Ubuntu] domyślnie jest
mawk, szybki i oszczędny. Jeśli doinstalujeszgawk, przejmuje on komendęawkprzez mechanizm alternatyw (sprawdziszreadlink -f /usr/bin/awk).
Wszystko z tej lekcji działa w obu. Różnice wychodzą przy dodatkach. Na przykład przy polskich znakach:
echo 'ćma' | mawk '{print length($0)}'
echo 'ćma' | LC_ALL=C.UTF-8 gawk '{print length($0)}'4
3Mawk liczy bajty, a ć w UTF-8 zajmuje dwa. Gawk z ustawionym UTF-8 liczy litery.
[macOS] Na Macu awk to wersja z rodziny BSD, wywodząca się od awka autorów języka (awk --version pokazuje zamiast numeru datę, np. 20200816). Podstawy z tej lekcji działają tak samo. Funkcje typowe dla GNU, na przykład strftime czy sortowanie tablic, nie istnieją w awku z Maca, są w gawk: brew install gawk.
Ściąga flag
| Komenda | Flaga / element | Co robi |
|---|---|---|
| awk | -F X | separator kolumn na wejściu (FS) |
| awk | -v zm=wart | przekazuje wartość z Basha do zmiennej awka |
| awk | -f plik.awk | czyta program z pliku |
| awk | $1, $2, $0 | pierwsza, druga kolumna, cała linijka |
| awk | NF / $NF | liczba kolumn / ostatnia kolumna |
| awk | NR | numer linijki |
| awk | OFS | separator kolumn na wyjściu |
| awk | BEGIN {} / END {} | przed pierwszą / po ostatniej linijce |
| awk | /wzor/ | warunek: linijka pasuje do regeksu |
| awk | $3 ~ /wzor/ | warunek: kolumna 3 pasuje do regeksu |
| awk | && / pionowe kreski / ! | i / lub / nie |
| awk | suma += $4 | sumowanie kolumny |
| awk | t[$1]++ | licznik w tablicy (zestawienie) |
| awk | for (k in t) | pętla po kluczach tablicy |
| awk | printf | wypisywanie z formatem, jak w Bashu |
| awk | length() | długość napisu |
⚠ Typowy błąd
awk -F: "{print $1}" konta.txt | head -2root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinMiała być pierwsza kolumna, a jest cała linijka. Program był w cudzysłowach, a nie w apostrofach. Bash zajrzał do koperty, zobaczył $1 i podstawił swoją zmienną $1, czyli pierwszy argument skryptu. A tu była pusta. Awk dostał {print }, czyli „wypisz wszystko”. Dwa programy, ten sam znaczek dolara, a każdy myśli, że jest jego. Jak dwóch Kowalskich wołanych w poczekalni.
Naprawa: program awka zawsze w apostrofach. A jeśli chcesz przekazać zmienną z Basha, nie sklejaj cudzysłowów, tylko użyj -v:
powloka=/bin/bash
awk -F: -v p="$powloka" '$7 == p {print $1}' konta.txtI drugi, cichy grzech: cut -d' ' na wyniku ps albo df. Kolumny wyrównane spacjami, liczba spacji się zmienia i nagle dostajesz puste pola. Tu zawsze awk.
✎ Zadanie
- Z
/etc/passwdwypisz awk-iem tabelkę kont ludzi (UID od 1000 w górę, beznobody): nazwa, UID i powłoka w równych kolumnach, z nagłówkiem wBEGINi liczbą kont wEND. - Z
df -Pwypisz systemy plików zajęte w ponad 50%: punkt montowania i procent. Podpowiedź: znak procentu przeszkadza w porównaniu. - Z
sprzedaz.csvpolicz, ile sztuk każdego produktu sprzedano łącznie, od największej liczby.
Pokaż rozwiązanie
Część pierwsza, tabelka ludzi z /etc/passwd. Pokazuję na pliku konta.txt z poprzedniej lekcji, na prawdziwym /etc/passwd działa tak samo:
awk -F: '
BEGIN { printf "%-12s %6s %s\n", "KONTO", "UID", "POWLOKA" }
$3 >= 1000 && $1 != "nobody" { printf "%-12s %6d %s\n", $1, $3, $7; n++ }
END { print "Kont ludzi:", n }
' konta.txtKONTO UID POWLOKA
eryk 1000 /bin/bash
ania 1001 /bin/zsh
stazysta 1002 /bin/bash
Kont ludzi: 3Część druga, zajęte systemy plików:
df -P | awk 'NR>1 {zaj = $5; sub(/%/, "", zaj); if (zaj+0 > 50) print $6, $5}'/ 63%
/boot 57%Czemu nie po prostu $5 > 50? Bo „63%” to dla awka napis, a nie liczba. A napisy porównuje znak po znaku, jak słownik. Wtedy „8%” jest większe od „50”, bo ósemka stoi dalej niż piątka. Dysk zajęty w ośmiu procentach dostałby alarm, a ty telefon o trzeciej w nocy. sub wycina procent, a +0 mówi: „to jest liczba, licz jak liczbę”. -P przy df daje stały format bez łamania długich nazw na dwie linijki.
Część trzecia, sztuki na produkt:
awk -F, 'NR>1 {szt[$3] += $4} END {for (p in szt) print szt[p], p}' sprzedaz.csv | sort -rn14 kawa
7 herbataKawa wygrywa. Jak w każdym dziale IT na świecie.
Co dalej
Umiesz już przerabiać to, co jest w plikach. Tylko że najpierw trzeba te pliki znaleźć. W następnej lekcji find: szukanie po nazwie, rozmiarze, dacie, właścicielu i uprawnieniach, a do tego xargs i locate.