Znajdź wszystko
Po tej lekcji: kursant znajduje pliki po nazwie, typie, rozmiarze, dacie, właścicielu i uprawnieniach, bezpiecznie wykonuje na nich komendy (-exec, xargs -0, -delete) i wie, kiedy zamiast find użyć locate.
Wstęp
Noc, dysk prawie pełny, usługi zaczynają się sypać. Masz kilka minut, żeby odpowiedzieć na pytanie: co tam tyle zajmuje?
sudo find / -xdev -type f -size +100M -exec ls -lh {} + 2>/dev/null | sort -k5 -rh | headJedna linijka i wiesz, gdzie szukać winnego. A rano kolejna: „usuń pliki tymczasowe starsze niż miesiąc”. Też jedna linijka. Dziś find, narzędzie, które zna każdy plik w budynku.
Ochroniarz z latarką i recepcja z księgą
Pamiętasz, że folder to budynek, a podfoldery to pokoje? find to ochroniarz z latarką. Wchodzi do budynku, zagląda do każdego pokoju, do każdej szafy, i sprawdza każdy plik z listą: „większy niż megabajt, nie ruszany od miesiąca, należy do Kuby”. Dokładny, zawsze aktualny, ale przy dużym budynku chwilę to trwa.
locate to recepcja z księgą gości. Nie chodzi po pokojach, tylko zagląda do spisu, który ktoś zrobił rano. Odpowiada natychmiast, ale nie wie o nikim, kto przyszedł po południu.
Na koniec lekcji poznasz oba. Najpierw zrób sobie poligon. Skrypt lab.sh:
#!/bin/bash
# Poligon do ćwiczeń z find
mkdir -p lab/logi lab/zdjecia lab/kopie
cd lab || exit 1
head -c 2M /dev/zero > logi/app.log
head -c 500K /dev/zero > logi/stary.log
head -c 3M /dev/zero > kopie/baza.sql
touch pusty.txt zdjecia/IMG_001.jpg zdjecia/IMG_002.JPG
echo hej > notatki.txt
echo x > 'raport roczny.tmp'
echo y > a.tmp
echo z > b.TMP
touch -d '40 days ago' a.tmp 'raport roczny.tmp' logi/stary.log
touch -d '10 days ago' b.TMP
chmod 777 notatki.txthead -c 2M /dev/zero robi plik z dwoma megabajtami zer, a touch -d '40 days ago' cofa plikowi datę modyfikacji. Wehikuł czasu dla plików, w którym nikt nie ginie. Wszystkie dalsze przykłady uruchamiamy z folderu lab.
Składnia: gdzie, co, co zrobić
find GDZIE SZUKAĆ JAKIE WARUNKI CO ZROBIĆfind . -name '*.log'./logi/stary.log
./logi/app.logKropka to „tu, gdzie stoję”. -name sprawdza nazwę wzorcem z gwiazdką z lekcji 5, więc glob, a nie regex. Wzorzec zawsze w apostrofach, inaczej gwiazdkę rozwinie Bash, zanim find ją zobaczy. A domyślna akcja to wypisanie ścieżki.
find . -iname '*.jpg'./zdjecia/IMG_001.jpg
./zdjecia/IMG_002.JPG-iname ignoruje wielkość liter. Aparaty w telefonach piszą rozszerzenia raz tak, raz tak, chyba po to, żebyśmy nie zasnęli.
[macOS] Na Macu find wymaga ścieżki. GNU find bez ścieżki szuka w bieżącym folderze, BSD find z Maca wypisze błąd użycia. Pisz zawsze find . i będzie działać wszędzie.
-type: plik czy folder
find . -type d.
./logi
./zdjecia
./kopie-type f to zwykłe pliki, -type d foldery, -type l dowiązania symboliczne. Prawie zawsze dopisuj -type f, kiedy szukasz plików. Inaczej przy kasowaniu albo zmianie uprawnień zahaczysz o foldery.
-size: rozmiar
find . -type f -size +1M./logi/app.log
./kopie/baza.sqlPlus to „więcej niż”, minus to „mniej niż”, bez znaku „dokładnie”. Jednostki: c bajty, k kilobajty, M megabajty, G gigabajty, wszystko po 1024. Na serwerze najczęściej -size +100M albo +1G.
A teraz pułapka, o którą potyka się prawie każdy:
find . -type f -size -1M./pusty.txt
./zdjecia/IMG_001.jpg
./zdjecia/IMG_002.JPGGdzie notatki.txt z czterema bajtami? Gdzie stary.log z pięciuset kilobajtami? GNU find zaokrągla rozmiar w górę do pełnych jednostek. Cztery bajty to dla niego „jeden megabajt”, a jeden to nie mniej niż jeden. Mniej niż jeden megabajt mają tylko pliki puste. Jak sklep, który każdy zakup zaokrągla do pełnej złotówki, a potem pyta, kto wydał mniej niż złotówkę.
Naprawa: mniejsza jednostka.
find . -type f -size -1024k./a.tmp
./b.TMP
./logi/stary.log
./notatki.txt
./pusty.txt
./raport roczny.tmp
./zdjecia/IMG_001.jpg
./zdjecia/IMG_002.JPGA pliki puste znajdziesz wprost: find . -type f -empty.
-mtime: kiedy ostatnio zmieniany
find . -type f -mtime +30./a.tmp
./logi/stary.log
./raport roczny.tmp-mtime liczy w pełnych dobach od ostatniej zmiany zawartości. +30 to „więcej niż 30 pełnych dób”, czyli w praktyce co najmniej 31 dni. -7 to „mniej niż 7 dób”, czyli świeżynki z ostatniego tygodnia. Sprawdziłem: plik sprzed 30 dni i godziny nie łapie się na +30, a plik sprzed 31 dni już tak. Drobiazg, ale kiedy ktoś pyta „czemu ten plik nie został usunięty”, to właśnie dlatego.
Krewni: -mmin liczy w minutach (-mmin -15 to „zmieniane w ostatnim kwadransie”), -newer plik to „nowsze niż ten plik”. A -atime i -ctime dotyczą odczytu i zmiany metadanych, jak właściciel czy uprawnienia.
-user i -perm: właściciel i uprawnienia
find . -user kuba
find . -type f -perm -o+w./kopie/baza.sql
./notatki.txtPierwsza komenda: pliki Kuby. Przydaje się, kiedy Kuba odchodzi z firmy i trzeba sprawdzić, co po nim zostało. Jest też -group oraz -nouser, czyli pliki, których właściciela już nie ma w systemie. Sieroty po usuniętych kontach.
Druga: pliki, które może zapisywać każdy. Klucze do pokoju wiszą na klamce. -perm ma trzy tryby:
-perm 777to dokładnie te uprawnienia, ani bitu więcej, ani mniej.-perm -o+wz minusem to „ma co najmniej te bity”.-perm /o+wz ukośnikiem to „ma którykolwiek z tych bitów” (GNU, na Macu zamiast ukośnika jest plus).
Klasyka audytu bezpieczeństwa z lekcji 16: sudo find / -xdev -type f -perm -4000 2>/dev/null, czyli wszystkie programy z bitem SUID.
Łączenie warunków
Warunki obok siebie to „i”. Do „lub” jest -o, do „nie” wykrzyknik, a do grupowania nawiasy, które trzeba zabezpieczyć ukośnikiem, bo inaczej zjadłby je Bash:
find . -type f \( -name '*.tmp' -o -name '*.TMP' \)./a.tmp
./raport roczny.tmp
./b.TMPDwie opcje, które ratują życie na serwerze:
-maxdepth 1to „nie schodź głębiej niż jeden poziom”. Ochroniarz sprawdza tylko parter.-xdevto „nie wychodź do innych systemów plików”. Bez tegofind /poleże też do zamontowanego dysku sieciowego z milionem plików i wróci w przyszłym tygodniu.
A na „Permission denied” przy szukaniu jako zwykły użytkownik jest drugi głośnik do czarnej dziury: 2>/dev/null.
Akcje: -exec, -printf, -delete
Znaleźć to połowa roboty. Druga połowa to zrobić coś ze znalezionym.
find . -type f -size +1M -exec ls -lh {} +-rw-r--r-- 1 kuba root 3.0M Nov 3 16:38 ./kopie/baza.sql
-rw-r--r-- 1 root root 2.0M Nov 3 16:38 ./logi/app.log-exec uruchamia komendę, a w miejsce {} wstawia znalezione ścieżki. Koniec może być na dwa sposoby:
{} \;uruchamia komendę osobno dla każdego pliku. Kurier, który jedzie do magazynu po każdą paczkę osobno.{} +wkłada do jednego wywołania tyle plików, ile się zmieści. Kurier, który bierze cały wózek.
Różnica nie jest akademicka. U mnie na 2825 plikach .gz w /usr/share wersja ze średnikiem trwała około 4,3 sekundy, a z plusem 0,1 sekundy. Czterdzieści razy szybciej. Jeśli komenda przyjmuje wiele plików naraz, zawsze plus.
Bezpieczna wersja: -ok zamiast -exec pyta o każdy plik „czy na pewno?”.
Na Linuksie jest jeszcze -printf, ściągnięty z printf z lekcji 44:
find . -type f -size +1M -printf '%k KB %TY-%Tm-%Td %p\n'2048 KB 2026-11-03 ./logi/app.log
3072 KB 2026-11-03 ./kopie/baza.sql%k to rozmiar w kilobajtach, %TY-%Tm-%Td data modyfikacji, %p ścieżka. [macOS] Tego nie ma w BSD find, tam zostaje -exec ls -lh {} + albo -ls.
I wreszcie -delete. Tu robimy przerwę na poważną rozmowę.
Zasada: najpierw zawsze ta sama komenda bez -delete. Czytasz listę. Jeśli wszystko na niej może zniknąć, dopiero wtedy dopisujesz -delete na samym końcu. Nie ma kosza. Nie ma „cofnij”. Nie ma „czy na pewno?”.
find . -iname '*.tmp' -mtime +30./a.tmp
./raport roczny.tmpLista się zgadza: dwa stare pliki tymczasowe, a b.TMP ma dopiero dziesięć dni, więc zostaje. Teraz:
find . -iname '*.tmp' -mtime +30 -delete
find . -iname '*.tmp'./b.TMPxargs: taśma, która robi argumenty
Rura podaje tekst na wejście. Ale wiele komend, na przykład ls, rm czy chmod, nie czyta wejścia, tylko chce nazwy plików jako argumenty. xargs to pracownik na końcu taśmy, który zbiera linijki i wkłada je do komendy jako argumenty.
Tylko że domyślnie tnie po spacjach. Patrz:
find . -name '*.tmp' | xargs ls -lls: cannot access './raport': No such file or directory
ls: cannot access 'roczny.tmp': No such file or directory
-rw-r--r-- 1 root root 2 Sep 24 17:38 ./a.tmp„raport roczny.tmp” został posiekany na dwa nieistniejące pliki. Przy ls to tylko komunikat. Przy rm to może być usunięcie zupełnie innego pliku, który akurat nazywa się raport.
Rozwiązanie: niech find oddziela nazwy znakiem, którego nie może być w nazwie pliku. To bajt zerowy. -print0 po stronie find, -0 po stronie xargs:
find . -name '*.tmp' -print0 | xargs -0 ls -l-rw-r--r-- 1 root root 2 Sep 24 17:38 ./a.tmp
-rw-r--r-- 1 root root 2 Sep 24 17:38 ./raport roczny.tmpCały temat spacji w nazwach plików rozbierzemy w następnej lekcji. Na dziś zapamiętaj parę -print0 | xargs -0 jak numer alarmowy.
Przydatne opcje xargs: -n 1 to jeden argument na wywołanie, -I{} wstawia element w wybrane miejsce (xargs -I{} cp {} {}.bak), -P 4 uruchamia cztery procesy równolegle. I -r: GNU xargs przy pustym wejściu i tak raz uruchamia komendę, więc ... | xargs ls przy braku wyników wypisze bieżący folder. -r tego zabrania. [macOS] Na Macu xargs przy pustym wejściu niczego nie uruchamia, a -r przyjmuje tylko dla zgodności.
Kiedy -exec, a kiedy xargs? Do prostych rzeczy -exec ... {} + wystarcza i nie ma problemu ze spacjami. xargs wygrywa, kiedy chcesz równolegle (-P) albo kiedy lista plików przychodzi skądś indziej niż z find.
locate: recepcja z księgą gości
sudo apt install plocate # [Ubuntu]
sudo dnf install plocate # [RHEL] 10 i klony; na starszych RHEL pakiet bywa nazwany mlocate
sudo updatedb
locate -i img_001/home/eryk/lab/zdjecia/IMG_001.jpgupdatedb obchodzi cały dysk raz i zapisuje spis nazw do bazy. Potem locate tylko w tej bazie szuka, więc odpowiada od razu, nawet na dużym serwerze. -i ignoruje wielkość liter, -c podaje liczbę wyników.
Haczyk, który sprawdziłem na sobie:
touch swiezy_plik_48.txt
locate swiezy_plik_48.txt; echo "kod: $?"kod: 1Plik istnieje, a locate o nim nie wie, bo spis jest z rana. Na Ubuntu bazę odświeża timer systemd plocate-updatedb.timer, raz dziennie. Po ręcznym sudo updatedb plik się znajdzie. Odwrotnie też: po usunięciu pliku locate dalej go pokazuje, chyba że dodasz -e, które sprawdza, czy plik nadal istnieje.
Czy ktoś przez locate podejrzy nazwy plików roota? Nie. Zrobiłem plik w /root, odświeżyłem bazę i jako zwykły użytkownik dostałem pusty wynik. Recepcja nie podaje numerów pokojów, do których nie masz wstępu.
[macOS] Na Macu locate jest, ale baza jest domyślnie nieaktywna. Włączasz ją przez sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist, a ręcznie odświeżasz przez sudo /usr/libexec/locate.updatedb. Odświeża się sama raz w tygodniu.
Zasada: locate, kiedy szukasz po nazwie i chcesz szybko. Find, kiedy potrzebujesz rozmiaru, daty, właściciela, uprawnień albo pewności, że wynik jest aktualny.
Jak to działa pod spodem
Find czyta folder za folderem. Dla każdego wpisu pyta jądro o metadane pliku, czyli i-węzeł: rozmiar, daty, właściciela, uprawnienia. To dlatego find może szukać po tych wszystkich rzeczach i dlatego na milionie plików potrafi chwilę pomielić.
Najważniejsze: wyrażenie find to nie lista warunków, tylko mały program czytany od lewej do prawej. Dla każdego pliku find sprawdza po kolei. Jeśli któryś warunek nie pasuje, reszty już nie sprawdza i bierze następny plik. Akcje, jak -delete czy -exec, to też elementy tego programu, wykonywane w miejscu, w którym stoją. To ma dwie konsekwencje:
- Kolejność ma wpływ na szybkość. Tani warunek, jak
-name, daj przed drogim, jak-exec grep. - Kolejność ma wpływ na to, co się stanie. O tym za chwilę w typowym błędzie.
Z -delete find przechodzi drzewo od dołu, najpierw zawartość folderu, potem sam folder. Inaczej nie dałoby się usunąć folderu, bo pusty folder to jedyny, który da się skasować.
A -exec {} \; dla każdego pliku tworzy nowy proces: rozwidlenie, uruchomienie programu, sprzątanie. Tysiące kucharzy zatrudnianych na jedno jajko. Stąd te czterdzieści razy wolniej od {} +.
Locate z kolei w ogóle nie patrzy na dysk przy szukaniu. Czyta tylko swoją bazę w /var/lib/plocate/. [dla zaawansowanych: program plocate ma ustawiony bit SGID dla grupy plocate, która jako jedyna może czytać plik bazy. Dzięki temu każdy użytkownik może szukać, ale plocate przed pokazaniem wyniku sprawdza, czy pytający ma dostęp do danego folderu. Co pomija updatedb, ustawiasz w /etc/updatedb.conf: PRUNEPATHS, PRUNEFS.]
Ściąga flag
| Komenda | Flaga | Co robi |
|---|---|---|
| find | -name / -iname | nazwa wzorcem glob / bez wielkości liter |
| find | -type f / d / l | plik / folder / dowiązanie |
| find | -size +100M | większy niż 100 MB (c, k, M, G; zaokrągla w górę!) |
| find | -empty | pusty plik albo folder |
| find | -mtime +30 / -7 | zmieniany ponad 30 / mniej niż 7 pełnych dób temu |
| find | -mmin -15 | zmieniany w ostatnich 15 minutach |
| find | -newer plik | nowszy niż wskazany plik |
| find | -user / -group / -nouser | właściciel / grupa / właściciel nie istnieje |
| find | -perm 644 / -perm -o+w / -perm /o+w | dokładnie / co najmniej te bity / którykolwiek (macOS: +) |
| find | ! / -o / \( \) | nie / lub / grupowanie |
| find | -maxdepth N | nie schodź głębiej niż N poziomów |
| find | -xdev | nie wychodź poza ten system plików |
| find | -exec cmd {} + | uruchamia komendę na wielu plikach naraz |
| find | -exec cmd {} \; | uruchamia komendę osobno dla każdego pliku |
| find | -ok cmd {} \; | jak -exec, ale pyta o każdy plik |
| find | -delete | usuwa (zawsze na końcu, najpierw test bez niego) |
| find | -print0 | oddziela wyniki bajtem zerowym (dla xargs -0) |
| find | -printf '%s %p\n' | własny format wyniku (tylko GNU) |
| find | -ls | wynik w stylu ls -l (działa też na Macu) |
| xargs | -0 | czyta elementy oddzielone bajtem zerowym |
| xargs | -r | nie uruchamia nic przy pustym wejściu (GNU) |
| xargs | -n N / -I{} / -P N | N argumentów naraz / własne miejsce wstawienia / N procesów równolegle |
| locate | -i / -c / -e | bez wielkości liter / liczba wyników / tylko istniejące pliki |
| updatedb | (bez flag) | odświeża bazę locate (sudo) |
⚠ Typowy błąd
mkdir kosz && cd kosz
touch a.tmp wazny.txt raport.pdf
mkdir dok && touch dok/umowa.odt
find . -delete -name '*.tmp'
ls -A | wc -l0Miały zniknąć pliki .tmp. Zniknęło wszystko: ważny plik, raport, folder z umową. Dlaczego? Pamiętasz, że find czyta wyrażenie od lewej do prawej? Dla każdego pliku najpierw wykonał -delete, a dopiero potem sprawdził, czy nazwa pasuje. Ochroniarz najpierw wyrzucił każdego przez okno, a dopiero potem zapytał o nazwisko.
Naprawa: akcje zawsze na końcu, po wszystkich warunkach. I zawsze próba generalna bez -delete.
Drugi klasyk, mniej bolesny:
find . -name *.logfind: paths must precede expression: `y.log'
find: possible unquoted pattern after predicate `-name'?Gwiazdka bez apostrofów. W bieżącym folderze były x.log i y.log, więc Bash rozwinął wzorzec, zanim find go zobaczył, i find dostał -name x.log y.log. Gorzej, kiedy w folderze jest dokładnie jeden plik .log: wtedy nie ma błędu, tylko find po cichu szuka jednej konkretnej nazwy. Wzorzec zawsze w kopercie.
✎ Zadanie
Znajdź w /var/log pliki większe niż 1 MB zmienione w ostatnich 7 dniach i wypisz je z rozmiarem. Potem znajdź w folderze domowym pliki .tmp starsze niż 30 dni: najpierw tylko je wypisz, a dopiero potem usuń.
Pokaż rozwiązanie
Część pierwsza: duże i świeże pliki w /var/log.
sudo find /var/log -type f -size +1M -mtime -7 -exec ls -lh {} +-rw-r----- 1 syslog adm 1.3M Nov 3 15:50 /var/log/kern.log
-rw-r----- 1 syslog adm 2.4M Nov 3 16:24 /var/log/syslog-type f to tylko zwykłe pliki, -size +1M to więcej niż mebibajt, -mtime -7 to zmienione w ciągu ostatnich siedmiu dni, a -exec ls -lh {} + dokleja ładny rozmiar. sudo, bo część logów czyta tylko root. Jak wolisz same liczby, jest -printf '%s\t%p\n', czyli rozmiar w bajtach, tabulator i ścieżka.
Część druga: stare pliki .tmp w domu. Najpierw tylko patrzymy:
find ~ -type f -name '*.tmp' -mtime +30/home/eryk/projekty/stare rzeczy/notatka robocza.tmp
/home/eryk/stary.tmpLista wygląda dobrze, więc dopiero teraz kasujemy, tą samą komendą z dopisanym końcem:
find ~ -type f -name '*.tmp' -mtime +30 -print -delete-print przed -delete wypisze każdy usuwany plik, żeby był ślad. I najważniejsze: -delete zawsze na samym końcu. find czyta warunki od lewej. Jak napiszesz find ~ -delete -name '*.tmp', to najpierw usunie wszystko, a potem sprawdzi, czy nazwa pasuje. Bardzo dokładne sprzątanie. Bardzo krótkie.
A teraz zauważ jedną rzecz w wyniku: stare rzeczy/notatka robocza.tmp. Spacja w folderze, spacja w nazwie. find sobie poradził. Twój skrypt by sobie poradził? O tym dzisiaj.
Co dalej
„raport roczny.tmp” dał nam dziś w kość. W następnej lekcji bierzemy na warsztat spacje w nazwach plików: cytowanie, IFS, bezpieczne pętle po plikach i nullglob. Czyli jak pisać skrypty, które nie wybuchają, kiedy ktoś z działu marketingu nazwie plik „FINAL wersja (2) OSTATECZNA.docx”.