awk to nie one-liner do wycinania kolumn
Większość deweloperów zna awk z jednej formy: awk '{print $2}' do wyciągnięcia drugiej kolumny. To jak znać Pythona wyłącznie przez print(). awk to kompletny, Turing-zupełny język programowania z własnym modelem wykonania, typami, tablicami asocjacyjnymi, funkcjami i kontrolą przepływu — zaprojektowany specjalnie do przetwarzania strumieni tekstowych, gdzie bije Pythona o rząd wielkości pod kątem przepustowości.
Nazwa pochodzi od nazwisk twórców (Aho, Weinberger, Kernighan, 1977) — tych samych Aho i Kernighana, których podpisy widnieją na fundamentach informatyki. awk nie jest reliktem; jest precyzyjnym narzędziem, które na strumieniu 10 GB logów przetworzy dane, zanim interpreter Pythona zdąży zaimportować pandas.
Ten artykuł rozkłada awk jako język: model rekord-pole, bloki BEGIN/END, tablice asocjacyjne, maszyny stanowe i — kluczowe dla decyzji produkcyjnych — kiedy mawk bije gawk pięciokrotnie i kiedy w ogóle sięgnąć po awk zamiast Pythona.
Model wykonania: rekord, pole, wzorzec-akcja
Fundament awk to cykl przetwarzania, który większość użytkowników wykonuje nieświadomie. awk czyta wejście rekord po rekordzie (domyślnie linia = rekord), dzieli każdy rekord na pola (domyślnie po białych znakach) i dla każdego rekordu sprawdza zestaw reguł wzorzec → akcja.
# Struktura programu awk: seria reguł pattern { action }
pattern1 { action1 }
pattern2 { action2 }
# Jeśli wzorzec pasuje do bieżącego rekordu → wykonaj akcję
# Brak wzorca → akcja dla każdego rekordu
# Brak akcji → domyślnie { print $0 } (cały rekord)Zmienne wbudowane definiują kontekst każdego rekordu:
| Zmienna | Znaczenie |
|---|---|
$0 | Cały bieżący rekord |
$1, $2, ... $NF | Poszczególne pola (od 1, nie od 0) |
NF | Number of Fields — liczba pól w rekordzie |
NR | Number of Records — numer bieżącego rekordu (globalnie) |
FNR | Numer rekordu w bieżącym pliku (resetuje się między plikami) |
FS | Field Separator — separator pól (domyślnie biały znak) |
OFS | Output Field Separator — separator przy print |
RS | Record Separator — separator rekordów (domyślnie newline) |
Bloki BEGIN i END
Dwa specjalne wzorce zmieniają awk z filtra w pełny program. BEGIN wykonuje się raz przed pierwszym rekordem, END raz po ostatnim. To pozwala na inicjalizację stanu i agregację wyników:
# Suma i średnia trzeciej kolumny — pełny program, nie one-liner
BEGIN {
FS = "," # separator: przecinek (CSV)
OFS = "\t" # output: tab
print "Przetwarzanie..."
}
# Główna pętla — wykonuje się dla każdego rekordu
NR > 1 { # pomiń nagłówek (rekord 1)
sum += $3
count++
}
# Wykonuje się raz, po przetworzeniu całości
END {
if (count > 0)
printf "Suma: %.2f | Średnia: %.2f | Rekordów: %d\n", \
sum, sum / count, count
}Tablice asocjacyjne — gdzie awk pokazuje pazury
Tablice w awk są zawsze asocjacyjne (hash maps) — indeksowane dowolnym stringiem, nie tylko liczbą. To czyni awk naturalnym narzędziem do grupowania, zliczania i deduplikacji bez żadnych zewnętrznych struktur danych.
# Zliczanie wystąpień IP w logu Apache — klasyczny problem produkcyjny
# Wejście: standardowy access.log (IP w pierwszym polu)
{
ip_count[$1]++ # tablica indeksowana adresem IP
}
END {
# Iteracja po kluczach tablicy asocjacyjnej
for (ip in ip_count) {
print ip_count[ip], ip
}
}
# Użycie z sortowaniem top 10:
# awk -f count_ips.awk access.log | sort -rn | head -10Ten 6-liniowy program zastępuje typowy pipeline cut | sort | uniq -c | sort -rn i jest znacznie szybszy, bo wykonuje wszystko w jednym przejściu (single pass) zamiast wielokrotnie sortować cały strumień. Dla pliku 5 GB różnica to minuty.
Deduplikacja zachowująca kolejność
Problem, którego sort -u nie rozwiąże, bo niszczy kolejność. awk robi to trywialnie:
# Usuń duplikaty, zachowaj pierwszą kolejność wystąpienia
# (sort -u zmieniłby kolejność — to inny rezultat)
!seen[$0]++
# Mechanika: seen[$0] to 0 (false) przy pierwszym wystąpieniu,
# !0 = true → linia drukowana. Post-inkrementacja czyni ją >0,
# więc !seen[$0] = false przy kolejnych → linia pomijana.To idiomatyczny awk: jedna linia, zero zewnętrznych zależności, single pass. Wyrażenie !seen[$0]++ to wzorzec bez akcji — domyślna akcja { print $0 } wykonuje się tylko gdy wzorzec jest prawdziwy.
awk jako maszyna stanowa
Tu awk przekracza granicę „narzędzia tekstowego” i staje się pełnym językiem. Range patterns (/start/,/end/) i zmienne stanu pozwalają parsować strukturalne formaty — sekcje, bloki, zagnieżdżenia.
# Parser sekcji w pliku konfiguracyjnym (format INI)
# Ekstrakcja wszystkich kluczy z konkretnej sekcji [database]
BEGIN { in_section = 0 }
# Wykrycie nagłówka sekcji
/^\[.*\]$/ {
# Usuń nawiasy, porównaj nazwę sekcji
section = substr($0, 2, length($0) - 2)
in_section = (section == "database") ? 1 : 0
next # przejdź do następnego rekordu
}
# Wewnątrz docelowej sekcji i linia to klucz=wartość
in_section && /=/ {
split($0, kv, "=")
gsub(/^[ \t]+|[ \t]+$/, "", kv[1]) # trim klucza
gsub(/^[ \t]+|[ \t]+$/, "", kv[2]) # trim wartości
config[kv[1]] = kv[2]
}
END {
for (key in config)
printf "%-20s = %s\n", key, config[key]
}To kompletny parser stanowy: śledzi w której sekcji się znajduje (in_section), reaguje różnie w zależności od stanu, buduje strukturę wynikową. Napisanie tego w Pythonie zajęłoby 3× więcej kodu i działało wolniej na dużych plikach.
Funkcje zdefiniowane przez użytkownika
awk wspiera własne funkcje z rekurencją i lokalnym zakresem — kolejny dowód, że to pełny język:
# Funkcja rekurencyjna: silnia (dowód Turing-zupełności)
function factorial(n) {
return (n <= 1) ? 1 : n * factorial(n - 1)
}
# Funkcja z parametrami lokalnymi (konwencja: dodatkowe params = locals)
function trim(str, result) { # 'result' to zmienna lokalna
result = str
gsub(/^[ \t\r\n]+|[ \t\r\n]+$/, "", result)
return result
}
BEGIN {
print factorial(10) # 3628800
print "[" trim(" hello ") "]" # [hello]
}Uwaga na konwencję: awk nie ma jawnych zmiennych lokalnych. Idiom to dodatkowe parametry funkcji (po wcięciu dla czytelności) — są inicjalizowane na pusty string i działają jako lokalne. To jedyny sposób na uniknięcie zanieczyszczenia globalnego zakresu.
mawk vs gawk vs busybox awk — pomiar, nie ideologia
Istnieje kilka implementacji awk o fundamentalnie różnej charakterystyce wydajnościowej. Wybór ma realne konsekwencje produkcyjne.
| Implementacja | Charakterystyka | Throughput (relatywny) |
|---|---|---|
| mawk | Minimalistyczna, kompilacja do bytecode, najszybsza | ★★★★★ (baseline) |
| gawk (GNU) | Najbogatsza: rozszerzenia, Unicode, networking, debugger | ★★★ (~2–5× wolniej) |
| busybox awk | Wbudowana w systemy embedded, minimalna | ★★ (zmienne) |
| goawk | Implementacja w Go, dobra na CSV, przenośna | ★★★ (porównywalna z gawk) |
# Benchmark na realnym pliku — 10M linii, sumowanie kolumny
$ time mawk '{s+=$1} END{print s}' bignum.txt
real 0m1.243s
$ time gawk '{s+=$1} END{print s}' bignum.txt
real 0m5.876s # ~4.7× wolniej na czystej arytmetyce
# Sprawdź którą implementację masz domyślnie
$ ls -l $(which awk)
lrwxrwxrwx ... /usr/bin/awk -> mawk # Debian/Ubuntu często mawk
# lub -> gawk na większości innych dystrybucjiReguła praktyczna: mawk do prostego, masowego przetwarzania (sumowanie, filtrowanie, zliczanie na gigabajtowych strumieniach), gawk gdy potrzebujesz Unicode, tablic wielowymiarowych, gensub(), sortowania wbudowanego (asort) lub innych rozszerzeń GNU. Na Debianie domyślny awk to często mawk — co oznacza, że skrypty używające rozszerzeń gawk cicho zawiodą.
awk vs Python — kiedy co
| Scenariusz | Wybór |
|---|---|
| Filtrowanie/sumowanie kolumn na strumieniu >1 GB | awk (mawk) — single pass, minimalny narzut |
Pipeline w shellu (część cmd | awk | cmd) | awk — natywny obywatel uniksowego pipeline |
| Prosta agregacja/grupowanie w jednym przejściu | awk — tablice asocjacyjne za darmo |
| Logika >50 linii, wiele struktur danych | Python — czytelność, testowalność |
| Parsowanie JSON/XML/formatów zagnieżdżonych | Python — awk nie ma natywnego parsera |
| Integracja z API, bazami, zewnętrznymi bibliotekami | Python — ekosystem |
| Jednorazowa transformacja w terminalu | awk — zero boilerplate, zero importów |
| Kod produkcyjny wymagający testów jednostkowych | Python — awk trudno testować |
Granica jest klarowna: awk wygrywa na strumieniach tabularycznych w pipeline, gdzie liczy się przepustowość i zwięzłość. Python wygrywa, gdy logika rośnie, dochodzą struktury zagnieżdżone albo kod ma żyć długo i być utrzymywany. Sięganie po Pythona do print $2 to over-engineering; pisanie parsera JSON w awk to inżynierski masochizm.
Praktyczny przykład: analiza logów w jednym przejściu
# Pełna analiza access.log: status codes, top IP, transfer total
# Jeden pass przez plik, wiele agregacji jednocześnie
BEGIN {
OFS = "\t"
}
{
# Pole 9 = status HTTP, pole 1 = IP, pole 10 = bajty
status[$9]++
ip_hits[$1]++
total_bytes += ($10 ~ /^[0-9]+$/) ? $10 : 0
if ($9 >= 500) errors_5xx++
if ($9 >= 400 && $9 < 500) errors_4xx++
}
END {
print "=== Status Codes ==="
for (code in status)
print code, status[code]
print "\n=== Transfer ==="
printf "Total: %.2f MB\n", total_bytes / 1024 / 1024
printf "4xx: %d | 5xx: %d\n", errors_4xx, errors_5xx
print "\n=== Rekordów przetworzonych ==="
print NR
}Ten program robi w jednym przejściu to, co wymagałoby kilku osobnych pipeline’ów grep | wc. Na pliku 10 GB to różnica między 8 sekundami a kilkoma minutami — bo dysk czytany jest raz, nie pięć razy.
Podsumowanie: awk jako precyzyjne narzędzie, nie relikt
awk to nie przeżytek z lat 70. ani „gorszy Python do tekstu”. To wyspecjalizowany język, który na swojej domenie — strumienie tabularyczne w uniksowym pipeline — bije narzędzia ogólnego przeznaczenia o rząd wielkości pod kątem przepustowości i zwięzłości. Inżynier znający awk jako język, a nie jako {print $2}, rozwiązuje całą klasę problemów w jednej linii tam, gdzie inni piszą skrypt.
Kluczowe zrozumienia: model rekord-pole z cyklem przetwarzania, bloki BEGIN/END dla stanu i agregacji, tablice asocjacyjne dla grupowania bez zewnętrznych struktur, oraz świadomość że mawk i gawk to różne narzędzia o różnej wydajności. Ta wiedza zamienia awk z czarnej skrzynki do wycinania kolumn w pełnoprawne narzędzie inżynierskie.
Następnym razem, gdy odruchowo sięgniesz po Pythona do przetworzenia wielkiego CSV w pipeline — zważ, czy awk nie zrobi tego pięć razy szybciej w jednej dziesiątej kodu.
Potencjalne linki wewnętrzne
.bashrc — anatomia startu shella i optymalizacja— awk jako część uniksowego workflowCzytanie cudzego kodu — jak nawigować bez rabbit holes— awk do szybkiej analizy nieznanych logówDebugging — jak robi to prawdziwy deweloper— awk jako narzędzie analizy strumieni diagnostycznych


