Praca z plikami w terminalu: find, grep, sed i awk na praktycznych przykładach

0
143
Rate this post

Nawigacja:

Po co w ogóle bawić się w find, grep, sed i awk

Praca z plikami w terminalu kojarzy się wielu osobom z „czarną magią” dla adminów. W praktyce to tani, szybki i bardzo elastyczny zestaw narzędzi, który zastępuje drogie programy, rozbudowane IDE i godziny klikania myszką. Wystarczy kilka poleceń, aby przetworzyć tysiące plików lub gigabajty logów bez otwierania choćby jednego okna GUI.

find, grep, sed i awk są dostępne praktycznie wszędzie: na każdej dystrybucji Linuksa, w terminalu macOS, a pod Windows można je uruchomić choćby przez WSL, Git Bash lub Cygwin. Nie wymagają licencji, nie zużywają prawie zasobów i działają dobrze nawet na słabszych maszynach. Dla „budżetowego pragmatyka” to w zasadzie zestaw obowiązkowy.

Największa przewaga tych narzędzi to relacja efektu do wysiłku. Krótkie opanowanie podstaw daje realny zysk: zamiast ręcznie filtrować logi w Notatniku czy Excelu, można jednym poleceniem wyłuskać tylko błędy; zamiast poprawiać konfigurację w 200 plikach – zrobić jedną masową zamianę. Minuta nauki polecenia często oszczędza godzinę żmudnych, powtarzalnych działań.

Terminal wygrywa z GUI głównie w trzech scenariuszach:

  • logi i monitoring – szybkie przeszukiwanie plików .log z serwerów, aplikacji, usług systemowych;
  • masowe poprawki – zmiana domeny, ścieżek, parametrów w dziesiątkach plików konfiguracyjnych lub kodu;
  • szybka analityka tekstowa – tworzenie prostych raportów z plików CSV, JSON-lines czy zwykłych tabel tekstowych.

Warto zacząć od małych, powtarzalnych problemów: codzienne filtrowanie logów, wyszukiwanie TODO w projekcie, zliczanie wystąpień konkretnych komunikatów. Zamiast kupować kolejny „enterprise log analyzer”, można wykorzystać zestaw find+grep+sed+awk i zautomatyzować większość podstawowych zadań za darmo.

Podstawy pracy z plikami w terminalu – fundament pod cztery narzędzia

Ścieżki, katalogi i wildcardy – szybkie przypomnienie

Bez sprawnego poruszania się po systemie plików terminal szybko frustruje. Kluczowe pojęcia to ścieżka bezwzględna i względna. Ścieżka bezwzględna zaczyna się od / (na Linuksie i macOS), np. /var/log/nginx/access.log. Ścieżka względna jest liczona od aktualnego katalogu, np. logs/access.log lub ../stare_logi.

Podstawowe polecenia na start:

  • pwd – pokazuje aktualny katalog roboczy,
  • ls – listuje pliki i katalogi,
  • cd katalog – zmienia katalog,
  • cd .. – przechodzi „w górę” o jeden poziom.

Do wskazywania wielu plików przydają się wildcardy (znaki wieloznaczne):

  • * – dowolny ciąg znaków (także pusty), np. *.log dopasuje error.log, access.log itd.;
  • ? – dokładnie jeden dowolny znak, np. log?.txt dopasuje log1.txt, loga.txt, ale nie log10.txt;
  • [abc] – dowolny pojedynczy znak z nawiasów, np. plik[12].txt dopasuje plik1.txt i plik2.txt;
  • [0-9] – zakres znaków, np. log[0-9].txt.

Te same wildcardy będą potem używane z find, grep, a także w skryptach basha. Im szybciej wejdą w nawyk, tym sprawniej łączy się później kolejne narzędzia.

Przekierowania i potoki: >, >>, < i |

find, grep, sed i awk działają najlepiej, gdy łączy się je w potoki. Każde z nich czyta dane ze standardowego wejścia (stdin) i wypisuje wynik na standardowe wyjście (stdout). Przekierowania pozwalają przechwycić te dane lub przesłać dalej.

  • komenda > plik – zapisuje wynik do pliku, nadpisując go;
  • komenda >> plik – dopisuje wynik na końcu pliku;
  • komenda < plik – podaje plik jako wejście do komendy (rzadziej używane przy naszych narzędziach);
  • komenda1 | komenda2 – potok: wyjście pierwszej komendy staje się wejściem dla drugiej.

Prosty przykład: wyświetlenie tylko linii zawierających „ERROR” z logu serwera i zapis do osobnego pliku:

grep "ERROR" /var/log/nginx/error.log > errors_only.log

Połączenie potoków daje większą elastyczność. Wyłuskanie linii z błędami, a następnie zliczenie ile razy występuje każdy rodzaj błędu:

grep "ERROR" /var/log/nginx/error.log | awk '{print $7}' | sort | uniq -c | sort -nr

W tym przykładzie grep filtruje, awk wyciąga konkretną kolumnę, sort sortuje, uniq grupuje i zlicza, a drugi sort układa wyniki od najczęściej występujących. Cztery proste narzędzia zamieniają chaotyczny log w czytelną mini-statystykę.

Standardowe wejście, wyjście i bezpieczne testowanie

Każda z omawianych komend może czytać dane z pliku (podanego jako argument) albo z potoku. Dzięki temu łatwo podmienić źródło danych bez zmiany reszty polecenia. To kluczowe przy testowaniu. Zamiast ryzykować na produkcyjnych plikach, lepiej przetestować sed czy awk na krótkim fragmencie tekstu:

echo "stare_dane" | sed 's/stare/nowe/'

Przed użyciem ryzykownych opcji, takich jak -delete w find czy -i w sed, dobrze jest przejść przez prostą checklistę:

  • skopiuj katalog testowy: cp -r oryginal katalog_testowy;
  • przejdź do katalogu testowego i uruchom tam swoje komendy;
  • jeśli polecenie ma coś usuwać lub nadpisywać – na początek dodaj echo, np. echo rm {}; przy -exec;
  • sprawdź, czy lista plików zgadza się z oczekiwaniami, dopiero potem usuń echo.

Dobry nawyk na start: zamiast od razu wykonywać polecenia modyfikujące, najpierw zrobić „suchy bieg” (dry-run) i obejrzeć, co zostałoby zmienione. To nic nie kosztuje, a często ratuje przed skasowaniem niewłaściwych plików.

find – wyszukiwanie plików jak chirurg zamiast szukania „na oko”

Podstawowa składnia i zakres działania find

find służy do wyszukiwania plików i katalogów według różnych kryteriów: nazwy, typu, wielkości, daty, uprawnień i nie tylko. Najprostsza składnia:

find ŚCIEŻKA KRYTERIA DZIAŁANIE

Przykłady:

  • find . -name "*.log" – szukanie plików z rozszerzeniem .log w bieżącym katalogu i podkatalogach;
  • find /var/log -type f -name "error*.log" – wszystkie pliki (nie katalogi) pasujące do wzorca w /var/log;
  • find / -maxdepth 2 -name "nginx" – szukanie katalogu/plików o nazwie „nginx” maksymalnie dwa poziomy w dół.

Różnica między find . a find / jest kluczowa dla wydajności. find . przeszukuje tylko bieżący katalog i jego podkatalogi. find / rusza od katalogu głównego i może przeglądać cały system plików, co bywa bardzo wolne. W praktyce warto zawężać ścieżkę jak najbardziej, np. do katalogu projektu lub konkretnego miejsca z logami.

Filtrowanie po nazwie, wielkości i czasie modyfikacji

Najczęściej używane kryteria find to:

  • -name "wzorzec" – dopasowanie po nazwie z wildcardami shella;
  • -type f / -type d – tylko pliki lub tylko katalogi;
  • -size – filtr po wielkości pliku (np. -size +10M – większe niż 10 MB);
  • -mtime – czas modyfikacji (w dniach);
  • -mmin – czas modyfikacji (w minutach, przydatne do świeżych plików).

Kilka praktycznych przykładów:

# wszystkie pliki .log starsze niż 7 dni w /var/log
find /var/log -type f -name "*.log" -mtime +7

# pliki większe niż 100 MB w katalogu domowym
find ~ -type f -size +100M

# pliki zmodyfikowane w ostatniej godzinie
find /var/log -type f -mmin -60

-mtime +7 oznacza „ściśle więcej niż 7 dni”, -mtime -7 – „mniej niż 7 dni”. Podobnie dla -size: + – większe, - – mniejsze. To pozwala szybko znaleźć np. pliki-logi, które można zarchiwizować lub usunąć, aby zwolnić miejsce na dysku.

Praktyczne zastosowania: logi i projekty kodu

Typowy scenariusz dla find to sprzątanie starych logów. Zamiast ręcznie kasować je co tydzień, lepiej zbudować prostą komendę lub skrypt crona:

# lista logów starszych niż 14 dni
find /var/log/myapp -type f -name "*.log" -mtime +14 -print

# po sprawdzeniu listy można dodać kasowanie
find /var/log/myapp -type f -name "*.log" -mtime +14 -delete

W projektach kodu find ułatwia wyszukiwanie plików o konkretnych rozszerzeniach lub nazwach. Przykładowo, odnalezienie wszystkich plików PHP zawierających w nazwie „user”:

find . -type f -name "*user*.php"

Takie wyszukiwanie można połączyć z grepem, aby znaleźć pliki, które zawierają zarówno określony fragment nazwy, jak i treści, np. kontrolery z konkretną funkcją. To oszczędza czas, który w IDE poszedłby na wielokrotne wyszukiwania „po nazwie” i „po treści”.

Działania na znalezionych plikach: -delete, -exec i xargs

find nie tylko szuka plików – potrafi też wykonywać na nich działania. Najczęściej używane opcje:

  • -print – domyślne działanie: wypisanie ścieżek plików;
  • -delete – usunięcie znalezionych plików;
  • -exec KOMENDA {} ; – wykonanie komendy dla każdego pliku osobno;
  • -exec KOMENDA {} + – wykonanie komendy dla wielu plików naraz (bardziej wydajne);
  • | xargs KOMENDA – przekazanie listy plików dalej jako argumenty do innej komendy.

Przykład usuwania plików tymczasowych z katalogu projektu:

# najpierw tylko wypisanie
find . -type f -name "*.tmp" -print

# po weryfikacji listy – kasowanie
find . -type f -name "*.tmp" -delete

Przykład uruchomienia komendy na każdym pliku – zmiana uprawnień wszystkich skryptów .sh na wykonywalne:

find . -type f -name "*.sh" -exec chmod +x {} ;

{} jest zastępowane przez aktualnie znaleziony plik, a ; kończy polecenie. Dla komend, które radzą sobie z wieloma plikami naraz (np. rm, tar), lepsze bywa + zamiast ;:

find . -type f -name "*.bak" -exec rm {} +

Bezpieczne uruchamianie -delete i -exec

Największa pułapka find to pochopne użycie -delete lub -exec rm. Kilka prostych zasad zmniejsza ryzyko pomyłki:

  • zawsze zaczynaj od -print bez akcji niszczących;
  • ograniczaj zakres ścieżki – jeśli sprzątasz logi aplikacji, użyj /var/log/myapp, nie /var/log ani /;
  • jeśli używasz -exec – najpierw zastąp komendę przez echo, np.:
    find . -type f -name "*.bak" -exec echo rm {} ;
  • sprawdź, czy w wynikach nie ma ścieżek, których nie chcesz dotykać (systemowe, produkcyjne dane).
Sprawdź też ten artykuł:  OCR po polsku: jak wyciągnąć tekst ze skanów i PDF w darmowych programach

Dobrze działa też prosty schemat pracy: najpierw find ... -print, potem to samo polecenie przeklejone ze zmienioną akcją. Zamiast kombinować od zera z groźnym -delete, bierzesz sprawdzoną komendę, dopinasz czyszczenie i redukujesz szansę na literówkę. To sposób mało efektowny „marketingowo”, ale skuteczny i tani czasowo.

Jeśli w zespole czy na serwerach produkcyjnych chcesz jeszcze bardziej się zabezpieczyć, sensowne są dwa proste triki. Po pierwsze: aliasy w stylu alias rm='rm -i' lub osobny alias na czyszczące findy, które zawsze pytają o potwierdzenie. Po drugie: trzymanie reguł sprzątania w skryptach wersjonowanych w Git zamiast w historii shella – w razie czego łatwo prześledzić, co faktycznie zostało uruchomione i kto co zmienił.

Przy szerokim sprzątaniu (np. w katalogach użytkowników) przydaje się jeszcze filtracja po ścieżce: -path i -prune. Pozwala to ominąć katalogi typu .git czy node_modules bez pisania skomplikowanych skryptów. Jeden dobrze przemyślany find potrafi zaoszczędzić godziny ręcznego klikania lub czekania, aż IDE „przemieli” cały projekt.

Im więcej pracy przeniesiesz z myszki do find + grep + sed + awk, tym mniej czasu ucieknie na powtarzalne, ręczne czynności. Na start wystarczy kilka prostych, zapamiętanych wzorców i odrobina ostrożności. Reszta przychodzi sama przy codziennym użyciu – logi porządkują się szybciej, drobne poprawki w plikach nie wymagają otwierania edytora, a poważniejsze zadania da się rozbić na krótkie, tanie w utrzymaniu komendy, które robią dokładnie to, czego potrzebujesz.

grep – szybkie wyłuskiwanie informacji z potoku tekstu

Najprostsze użycie grep w logach i kodzie

grep przeszukuje tekst po liniach i wypisuje tylko te, które pasują do wzorca. Minimalny schemat:

grep WZORZEC PLIK

Przykłady z codziennej pracy:

# znajdź linie z błędami w logu
grep "ERROR" app.log

# wyszukaj wszystkie użycia funkcji getUser w projekcie
grep "getUser" -n src/*.js

# przefiltruj wynik innego polecenia
ps aux | grep nginx

Przełącznik -n dodaje numer linii – przy debugowaniu lub refaktoryzacji oszczędza kilka sekund na każdej poprawce.

Wygodne przełączniki: -i, -v, -r, -H, –color

Kilka flag czyni z grep narzędzie „do wszystkiego” za małą cenę mentalną:

  • -i – ignoruj wielkość liter (ERROR, error, Error traktowane tak samo);
  • -v – wyświetl linie niepasujące do wzorca (negacja);
  • -r lub -R – rekursywne szukanie w katalogach;
  • -H – zawsze pokazuj nazwę pliku (przy kilku plikach bywa domyślne, ale wymuszenie nic nie kosztuje);
  • --color=auto – podświetl dopasowania (często już ustawione w aliasie).

Praktyczne pakiety flag, które można zapamiętać raz i używać codziennie:

# rekursywnie w projekcie, bez rozróżniania wielkości liter
grep -rin --color=auto "timeout" .

# pokaż tylko linie bez komentarzy (np. w plikach konfiguracyjnych)
grep -v "^#" /etc/myapp.conf

# wyszukaj wszystkie użycia słowa "password" z nazwą pliku i numerem linii
grep -RinH "password" .

Łączenie find i grep w tanie „IDE w terminalu”

grep sam w sobie jest mocny, ale z find tworzy szkielet wyszukiwania, którego brakuje w prostych edytorach. Schemat jest powtarzalny:

find ŚCIEŻKA KRYTERIA -print0 | xargs -0 grep OPCJE "wzorzec"

Przykłady z realnych porządków w projekcie:

# szukaj "TODO" tylko w plikach .py
find . -type f -name "*.py" -print0 | xargs -0 grep -n "TODO"

# znajdź miejsca, gdzie w plikach .env użyto słowa DEBUG
find . -type f -name ".env*" -print0 | xargs -0 grep -n "DEBUG"

-print0 w find i -0 w xargs zabezpieczają ścieżki z odstępami, co eliminuje całą klasę dziwnych błędów. To minimalna inwestycja w bezpieczeństwo.

Filtracja potoków: grep jako „sito w środku”

grep rzadko musi działać na plikach – równie często stoi w środku potoku:

# pokaż tylko procesy nginx
ps aux | grep nginx | grep -v "grep"

# liczba zapytań 500 w logach nginx z ostatniej godziny
journalctl -u nginx --since "-60 min" | grep " 500 " | wc -l

# monitoruj log w locie, ale tylko błędy
tail -f app.log | grep --color=auto "ERROR"

Sztuczka z grep -v "grep" usuwa linię z samym poleceniem. Da się to zastąpić sprytniejszymi wyrażeniami (np. grep "[n]ginx"), ale klasyczna wersja jest wystarczająco dobra i czytelna.

Wyszukiwanie całych słów i prostych wzorców

Częsty problem: zbyt „luźne” dopasowanie – słowo cat trafia także w concatenate. Dwa przełączniki rozwiązują to z niskim kosztem nauki:

  • -w – dopasowanie całych słów;
  • -x – dopasowanie całej linii.
# znajdź linie, gdzie "status" jest osobnym słowem
grep -Rw "status" .

# znajdź linie, które są dokładnie "enabled=true"
grep -x "enabled=true" config.ini

Podstawowy regex w grep bez teorii

Do większości zadań wystarczy kilka wzorców. Bez wchodzenia w pełną składnię, te fragmenty przydają się najczęściej:

  • . – dowolny pojedynczy znak;
  • * – „zero lub więcej” poprzedniego znaku;
  • [abc] – jeden z podanych znaków;
  • [0-9] – dowolna cyfra (zakres);
  • ^ – początek linii, $ – koniec linii.

Warto od razu używać „rozszerzonego” grepa (egrep lub grep -E), bo obsługuje wygodniejszą składnię bez uciążliwego uciekania nawiasów:

# linie zaczynające się od ERROR lub WARN
grep -E "^(ERROR|WARN)" app.log

# proste dopasowanie adresu IP (byle jakie cztery liczby 0-999)
grep -E "[0-9]+.[0-9]+.[0-9]+.[0-9]+" access.log

# linie, które NA PEWNO nie są puste (coś poza białymi znakami)
grep -E "^[[:space:]]*[^[:space:]]" plik.txt

+ oznacza „co najmniej raz”, a | działa jak „albo”. To dwa operatory, które dają największy zwrot z nauki w codziennym użyciu.

grep jako tani zamiennik „ag” czy „ripgrep”

Narzędzia typu rg (ripgrep) czy ag są szybsze w dużych repozytoriach, ale grep jest dostępny wszędzie. Można go trochę „podciągnąć” aliasem:

alias g='grep -RinH --color=auto'

Po takim aliasie cały projekt przeszukujesz jedną krótką komendą:

g "api/v1/users"

Brzmi banalnie, ale przy dziesiątkach wyszukiwań dziennie różnica między długą komendą a skrótem to konkretne minuty oszczędności.

Wstęp do regex w praktyce – mniej teorii, więcej prostych wzorców

Myślenie „po wzorcach” zamiast po dosłownych ciągach

Wyrażenia regularne przydają się wtedy, gdy nie szukasz konkretnego napisu, ale pewnej struktury: numeru, e‑maila, fragmentu ścieżki. Zamiast budować skomplikowaną teorię, szybciej jest zapamiętać kilka szablonów i korygować je ad hoc.

Kluczowe elementy składowe:

  • kotwice^ (początek), $ (koniec);
  • klasy znakówd / [0-9], s / [[:space:]], w / [A-Za-z0-9_] (w zależności od narzędzia);
  • ilości?, *, +, {min,max};
  • grupy(...) i alternatywy z |.

Proste szablony do logów, dat i ID

Zamiast szukać „po słowie”, można od razu filtrować po strukturze. Kilka gotowców, które często się przydają:

# data w formacie 2023-12-31
[0-9]{4}-[0-9]{2}-[0-9]{2}

# godzina HH:MM:SS (bez weryfikacji zakresu)
[0-9]{2}:[0-9]{2}:[0-9]{2}

# UUID (prosty wariant)
[0-9a-fA-F-]{36}

# liczba całkowita (z opcjonalnym minusem)
-?[0-9]+

Zastosowanie w grep:

# wytnij tylko linie z datą z roku 2024
grep -E "^2024-[0-9]{2}-[0-9]{2}" app.log

# znajdź linie z UUID i błędem
grep -E "ERROR.*[0-9a-fA-F-]{36}" app.log

Testowanie regex „na sucho” w terminalu

Zamiast strzelać regexem w pełne logi, wygodniej jest potestować go najpierw na wycinku:

# weź 20 linii z loga i sprawdź wzorzec
head -n 20 app.log | grep -E "ERROR.*[0-9]{3}"

# sprawdź wzorzec na ręcznie wpisanych liniach
printf "user1@example.comnzlyformatnuser.2@domain.pln" | 
  grep -E "^[^@]+@[^@]+.[a-z]+$"

Takie mini-próbki oszczędzają nerwy, bo nie trzeba przewijać setek linii po każdej poprawce wzorca.

Najczęstsze pułapki: „zachłanność” i uciekanie znaków

Dwa zjawiska powodują najwięcej zdziwień:

  • zachłanne dopasowanie.* weźmie „jak najwięcej”, więc foo.*bar złapie także foo ... bar ... bar aż do ostatniego bar w linii;
  • specjalne znaki., ?, *, +, (), [], {}, | często trzeba „uciekać” backslashem.

Kilka przykładów bezpieczniejszych wzorców:

# dopasuj tekst pomiędzy dwoma nawiasami kwadratowymi
[.*]

# dosłowna kropka, nie „dowolny znak”
foo.bar

# dopasuj minimalny fragment między <tag> a </tag> (w sed/awk często inaczej)
<tag>[^<]*</tag>

Regex a koszt utrzymania skryptów

Reguła praktyczna: im bardziej „magiczny” regex, tym droższy w utrzymaniu. W wielu przypadkach sensowniej jest rozbić zadanie na dwa prostsze kroki, niż budować jedną konstrukcję, której za miesiąc nikt nie rozszyfruje.

# wariant „magiczny” – pojedynczy regex
grep -E "^[0-9]{4}-[0-9]{2}-[0-9]{2} .* (ERROR|WARN) .* (db|cache)" app.log

# wariant prosty – kilka filtrów
grep -E "^[0-9]{4}-[0-9]{2}-[0-9]{2}" app.log | 
  grep -E "ERROR|WARN" | 
  grep -E "db|cache"

Druga wersja jest trochę wolniejsza, ale tańsza w debugowaniu i zmianach. Przy logach tekstowych różnica w wydajności zazwyczaj jest pomijalna w porównaniu z czasem spędzonym na rozkminianiu jednego „potwora”.

sed – szybkie, niewizualne „Znajdź i zamień” plus kilka sztuczek

Podstawowy wzorzec zamiany w sed

sed działa w trybie „przepuść i przerób”: czyta wejście, stosuje reguły i wypisuje wynik. Najbardziej użyteczna operacja na start to prosta zamiana:

sed 's/STARE/NOWE/' plik.txt

Domyślnie sed zamienia tylko pierwsze wystąpienie w linii. Żeby podmienić wszystkie, dochodzi flaga g (global):

# zamień wszystkie "foo" na "bar" i pokaż wynik
sed 's/foo/bar/g' plik.txt

Standardowo sed wypisuje wynik na stdout, więc pliku nie tknie, dopóki sam go nie nadpiszesz przekierowaniem lub przełącznikiem -i.

Bezpieczne użycie sed -i (i tańsza alternatywa)

sed -i modyfikuje plik „w miejscu”. To wygodne, ale ryzykowne bez kopii. Dostępne są dwie ścieżki:

# wariant bezpośredni z kopią bezpieczeństwa
sed -i.bak 's/foo/bar/g' plik.txt

# wariant ostrożny – zapis do nowego pliku
sed 's/foo/bar/g' plik.txt > plik.txt.new

Opcja z .bak kosztuje parę kilobajtów na backup, ale ratuje przy pomyłkach. Drugi wariant bywa wygodniejszy, jeśli i tak chcesz potem porównać wynik z oryginałem.

Wybiórcza zamiana tylko w pasujących liniach

sed pozwala połączyć filtr i zamianę w jednym przebiegu. Dzięki temu nie trzeba kleić kilku narzędzi:

# zamień "DEBUG" na "INFO" tylko w liniach z "auth"
sed '/auth/s/DEBUG/INFO/g' app.log

# usuń komentarze tylko w liniach, które zaczynają się od spacji
sed '/^[[:space:]]/s/#.*$//' config.ini

Struktura /wzorzec/akcja oznacza „zastosuj akcję tylko do linii, które pasują do wzorca”. Akcją może być zamiana, usunięcie albo coś bardziej złożonego.

Usuwanie linii i komentarzy bez otwierania edytora

Do prostego „sprzątania” plików sed wystarcza z minimalnym zestawem poleceń:

# usuń linie z komentarzami (zaczynającymi się od #)
sed '/^[[:space:]]*#/d' config.ini

# usuń puste linie lub zawierające tylko białe znaki
sed '/^[[:space:]]*$/d' raport.txt

# wywal linie z DEBUG, zostawiając tylko ważniejsze logi
sed '/DEBUG/d' app.log > app_clean.log

Takie jedno- lub dwulinijkowe komendy często zastępują ręczne grzebanie w edytorze. Jeśli plik ma kilkanaście tysięcy linii, różnica w czasie jest odczuwalna, a przy okazji da się te same polecenia łatwo powtórzyć na kolejnych środowiskach.

Podmiana z użyciem regex i grupowania

sed dobrze łączy się z prostymi regexami. Dzięki nawiasom i odwołaniom do grup (1, 2…) da się nie tylko coś wyciąć, ale też poprzekładać elementy w linii:

# zamiana formatu daty z YYYY-MM-DD na DD.MM.YYYY
sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})/3.2.1/' log.csv

# obcięcie milisekund w znacznikach czasu
sed -E 's/([0-9]{2}:[0-9]{2}:[0-9]{2}).[0-9]+/1/' app.log

# dodanie prefiksu do ID użytkownika wyłapanego regexem
sed -E 's/user_id=([0-9]+)/user_id=U-1/' events.log

Taki poziom skomplikowania zwykle wystarcza w codziennej pracy: drobne zmiany formatu, reformatowanie logów, szybkie przygotowanie danych pod import. Zamiast pisać skrypt w Pythonie na kilkanaście linii, da się to załatwić jedną komendą, którą łatwo wkleić komuś na czacie.

Zakresy linii i proste wstawki

Jeśli trzeba zmodyfikować tylko konkretny blok, przydają się zakresy. sed pozwala zadziałać od linii X do Y albo między dwiema frazami:

# usuń linie 10–20
sed '10,20d' plik.txt

# zakomentuj blok między BEGIN a END
sed '/BEGIN/,/END/s/^/# /' config.ini

# dodaj linię po nagłówku [database]
sed '/^[database]/a host=localhost' config.ini

To już wchodzi w terytorium prostych „migracji” konfiguracji. Zamiast edytować kilkanaście serwerów ręcznie, można taki fragment wrzucić do skryptu i mieć powtarzalny proces bez dodatkowych narzędzi konfiguracyjnych.

find, grep, sed i odrobina regexów dają razem zaskakująco duży zakres możliwości za praktycznie zerowy koszt: są wszędzie, działają szybko i dobrze się skryptują. Im częściej wchodzą w nawyk w prostych zadaniach – od szukania błędów w logach po masowe poprawki w konfiguracji – tym mniej czasu schodzi na klikanie i „ręczne poprawki”, a więcej zostaje na faktyczne rozwiązywanie problemów.

awk – mały język do obróbki tekstu „w locie”

Model działania: linia po linii, kolumna po kolumnie

awk łączy w sobie filtr, prosty język i kalkulator. Patrzy na wejście linia po linii, rozbija każdą linię na pola (domyślnie po białych znakach) i pozwala wykonywać akcje na podstawie warunków.

awk 'warunek { akcja }' plik.txt

Podstawowe zmienne, które przewijają się niemal wszędzie:

  • $0 – cała linia;
  • $1, $2, … – kolejne pola (kolumny);
  • NF – liczba pól w bieżącej linii;
  • NR – numer bieżącej linii (rekordu);
  • FS – separator pól wejściowych (Field Separator);
  • OFS – separator między polami w wyjściu.

W praktyce często wystarcza kilkanaście znaków:

# wypisz 1. i 3. kolumnę z raportu
awk '{ print $1, $3 }' raport.txt

# ustaw inny separator wejściowy – np. CSV z przecinkami
awk -F',' '{ print $1, $3 }' raport.csv

# zmień separator na wyjściu na średnik
awk 'BEGIN { OFS=";" } { print $1, $3 }' raport.csv

Szybkie wyliczenia bez Excela i bez Pythona

Jeśli w logu lub raporcie jest liczba w konkretnej kolumnie, awk pozwala policzyć sumy, średnie czy proste statystyki bez pisania skryptu.

# sumowanie wartości z 3. kolumny
awk '{ suma += $3 } END { print "Suma:", suma }' dane.txt

# średni czas odpowiedzi z 2. kolumny
awk '{ suma += $2; liczba++ } END { print "Średnia:", suma/liczba }' times.log

# policz, ile jest linii z błędem 500
awk '$9 == 500 { liczba++ } END { print "500-ek:", liczba }' access.log

Efekt: w kilkanaście sekund da się mieć liczbę, którą ktoś inny wyklikałby w arkuszu kalkulacyjnym albo dopiero prosił o eksport do Excela.

Filtrowanie po warunkach na „kolumnach”

grep szuka „po tekście”. awk może podejmować decyzję po konkretnym polu – np. status HTTP albo nazwę usługi.

# pokaż tylko wiersze, w których 3. kolumna > 100
awk '$3 > 100' raport.txt

# linie z 4. kolumną równą "ERROR"
awk '$4 == "ERROR"' app.log

# linie, w których 2. pole zawiera "auth"
awk '$2 ~ /auth/' app.log

Operator ~ oznacza „pasuje do regexu”, a !~ – „nie pasuje”. To daje prosty sposób na warunkowe cięcia:

# odfiltruj wszystko, co nie jest 2xx ani 3xx
awk '$9 !~ /^2|^3/' access.log

# zostaw tylko linie z user_id zaczynającym się na U-
awk '$0 ~ /user_id=U-/' events.log

Formatowanie raportów i zmiana układu pól

awk dobrze nadaje się do „przepakowania” wierszy – zmiany kolejności pól, wstawienia stałych nagłówków czy prostego formatu tabeli.

# zmiana kolejności: data, status, endpoint
awk '{ print $1, $9, $7 }' access.log

# prosty raport: tylko user i kwota, z opisem
awk '{ print "User:", $1, "Kwota:", $3 }' transakcje.txt

# CSV -> TSV (przecinek na tabulator)
awk -F',' 'BEGIN { OFS="t" } { print $1, $2, $3 }' dane.csv

Na małych zestawach danych to może wydawać się drobną oszczędnością, ale przy cyklicznych raportach nawet kilka minut mniej klikania dziennie robi różnicę.

Sprawdź też ten artykuł:  Najlepsze aplikacje do produktywności na Androida i iOS

Grupowanie i sumowanie „po kluczu”

Prosty odpowiednik „GROUP BY” z SQL da się ogarnąć w jednej linijce awk, jeśli liczba danych nie jest kosmiczna. Tablice asocjacyjne robią tu większość roboty.

# sumuj sprzedaż po użytkowniku (user w 1. kolumnie, kwota w 3.)
awk '{ suma[$1] += $3 } END { for (u in suma) print u, suma[u] }' sales.txt

# policz liczbę requestów na endpoint (ścieżka w 7. kolumnie)
awk '{ licz[$7]++ } END { for (url in licz) print licz[url], url }' access.log

Porządkowanie wyniku po liczbach można dorzucić tanio przez sort:

awk '{ licz[$7]++ } END { for (url in licz) print licz[url], url }' access.log 
  | sort -nr

Zestaw „awk + sort” rozwiązuje większość prostych zadań analitycznych na logach bez stawiania dodatkowych narzędzi.

Operacje na polach z prostą logiką warunkową

Kiedy zwykłe filtrowanie to za mało, można dorzucić if i proste obliczenia w środku.

# dodaj etykietę na podstawie wartości
awk '{
  status = ($3 > 1000) ? "HIGH" : "OK";
  print $1, $3, status
}' metrics.txt

# wylicz procent błędów 5xx w logu
awk '{
  total++;
  if ($9 ~ /^5/) errors++;
}
END {
  if (total > 0)
    printf "Błędy 5xx: %.2f%%n", (errors / total) * 100;
}' access.log

Tego typu mini-szkielety można przechowywać w pliku z kilkoma awk-owymi „szablonami” i kopiować w zależności od potrzeby zamiast pisać je od nowa.

awk jako „inteligentny” filtr między find, grep i sed

awk dobrze domyka łańcuch, który zaczyna się na find, przechodzi przez grep i ewentualnie sed. Kilka prostych układów na co dzień:

# zlicz łączną liczbę linii z ERROR we wszystkich logach z katalogu
find logs -name '*.log' -print0 | xargs -0 grep -h 'ERROR' | 
  awk 'END { print "Liczba linii z ERROR:", NR }'

# policz średni czas requestów na endpoint na podstawie przefiltrowanych logów
grep 'GET /api/' access.log | 
  awk '{ czasy[$7] += $NF; licz[$7]++ }
       END {
         for (url in czasy)
           printf "%s %.2fn", url, czasy[url]/licz[url];
       }'

Takie potoki często zastępują jednorazowe skrypty, które trafiłyby do szuflady i nigdy więcej by nie zostały odpalone.

Łączenie find, grep, sed i awk w praktyczne potoki

Przegląd kodu: szybki audyt bez otwierania IDE

Drobne porządki w projekcie da się ogarnąć bez pełnego środowiska, zwłaszcza kiedy chodzi o coś powtarzalnego w dziesiątkach plików.

# znajdź wszystkie miejsca użycia przestarzałej funkcji w kodzie
find src -name '*.py' -print0 | xargs -0 grep -n 'old_function'

# podmień nazwę funkcji na nową w konkretnym module
grep -rl 'old_function' src/module/ | xargs sed -i.bak 's/old_function/new_function/g'

# prosta statystyka: ile linii kodu w plikach .sh
find . -name '*.sh' -print0 | xargs -0 awk 'END { print "Liczba linii:", NR }'

W małych repo nie ma sensu wstawiać dodatkowych narzędzi do statystyk – powyższe przykłady działają wszędzie, także na serwerach bez rozbudowanych dodatków.

Sprzątanie logów: od wycięcia śmieci do małego raportu

Typowy scenariusz: ogromny log, trzeba wyłuskać wzorzec, przefiltrować, może lekko przekształcić i policzyć. Dużo da się zrobić sekwencyjnie.

# wyciągnij same linie z błędami 5xx, bez assetów typu .css/.js
grep 'HTTP/' access.log | grep ' 5[0-9][0-9] ' | 
  grep -vE '.(css|js|png|jpg)' > errors.log

# z errors.log policz najgorsze endpointy
awk '{ licz[$7]++ } END { for (url in licz) print licz[url], url }' errors.log | 
  sort -nr | head

Jeżeli log ma inny format, zamiast zmieniać logowanie w aplikacji, często szybciej jest skleić jedną-dwie takie komendy i powtarzać je przy każdej analizie.

Masowe poprawki w konfiguracji – podejście stopniowane

Przy konfiguracjach lepiej unikać jednego „magicznego” polecenia, które ma poprawić wszystko za jednym strzałem. Tańsze jest podzielenie pracy na kilka tanich kroków z prostymi komendami.

# 1. znajdź wszystkie pliki config.* w drzewie
find /etc/myapp -name 'config.*' > cfg_list.txt

# 2. zobacz, gdzie występuje stary host bazy
xargs -a cfg_list.txt grep -n 'db-old.local'

# 3. po weryfikacji – podmień host na nowy z backupem
xargs -a cfg_list.txt sed -i.bak 's/db-old.local/db-new.local/g'

Jeśli trzeba coś dodatkowo policzyć (np. ile plików faktycznie ruszono), można dołożyć awk:

# pokaż tylko te backupy, które rzeczywiście się zmieniły (niektóre narzędzia tworzą plik .bak zawsze)
find /etc/myapp -name 'config.*.bak' -print | 
  awk '{ licz++ } END { print "Zmienione pliki:", licz }'

Ekspresowe raporty z CSV bez importu do bazy

CSV z eksportu systemu często ląduje w katalogu „na chwilę”. Zamiast każdorazowo budować import do bazy, sensowniej jest użyć prostego potoku.

# liczenie zamówień per status (status w 4. kolumnie)
awk -F',' 'NR > 1 { status[$4]++ }
END { for (s in status) print status[s], s }' orders.csv | sort -nr

# łączna kwota zamówień per klient (ID klienta w 2., kwota w 5.)
awk -F',' 'NR > 1 { suma[$2] += $5 }
END { for (k in suma) print k, suma[k] }' orders.csv | sort -k2nr | head

Takie jedno-linijkowe raporty spokojnie wystarczą, kiedy pytanie jest jednorazowe, a nie ciągle powtarzane. Koszt wdrożenia jest praktycznie zerowy.

Prosta „ETL-ka”: wytnij, przemapuj, załaduj

Małe migracje danych albo przygotowanie wsadu do innego systemu można ogarnąć kombinacją sed + awk zamiast wyciągać cięższe działa.

# 1. usunięcie komentarzy i pustych linii konfiga
sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' settings.conf > settings.clean

# 2. przemapowanie formatu KEY=VALUE na KEY;VALUE
awk -F'=' 'NF == 2 { print $1 ";" $2 }' settings.clean > settings.csv

# 3. prosta konwersja liter (np. na wielkie litery kluczy)
awk -F';' '{
  key = toupper($1);
  print key ";" $2;
}' settings.csv > settings_final.csv

Całość da się rozpisać w krótkim skrypcie shellowym i odtwarzać przy każdej zmianie źródłowego pliku zamiast poprawiać ręcznie.

Bezpieczne testowanie potoków na małym wycinku

Najtańsze zabezpieczenie przed psuciem danych to nawyk testowania długich komend na małym fragmencie wejścia. Kilka prostych schematów:

# test potoku na 50 pierwszych liniach loga
head -n 50 access.log | sed 's/secret/****/g' | awk '{ print $1, $7 }'

# test na jednym pliku zamiast na wszystkich
find logs -name '*.log' | head -n 1 | 
  xargs grep 'ERROR' | 
  awk '{ print $1, $2, $NF }'

Dopiero kiedy wynik wygląda sensownie, warto dorzucić | wc -l lub przekierowanie do pliku tymczasowego i odpalić całość na pełnym zbiorze.

Typowe pułapki i bezpieczne nawyki przy pracy z find/grep/sed/awk

Flagi, które ratują dzień (i dane)

Najwięcej szkód robią nie same narzędzia, tylko uruchamianie ich „na pałę” bez zabezpieczeń. Kilka drobnych przełączników mocno obniża ryzyko.

# sed: zawsze rób backup przy edycji w miejscu
sed -i.bak 's/foo/bar/g' plik.txt

# grep: pokaż kontekst, zanim skasujesz coś na ślepo
grep -nC2 'wzorzec' duzy_plik.conf

# find: pokaż, co zostanie usunięte, zanim dodasz rm
find logs -name '*.old' -type f -print
# dopiero gdy lista wygląda sensownie:
find logs -name '*.old' -type f -delete

Jednorazowe dopisanie .bak czy -print zajmuje sekundę, a często oszczędza godziny odtwarzania danych.

Unikanie zjadania spacji i „niewidzialnych” znaków

Najtańsza diagnostyka przy podejrzanych wynikach to ujawnienie białych znaków.

# pokaż tabulatory i końce linii (np. przy CSV/TSV)
sed -n '1,5p' dane.tsv | sed -e 's/t/[TAB]/g' -e 's/$/[EOL]/'

# sprawdzenie, czy są spacje na końcu linii
grep -n '[[:space:]]+$' plik.txt

# szybkie wyczyszczenie końcowych spacji
sed -i.bak 's/[[:space:]]+$//' plik.txt

Jeśli awk nagle „gubi” kolumny, pierwszy krok to sprawdzenie, czy separator jest na pewno taki, jak nam się wydaje.

Testowanie sed/awk na echo zamiast na plikach

Zamiast od razu odpalania z -i na całym katalogu, lepiej najpierw przepuścić kilka ręcznie wpisanych linii przez potok.

# prototyp zamiany w sed
echo 'user_id=123 old=true' | sed 's/old=true/old=false/'

# test wzorca awk z customowym separatorem
echo 'jan,10,ok' | awk -F',' '{ print $1, $3 }'

Jeśli wyrażenie zadziała na małym przykładzie, szansa na psucie większego zbioru jest dużo mniejsza.

Na co uważać przy pracy na dużych zbiorach danych

find i grep kontra milion plików

Przy ogromnych drzewach katalogów widać różnicę między naiwnym a rozsądnym podejściem. Kilka drobnych zmian potrafi skrócić czas z minut do sekund.

# wolniej: grep samodzielnie chodzi po katalogach
grep -R 'haslo' /var/log

# szybciej: find zawęża zakres, grep robi tylko swoje
find /var/log -name '*.log' -type f -print0 | xargs -0 grep -n 'haslo'

Jeżeli katalog ma mieszankę binariów i tekstu, lepiej je odsiać wcześniej:

# wyłącz pliki binarne po rozszerzeniu
find . -type f ! -name '*.png' ! -name '*.jpg' -print0 | 
  xargs -0 grep -n 'TODO'

Streaming zamiast plików tymczasowych

Na większych danych mniej plików pośrednich to mniej I/O i mniej sprzątania. Lepiej sklejać potok niż tworzyć pięć osobnych „.tmp”.

# wersja z plikami pośrednimi (cięższa)
grep 'ERROR' access.log > step1.log
grep 'api/' step1.log > step2.log
awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }' step2.log > raport.txt

# wersja strumieniowa (tańsza)
grep 'ERROR' access.log | grep 'api/' | 
  awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }' > raport.txt

Plik pośredni ma sens głównie wtedy, gdy ktoś inny też ma go obejrzeć lub gdy planujesz wracać do niego kilkakrotnie.

Ograniczanie zakresu awk/sed przy ogromnych plikach

Kiedy plik ma dziesiątki gigabajtów, nawet jedno przejście robi się drogie. Prostym sposobem jest zawężenie działania.

# sed: zamiana tylko w liniach z konkretnym wzorcem
sed -i.bak '/^User:/ s/old_id/new_id/' users.txt

# awk: odcinanie tylko fragmentu loga po dacie
awk '$1 >= "2024-05-01" && $1 <= "2024-05-02"' access.log | 
  awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }'

Zamiast obrabiać cały historyczny log, taniej jest wcześniej wyciąć interesujący przedział dat sed -n lub dodatkowym grep.

Kobieta pracuje nocą przy laptopie z kodem w terminalu
Źródło: Pexels | Autor: cottonbro studio

Sprytne użycie regex z find, grep, sed i awk

find: wzorce nazwy kontra -regex

Większość zadań wystarczy załatwić prostymi maskami, ale czasem trafia się bardziej skomplikowana nazwa pliku.

# prosta maska: wszystkie backupy .bak i .old
find . -type f ( -name '*.bak' -o -name '*.old' )

# regex: logi z datą w nazwie (np. app-2024-06-01.log)
find . -regextype posix-extended 
  -regex '.*/app-[0-9]{4}-[0-9]{2}-[0-9]{2}.log'

Jeśli można rozwiązać problem kombinacją kilku -name, to zwykle będzie czytelniejsze niż jeden złożony -regex.

grep: kilka prostych wzorców, które robią robotę

W większości przypadków wystarczą naprawdę proste regexy, bez wchodzenia w akademicką teorię.

# adres IP w logu (4 liczby rozdzielone kropką)
grep -E '[0-9]+.[0-9]+.[0-9]+.[0-9]+' access.log

# HTTP status 4xx lub 5xx
grep -E ' 4[0-9]{2} | 5[0-9]{2} ' access.log

# numer biletu w formacie ABC-1234
grep -E '[A-Z]{3}-[0-9]{4}' tickets.txt

Jeśli regex zaczyna się rozrastać na kilka ekranów – to sygnał, że taniej będzie rozbić zadanie na dwa prostsze kroki z dodatkowymi filtrami.

sed: adresowanie fragmentów pliku prostymi wzorcami

Pełne regexy w sed potrafią być nieczytelne, ale kilka praktycznych schematów załatwia większość codziennych przypadków.

# usuń linie między BEGIN a END (włącznie)
sed '/^BEGIN/,/^END/d' plik.txt

# podmień tylko pierwszy wystąp w linii
sed 's/error/ERROR/' log.txt

# podmień od 10. do 20. linii
sed '10,20 s/old/new/' plik.txt

Adresowanie zakresem (numery linii lub wzorce początek/koniec) jest prostsze do ogarnięcia niż sklejanie skomplikowanych wyrażeń „w ciemno”.

awk: regex w warunkach zamiast w całej logice

Regex w awk najwygodniej stosować w prostym porównaniu zamiast budować skomplikowane potwory w jednym poleceniu.

# przetwarzaj tylko wiersze z mailem w 2. kolumnie
awk '$2 ~ /@/' users.txt

# osobna logika dla linii z ERROR i WARN
awk '
  /ERROR/ { err++ }
  /WARN/  { warn++ }
  END {
    print "ERROR:", err;
    print "WARN:", warn;
  }
' app.log

Warunek $kolumna ~ /regex/ jest wystarczający w większości raportów i filtrów „ad hoc”.

Minimalne skrypty shellowe z find/grep/sed/awk

Pudełko z narzędziami: małe funkcje zamiast kopiuj-wklej

Zamiast co tydzień sklejać od nowa podobny potok, taniej jest wrzucić kilka mikro-funkcji do jednego pliku tools.sh.

# tools.sh
log_errors() {
  grep 'ERROR' "$1" | grep -v 'Ignored'
}

top_endpoints() {
  awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }' "$1" | 
    sort -nr | head
}

csv_sum_column() {
  # $1 - plik, $2 - numer kolumny
  awk -F',' -v col="$2" 'NR > 1 { sum += $col } END { print sum }' "$1"
}
# użycie
. ./tools.sh
log_errors app.log | head
top_endpoints access.log
csv_sum_column sales.csv 5

Jednorazowy koszt stworzenia takiego pliku szybko się zwraca, bo każda kolejna analiza to dosłownie kilka znaków w terminalu.

Skrypt do recyklingu typowych zadań porządkowych

Przy serwerach, na których często sprzątasz logi czy backupy, sens ma jeden prosty skrypt-dozorca.

#!/bin/sh

LOG_DIR=/var/log/myapp
BACKUP_DIR=/var/backups/myapp

clean_old_logs() {
  find "$LOG_DIR" -name '*.log' -mtime +14 -type f -print -delete
}

clean_old_backups() {
  find "$BACKUP_DIR" -name '*.tar.gz' -mtime +30 -type f -print -delete
}

report_errors_last_day() {
  awk '$1 >= "2024-06-27"' "$LOG_DIR"/access.log | grep 'ERROR' | 
    awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }' | sort -nr
}

case "$1" in
  clean-logs) clean_old_logs ;;
  clean-backups) clean_old_backups ;;
  report-errors) report_errors_last_day ;;
  *) echo "Użycie: $0 {clean-logs|clean-backups|report-errors}" ;;
esac

Zamiast pamiętać długie komendy, zostaje krótka etykietka – wywołanie typu ./maintenance.sh clean-logs.

Profilowanie i optymalizowanie własnych potoków

Gdzie naprawdę znika czas

Przy dłuższych potokach łatwo obstawiać „winnych” na czuja. Zamiast zgadywać, lepiej zmierzyć.

# najprostszy pomiar czasu całego potoku
time grep 'ERROR' access.log | awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }'

# porównanie dwóch wariantów grep
time grep -R 'wzorzec' src/
time find src/ -name '*.py' -print0 | xargs -0 grep 'wzorzec'

Jeżeli różnica to ułamki sekund przy rzadkim zadaniu, nie ma sensu tracić pół dnia na optymalizację. Reagować warto dopiero wtedy, gdy komenda zaczyna przeszkadzać w codziennej pracy.

Sprawdź też ten artykuł:  Integracja AI z aplikacjami desktopowymi – krok po kroku

Zmiana kolejności kroków dla lepszej wydajności

Na dużych zbiorach taniej jest najpierw brutalnie zawęzić dane, a dopiero później wyciągać szczegóły.

# mniej korzystne: najpierw ciężki awk, potem filtr
awk '$9 ~ /^5/' access.log | grep 'api/'

# korzystniejsze: najpierw tani grep, potem awk
grep 'api/' access.log | awk '$9 ~ /^5/'

Najlżejsze operacje (proste grep, odcinanie kolumn) warto przesuwać na początek potoku, tak żeby „droższe” kroki widziały już tylko ułamek danych.

Reuse wyników, gdy potok jest naprawdę drogi

Kiedy jedna komenda mieli dane kilkadziesiąt sekund, czasem rozsądnie jest jednak zapisać wynik pośredni, by móc go wykorzystać w kilku analizach.

# raz wygenerowany, wiele razy używany
grep '2024-06' access.log > access_2024-06.log

# różne raporty na tym samym wycinku
awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }' access_2024-06.log | sort -nr | head
awk '$9 ~ /^5/' access_2024-06.log | wc -l
awk '$9 ~ /^4/' access_2024-06.log | wc -l

Koszt jednorazowego „przycięcia” dużego pliku zwykle jest niższy niż kilkukrotne odpalanie tej samej ciężkiej sekwencji od zera.

Rozsądne alternatywy i kiedy odpuścić terminal

Kiedy find/grep/sed/awk wystarczą, a kiedy już nie

Dla jednorazowych pytań typu „ile było błędów 5xx wczoraj” czy „które endpointy są najcięższe” klasyczny zestaw narzędzi wystarcza w zupełności. Problem zaczyna się, gdy:

  • raport ma być generowany cyklicznie dla wielu ludzi,
  • dane wymagają skomplikowanych joinów między wieloma plikami,
  • trzeba przechowywać historię i wracać do niej po miesiącach.

W takich sytuacjach taniej długofalowo jest wrzucić dane do małej bazy (SQLite, PostgreSQL) lub użyć dedykowanego narzędzia do logów niż składać coraz bardziej skomplikowane potoki.

Łagodne przejście: miks terminala z bazą

Praktyczne podejście to traktowanie terminala jako warstwy „pre-processingu”, a bazy – jako miejsca, gdzie dzieje się cięższa analiza.

# czyszczenie loga przed importem do bazy
grep 'HTTP/' access.log | grep -vE '.(png|jpg|css|js)' | 
  awk '{ print $1 "," $4 "," $7 "," $9 }' > access_clean.csv

# później import do SQLite czy innej bazy
# (tu tylko przykład komendy importu)
sqlite3 logs.db ".mode csv" ".import access_clean.csv access"

Takie rozdzielenie pracy trzyma w ryzach zarówno koszt utrzymania skryptów, jak i czas ich działania, bez rezygnacji z prostoty narzędzi terminalowych.

Nawykowe skróty i aliasy, które realnie oszczędzają czas

Alias zamiast pisania tej samej litanii

Jeżeli jakaś kombinacja find/grep/sed/awk przewija się kilka razy w tygodniu, najprościej zamienić ją w alias lub funkcję powłoki. Koszt: minuta. Zysk: za każdym razem kilkanaście–kilkadziesiąt stuknięć mniej.

# ~/.bashrc albo ~/.zshrc

# klasyka: grep z kolorem i numerem linii
alias g='grep --color=auto -n'

# wyszukiwanie tekstu w drzewie katalogów (ale tylko w sensownych plikach)
alias gr='grep -R --exclude-dir=.git --exclude="*.log"'

# raport 5xx w logu access
access_5xx() {
  awk '$9 ~ /^5/' "$1" | awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }' 
    | sort -nr | head
}

Takie aliasy i funkcje działają jak „makra mentalne”: pamiętasz jedno krótkie słowo (np. access_5xx), a nie pięć parametrów do awk i sort.

Łączenie aliasów z gotowymi wzorcami regex

Najbardziej męczące są ciągi znaków, które trzeba pisać bez literówek: złożone regexy, kilka wykluczeń, lista rozszerzeń. To też można zwinąć do aliasu.

# wyszukaj todo/fixme w kodzie, pomijając vendor i pliki zbuildowane
alias todo='grep -RInE "TODO|FIXME" 
  --exclude-dir=vendor 
  --exclude-dir=node_modules 
  --exclude="*.min.js" 
  --exclude="*.map"'

# szukanie potencjalnych haseł/kluczy w repo
alias secrets='grep -RInE "(AWS_SECRET|PASSWORD|SECRET_KEY)" 
  --exclude-dir=.git 
  --exclude="*.png" --exclude="*.jpg" --exclude="*.pdf"'

Raz poprawnie złożony alias z regexem da się potem używać bez zastanawiania się „jak to było z tym backslashem”.

Praktyczne wzorce pracy: od jednorazowego strzału do małego narzędzia

Etap 1: szybki jednorazowy potok

Standardowy scenariusz: ktoś pyta na kanale „co się działo na /api/orders wczoraj po południu?”. Najtańszy wariant to sklejony ad hoc potok.

# wycinek 2024-06-27, godziny 12–17, tylko /api/orders
grep '2024-06-27' access.log | 
  awk '$4 >= "[27/Jun/2024:12" && $4 <= "[27/Jun/2024:17"' | 
  grep ' /api/orders ' | 
  awk '{ licz[$9]++ } END { for (c in licz) print licz[c], c }' | sort -nr

Działa, odpowiada na pytanie, po minucie sprawa zamknięta. Na tym etapie nie ma sensu tworzyć dedykowanych plików, jeżeli zapytanie jest rzeczywiście jednorazowe.

Etap 2: powtarzalne pytanie => mała funkcja

Jeśli takie pytanie wraca co kilka dni, opłaca się wynieść logikę do funkcji w jednym pliku narzędziowym.

# w tools.sh
errors_for_endpoint() {
  # $1 - data (YYYY-MM-DD), $2 - endpoint (np. /api/orders)
  local date="$1"
  local endpoint="$2"

  grep "$date" access.log | 
    grep " $endpoint " | 
    awk '$9 ~ /^5/ { licz[$9]++ } END { for (c in licz) print licz[c], c }' | 
    sort -nr
}

# użycie:
. ./tools.sh
errors_for_endpoint 2024-06-27 /api/orders

Modyfikacja warunków lub dodanie kolejnej metryki (np. 4xx) to wtedy edycja kilku linii w jednym miejscu, a nie grzebanie w historii poleceń.

Etap 3: więcej użytkowników => małe CLI w shellu

W chwili, gdy takie narzędzie ma używać kilka osób, przydaje się prosty interfejs z parametrami i komunikatem pomocy.

#!/bin/sh

usage() {
  echo "Użycie: $0 errors <data> <endpoint>"
  echo "       $0 top <data>"
  exit 1
}

errors_for_endpoint() {
  grep "$1" access.log | 
    grep " $2 " | 
    awk '$9 ~ /^5/ { licz[$9]++ } END { for (c in licz) print licz[c], c }' | 
    sort -nr
}

top_endpoints_for_day() {
  grep "$1" access.log | 
    awk '{ licz[$7]++ } END { for (u in licz) print licz[u], u }' | 
    sort -nr | head
}

case "$1" in
  errors)
    [ $# -eq 3 ] || usage
    errors_for_endpoint "$2" "$3"
    ;;
  top)
    [ $# -eq 2 ] || usage
    top_endpoints_for_day "$2"
    ;;
  *)
    usage
    ;;
esac

Koszt takiego „CLI na biednie” jest mały, a zdejmujesz z siebie odpowiadanie w kółko na te same pytania i ręczne kopiowanie poleceń innym.

Bezpieczniejsze modyfikacje plików: dry-run, kopie i in-place

sed -i i przyjaciele – jak nie skasować sobie projektu

Najwięcej szkód robią hurtowe zamiany „w ciemno”. Dużo taniej jest dodać jeden krok kontrolny niż potem ratować się z backupów.

# krok 1: obejrzyj wynik na ekranie
grep -R "OldName" src/ | sed 's/OldName/NewName/g' | head

# krok 2: zrób kopię katalogu (jeśli to coś mniejszego)
cp -r src src.bak

# krok 3: dopiero potem użyj sed -i
find src -type f -name '*.py' -print0 | 
  xargs -0 sed -i 's/OldName/NewName/g'

Kiedy zmiana jest naprawdę szeroka (tysiące plików w repo), często bezpieczniej jest zamiast sed -i użyć generowania nowych plików i podmiany nazw.

# tworzenie nowych plików .new obok starych
find src -type f -name '*.conf' -print0 | 
  xargs -0 -I{} sh -c 'sed "s/debug=false/debug=true/" "{}" > "{}.new"'

# szybkie porównienie jednego z nich
diff -u file.conf file.conf.new

# dopiero gdy efekt jest OK, hurtowa podmiana
find src -type f -name '*.conf.new' -print0 | 
  xargs -0 -I{} sh -c 'mv "{}" "${0%.new}"' {}

awk i generowanie nowych plików zamiast edycji w miejscu

awk naturalnie wypisuje wynik na stdout, więc łatwo wymusić „bezpieczny tryb” poprzez zapis do nowego pliku i ręczną podmianę.

# przetworzenie CSV: dodanie kolumny z sumą dwóch pól
awk -F',' 'BEGIN { OFS="," } NR==1 { print $0, "total"; next } { print $0, $3+$4 }' 
  sales.csv > sales_with_total.csv

# rzut oka: ile wierszy, czy nagłówki są OK
head sales_with_total.csv
wc -l sales.csv sales_with_total.csv

# jeśli wszystko gra – podmiana
mv sales_with_total.csv sales.csv

Ten wzorzec (nowy plik, szybka kontrola, dopiero potem mv) eliminuje większość wpadek związanych z literówką w warunku lub złym separatorem.

Łączenie find/grep/sed/awk z innymi prostymi narzędziami

sort, uniq, wc – tani doping dla klasycznego kwartetu

Często nie potrzeba niczego cięższego niż kilka prostych filtrów po drodze. Połączenie małych narzędzi zwykle jest czytelniejsze niż pisanie złożonego programu w awk.

# top 10 najczęściej logujących się użytkowników z pliku auth.log
grep 'session opened for user' auth.log | 
  awk '{ print $NF }' | 
  sort | uniq -c | sort -nr | head

# policzenie linii/wyrazów/znaków po wstępnym przefiltrowaniu
grep 'ERROR' app.log | wc -l

Jeżeli raport da się opisać jako „policz, posortuj, pokaż górę listy” – sort/uniq/wc robią robotę przy minimalnym wysiłku.

xargs i parallel – sensowne równoległe przetwarzanie

Na maszynach z kilkoma rdzeniami tanio da się przyspieszyć operacje na wielu plikach, nie pisząc całej orkiestracji.

# liczenie linii w wielu dużych plikach jednocześnie (prosty wariant)
find logs -type f -name 'access.log.*' -print0 | 
  xargs -0 -n1 -P4 wc -l

# -n1: jeden plik na proces
# -P4: maksymalnie 4 procesy równolegle

Dla bardziej rozbudowanych zadań przydaje się GNU parallel, ale często sam xargs -P daje wystarczający zysk.

Przykładowe mini-scenariusze z codziennej pracy

Analiza zużycia API per klient

Załóżmy, że log zawiera w 3. kolumnie identyfikator klienta, w 7. – endpoint, w 9. – status HTTP. Bez wielkiej infrastruktury można szybko wyłuskać top obciążających system.

# klienci o największej liczbie wywołań na /api/orders
grep ' /api/orders ' access.log | 
  awk '{ klienci[$3]++ } END { for (k in klienci) print klienci[k], k }' | 
  sort -nr | head

# klienci generujący najwięcej 5xx
awk '$9 ~ /^5/ { err[$3]++ } END { for (k in err) print err[k], k }' access.log | 
  sort -nr | head

Dla doraźnego „kto zjada nam system” to wystarczy. Jeśli takie raporty mają iść codziennie do kilku działów, dopiero wtedy ma sens migracja do bazy albo narzędzia typu ELK.

Sprzątanie śmieciowych plików po buildach

W większych projektach katalog urasta od plików tymczasowych, starych buildów, artefaktów. Da się to ogarnąć jednym sensownym skryptem opartym o find i kilka warunków.

#!/bin/sh

ROOT=${1:-.}

# usuń pliki tymczasowe edytora
find "$ROOT" -type f ( -name '*~' -o -name '*.swp' -o -name '*.tmp' ) 
  -print -delete

# usuń katalogi build/ i dist/ starsze niż 7 dni
find "$ROOT" -type d ( -name 'build' -o -name 'dist' ) -mtime +7 
  -print0 | xargs -0 rm -rf

# pokaż ile miejsca zostało odzyskane (na szybko)
du -sh "$ROOT"

Jeżeli taki skrypt odpala się raz na kilka tygodni, to wciąż jest tańsze niż instalowanie i konfigurowanie osobnych narzędzi do czyszczenia artefaktów.

Strategie na duże pliki tekstowe bez kupowania więcej RAM

Praca „na wycinkach” zamiast ładowania całości

Plik logu po kilkanaście gigabajtów nie nadaje się do pełnego wczytywania w edytor. find/grep/sed/awk zadziałają, ale trzeba mądrze ograniczać zakres.

# szybki podgląd początku i końca (zakres czasowy, format)
head -n 50 huge.log
tail -n 50 huge.log

# wycięcie jednego dnia do osobnego pliku
grep '2024-06-28' huge.log > huge_2024-06-28.log

# dopiero na mniejszym pliku rób szczegółowe analizy
grep 'ERROR' huge_2024-06-28.log | awk '{ licz[$7]++ } END { ... }'

Metoda „najpierw brutalne cięcie, potem dokładka” jest zwykle wielokrotnie szybsza niż uruchamianie ciężkiego potoku na całej historii.

Podział na kawałki z split i późniejsze sklejanie wyników

Gdy nie da się łatwo uciąć loga po dacie, alternatywą jest mechaniczne pocięcie pliku na kilka części.

# podział na pliki po 500 MB
split -b 500M huge.log huge_part_

# prosty raport 5xx na każdym kawałku osobno
for f in huge_part_*; do
  awk '$9 ~ /^5/ { licz[$7]++ } END { for (u in licz) print licz[u], u }' "$f"
done | 
  awk '{ licz[$2] += $1 } END { for (u in licz) print licz[u], u }' | 
  sort -nr | head

Każdy kawałek przetwarzany jest osobno, w pamięci mieszczą się niewielkie porcje, a na końcu wyniki są scalane przez drugi przebieg awk.

Proste wzorce „obronne” przy dzieleniu się komendami

Parametry zamiast wklejonych ścieżek

Udostępniając kolegom gotowe snippet’y, lepiej od razu ubrać je w parametr, niż wklejać twarde ścieżki typu /var/log/app/prod.log.

# zamiast:
grep 'ERROR' /var/log/app/prod.log | awk '{ ... }'

# lepiej:
log_report() {
  local file="$1"
  grep 'ERROR' "$file" | awk '{ ... }'
}

Potem wystarczy dopisać krótką instrukcję „użyj: log_report /ścieżka/do/logu”. Mniej przeróbek, mniej potencjalnych pomyłek.

Wymuszenie „tylko podgląd” w ryzykownych snippetach

Jeśli ktoś ma użyć skryptu na produkcji, wygodniej jest domyślnie działać w trybie dry-run i wymagać jawnego potwierdzenia, że można kasować/podmieniać.

Najprościej zrobić to przełącznikiem lub zmienną środowiskową, tak żeby bez świadomej decyzji nic nie kasowało i nie nadpisywało.

#!/bin/sh

DRY_RUN=1  # ustaw na 0, gdy naprawdę chcesz kasować

clean_old_logs() {
  find "$1" -type f -name '*.log' -mtime +14 -print0 |
    if [ "$DRY_RUN" -eq 1 ]; then
      xargs -0 -n1 echo "Would remove"
    else
      xargs -0 rm -f
    fi
}

# użycie: clean_old_logs /var/log/myapp

Taki szkielet ma dwie zalety. Po pierwsze, w logach sesji zostaje ślad, co byłoby usunięte, więc da się go przeanalizować bez stresu. Po drugie, przełączenie na tryb „ostry” jest jawne – ktoś musiał wejść do pliku i zmienić wartość zmiennej albo dodać parametr przy wywołaniu.

Jeżeli przekazujesz dłuższy snippet innemu zespołowi, dobrym standardem jest też wypisanie krótkiego komunikatu na stderr, zanim komenda zrobi coś nieodwracalnego. To drobiazg, ale często ratuje w sytuacji, gdy ktoś przykleił skrypt do crona i zapomniał, że na końcu jest rm -rf.

echo "About to remove old backups from $DIR (Ctrl+C, jeśli to pomyłka)" >&2
sleep 5
# dopiero po tym czasie:
find "$DIR" -type f -name '*.bak' -mtime +30 -delete

Im więcej takich prostych bezpieczników w codziennych komendach, tym mniejsza szansa na „drogi” błąd typu skasowany katalog projektu czy przejechany config na produkcji. find, grep, sed i awk same w sobie są tanie i szybkie, ale dopiero w parze z rozsądnymi nawykami dają realną przewagę: szybkie ogarnianie problemów bez czekania na droższe narzędzia, których i tak nikt nie zdąży skonfigurować na czas.

Najczęściej zadawane pytania (FAQ)

Po co uczyć się find, grep, sed i awk, skoro mam IDE i graficzne narzędzia?

Te narzędzia rozwiązują masę powtarzalnych problemów szybciej i taniej niż większość „ładnych” programów. Jedna linijka w terminalu potrafi zastąpić godziny klikania w Excelu, w edytorze tekstu czy w panelu webowym od logów.

Do prostych analiz logów, masowych podmian w konfiguracji czy szybkiej analizy CSV nie trzeba kupować dodatkowych licencji ani stawiać ciężkich systemów. find, grep, sed i awk są darmowe, dostępne praktycznie wszędzie i działają błyskawicznie, nawet na słabszych laptopach.

Czy tych narzędzi da się używać pod Windowsem?

Tak. Najprostsze opcje to:

  • WSL (Windows Subsystem for Linux) – pełne środowisko linuksowe w Windows, idealne, jeśli pracujesz z kodem lub serwerami,
  • Git Bash – lekki terminal instalowany razem z Gitem, wystarczy do większości codziennych zadań tekstowych,
  • Cygwin lub MSYS2 – gdy potrzebujesz „prawie Linuksa” w ramach jednego okna konsoli.

Jeśli chcesz tylko od czasu do czasu przefiltrować logi lub podmienić coś w plikach, Git Bash w zupełności wystarczy. To zero dodatkowych kosztów i kilka minut instalacji.

Od czego zacząć naukę: find, grep, sed, awk czy czegoś innego?

Najbardziej opłacalna kolejność na start to:

  • grep – szybkie wyszukiwanie tekstu w plikach (błędy, TODO, konkretne komunikaty),
  • find – wyszukiwanie samych plików po nazwie, dacie, rozmiarze,
  • sed – proste zamiany tekstu (np. domen, ścieżek) w wielu plikach,
  • awk – wyciąganie kolumn, proste raporty i statystyki z logów lub CSV.

W praktyce opanowanie kilku podstawowych wzorców w grep i find daje największy zwrot z czasu. sed i awk warto dorzucać stopniowo, przy konkretnym problemie – wtedy nauka idzie dużo szybciej.

Jak bezpiecznie używać find, sed i awk, żeby niczego nie skasować przez pomyłkę?

Najpierw zawsze rób „suchy bieg”. Zamiast od razu kasować lub nadpisywać pliki, wyświetl listę tego, co zostałoby zmienione. Dobre nawyki:

  • pracuj na kopii katalogu: cp -r oryginal katalog_testowy,
  • używaj -print w find zamiast od razu -delete,
  • przy -exec dodaj na początek echo, np. -exec echo rm {} ; – zobaczysz, co by się wykonało,
  • w sed najpierw uruchom komendę bez -i i obejrzyj wynik w terminalu.

Ten dodatkowy krok zabiera kilkanaście sekund, a potrafi uratować dzień pracy, gdy zamiast 20 plików przypadkiem objąłbyś 2000.

Czym się różni find . od find / i dlaczego ma to znaczenie?

find . szuka tylko w bieżącym katalogu i jego podkatalogach. To typowy scenariusz: projekt, katalog z logami, konkretny folder z danymi. Wykonuje się szybko, nie „mieli” całego dysku.

find / startuje od katalogu głównego i może przeszukać cały system plików: wszystkie konta użytkowników, pliki systemowe, backupy. Na zwykłym desktopie to często minuty czekania i masa śmieciowych wyników. Dlatego zawsze ograniczaj zakres szukania do tego fragmentu dysku, który realnie cię interesuje.

Jak praktycznie łączyć find, grep, sed i awk w jednym poleceniu?

Najprostszy sposób to potoki. Schemat jest zawsze podobny: find wybiera pliki, grep filtruje treść, sed/awk coś poprawiają lub agregują. Przykład typowej „roboty za admina” bez drogich narzędzi:

  • wyszukanie logów z ostatniej godziny, wyłuskanie błędów i zrobienie mini-statystyki:
    find /var/log/myapp -type f -mmin -60 -print0 | xargs -0 grep "ERROR" | awk '{print $7}' | sort | uniq -c | sort -nr

Za cenę jednej złożonej linijki dostajesz to, co graficzne „analyzery” logów robią w kilku kliknięciach – bez płacenia za licencję i bez czekania, aż załaduje się interfejs.

Jakie są realne przykłady zadań, które terminal robi szybciej niż GUI?

Kilka typowych scenariuszy z codziennej pracy:

  • logi: wyciąganie tylko linii z ERROR albo z konkretnym ID użytkownika i zapis do osobnego pliku jednym grepem,
  • masowe poprawki: zmiana domeny w setkach plików konfiguracyjnych za pomocą find + sed zamiast otwierania każdego pliku z osobna,
  • prosta analityka: policzenie, który endpoint API generuje najwięcej błędów, przy pomocy grep + awk + sort + uniq.

W każdym z tych przypadków konfiguracja zajmuje minutę, a zaoszczędzony czas liczysz w dziesiątkach minut lub godzinach, szczególnie gdy zadanie wraca regularnie.