# find - Dateien und Verzeichnisse suchen Der Befehl `find` durchsucht Verzeichnisse rekursiv nach Dateien oder Ordnern anhand verschiedener Kriterien. ## Grundsyntax ```bash find [Pfad] [Suchkriterien] [Aktionen] ``` ## Suchkriterien ### Nach Namen suchen ```bash find . -name "datei.txt" # exakte Übereinstimmung find . -iname "datei.txt" # case-insensitive find . -name "*.log" # mit Wildcards ``` ### Nach Dateityp suchen ```bash find . -type f # nur Dateien find . -type d # nur Verzeichnisse find . -type l # symbolische Links ``` ### Nach Dateigröße suchen ```bash find . -size +10M # größer als 10 MB find . -size -100k # kleiner als 100 KB find . -size 0 # leere Dateien ``` ### Nach Berechtigungen suchen ```bash find . -perm 644 # Dateien mit Rechten 644 find . -user alice # Dateien von User "alice" find . -group admins # Dateien von Gruppe "admins" ``` ### Suchtiefe begrenzen ```bash find . -maxdepth 1 -type f ``` Nur im aktuellen Ordner, nicht in Unterordnern. ## Suche nach Datum und Zeit ### Zeitstempel-Typen - **mtime** (modification time) = Inhalt geändert - **atime** (access time) = Datei gelesen - **ctime** (change time) = Metadaten geändert (chmod, chown) ### Relative Zeitangaben (Tage) ```bash find . -mtime -7 # in den letzten 7 Tagen geändert find . -mtime +30 # älter als 30 Tage find . -atime -1 # in den letzten 24 Stunden geöffnet find . -ctime -1 # Metadaten in letzten 24h geändert ``` Dateien zwischen X und Y Tagen: ```bash find . -mtime +7 -mtime -30 ``` ### Absolute Zeitangaben (Datum, GNU find) ```bash find . -newermt "2025-04-01" # mtime nach Datum find . -newerat "2025-04-01" # atime nach Datum find . -newerct "2025-04-01" # ctime nach Datum find . -newermt "2023-09-01" ! -newermt "2023-09-10" # zwischen zwei Daten ``` **Hinweis:** `-newermt/at/ct` sind GNU-Erweiterungen (nicht auf BSD/macOS). ### Mit Referenzdatei ```bash touch -d "2025-04-01" /tmp/refdate find . -newer /tmp/refdate ``` ### Übersicht Zeitoptionen | Option | Bedeutung | |--------|-----------| | `-mtime -7` | in den letzten 7 Tagen geändert | | `-mtime +30` | älter als 30 Tage | | `-atime -2` | in den letzten 2 Tagen geöffnet | | `-ctime -1` | Metadaten in letzten 24h geändert | | `-newermt "2023-09-01"` | mtime nach Datum (GNU) | | `-newerat "2023-09-01"` | atime nach Datum (GNU) | | `-newerct "2023-09-01"` | ctime nach Datum (GNU) | | `-newer datei.txt` | neuer als Referenzdatei | ## Bedingungen kombinieren ### UND-Verknüpfung (Standard) ```bash find . -type f -size +1M -mtime -7 ``` Dateien größer als 1 MB UND in den letzten 7 Tagen geändert. ### ODER-Verknüpfung ```bash find . -type f \( -name "*.jpg" -o -name "*.png" \) ``` Alle `.jpg` ODER `.png` Dateien. ### Negation ```bash find . -type f ! -iname "*.mp3" ``` Alle Dateien, die NICHT auf `.mp3` enden. Mehrere Endungen ausschließen: ```bash find . -type f ! \( -iname "*.mp3" -o -iname "*.flac" \) ``` ## Aktionen auf gefundene Dateien ### Anzeigen ```bash find . -name "*.log" -print # Standard find . -name "*.log" -ls # mit Details ``` ### Dateien löschen **Grundprinzip:** Immer erst testen, dann löschen! ```bash # 1. Testen find . -name "*.tmp" -print # 2. Anzahl prüfen find . -name "*.tmp" | wc -l # 3. Dry-run find . -name "*.tmp" -exec echo rm {} + # 4. Löschen find . -name "*.tmp" -delete ``` Alternative mit `-exec`: ```bash find . -name "*.tmp" -exec rm {} + # effizient (mehrere auf einmal) find . -name "*.tmp" -exec rm {} \; # einzeln find . -name "*.tmp" -exec rm -i {} \; # mit Rückfrage ``` Mit xargs (sicher bei Leerzeichen): ```bash find . -name "*.tmp" -print0 | xargs -0 rm ``` ### Mit -exec Befehle ausführen ```bash find . -name "*.log" -exec rm {} + # Löschen find . -type f -name "*.sh" -exec chmod +x {} + # Ausführbar machen find /etc -type f -mtime -1 -exec cp {} /backup/ \; # Kopieren find . -type f -name "*.log" -mtime +7 -exec gzip {} + # Komprimieren ``` - `{}` = Platzhalter für gefundene Datei - `\;` = Ende des Befehls (einzeln, langsamer) - `+` = mehrere Dateien gleichzeitig (effizienter) ### Komplexe Shell-Befehle ```bash find . -type f -name "*.jpg" -exec sh -c 'for f; do convert "$f" -resize 800x "${f%.jpg}_small.jpg" done' _ {} + ``` ## Pfad- und Namensfilter ### Mit -path (Glob-Muster) ```bash find ~/Musik -type f -path "*/zzz Verschiedene/AlbumArt_*" ``` `-path` verwendet Shell-Wildcards (Globs), nicht reguläre Ausdrücke. ### Mit -regex (Reguläre Ausdrücke) ```bash find ~/Musik -type f -regextype posix-extended \ -regex '.*/zzz Verschiedene/AlbumArt_.*' ``` `-regex` matcht den gesamten Pfad, daher oft `.*` am Anfang nötig. ## Praktische Anwendungsfälle ### Alte Dateien löschen ```bash find /tmp -type f -mtime +7 -delete ``` ### Leere Dateien/Verzeichnisse finden ```bash find . -type f -empty # leere Dateien find . -type d -empty # leere Ordner ``` ### Dateien mit falschen Rechten ```bash find / -type f -perm -o+w # weltweit schreibbar ``` ### Große Dateien finden ```bash find /home -type f -size +100M find . -type f -exec du -h {} + | sort -rh | head -10 # Top 10 größte ``` ### Dateien nach Datum und Pfad ```bash find ~/Musik -type f \ -newermt "2025-04-01" \ -path "*/zzz Verschiedene/AlbumArt_*" \ -print ``` ### Dateien sichern ```bash find /etc -type f -mtime -1 -exec cp {} /backup/ \; ``` ### Backup-Kandidaten mit Datum ```bash find /backups -type f -name "*.tar.gz" \ -newermt "2023-08-01" ! -newermt "2023-08-31" ``` ### Logfile-Analyse ```bash find /var/log -type f -size +100M -newermt "2023-09-01" ``` ### Nur Dateien, nicht Verzeichnisse ändern ```bash find . -type f -exec chmod 644 {} + find . -type d -exec chmod 755 {} + ``` ## Best Practices und Fehler vermeiden ### Sicherheit beim Löschen ```bash # IMMER erst testen find . -name "*.tmp" -print find . -name "*.tmp" | wc -l find . -name "*.tmp" -exec echo rm {} + # Dann löschen find . -name "*.tmp" -delete ``` ### Leerzeichen in Dateinamen ```bash # RICHTIG: -print0 und xargs -0 find . -name "*.txt" -print0 | xargs -0 rm # RICHTIG: -exec mit + find . -name "*.txt" -exec rm {} + # FALSCH: Pipe ohne -print0 find . -name "*.txt" | rm # funktioniert nicht! ``` ### Trailing Slash bei -path ```bash find . -path "*/ordner/*" # Dateien IN ordner/ find . -path "*/ordner" # NUR ordner/ selbst ``` ### -delete Reihenfolge ```bash # RICHTIG: -type f VOR -delete find . -type f -name "*.tmp" -delete # FALSCH: kann Verzeichnisse löschen find . -name "*.tmp" -delete ``` ### -delete und -prune sind unvereinbar (wichtig!) **`-delete` aktiviert automatisch `-depth`.** Bei `-depth` (Tiefensuche) ist `-prune` aber wirkungslos – der Schutz wird also genau dann aufgehoben, wenn man ihn braucht. ```bash # FALSCH: -prune schützt NICHT, find warnt und führt den Befehl trotzdem aus! find ~/.cache -name .git -prune -o -type f -atime +30 -delete ``` Ausgabe von GNU find: ``` find: Die Option -delete aktiviert -depth automatisch, aber -prune ist wirkungslos, wenn -depth aktiv ist. In diesem Fall muss -depth zusätzlich angegeben werden. ``` Das ist eine **Warnung, kein Fehler** – der Befehl läuft weiter, mit deaktiviertem Schutz. Das hat im April 2026 bei einem AUR-Cache-Cleanup die `.git/HEAD`- und `.git/config`-Dateien von 24 Paketen gelöscht (siehe [[IT-Know-How/Linux/yay Cache reparieren]]). **Lösung: `-prune` durch `-not -path` ersetzen.** `-path` funktioniert auch bei `-depth`: ```bash # RICHTIG: -not -path statt -prune find ~/.cache -type f -atime +30 -not -path '*/.git/*' -delete ``` Verifiziertes Verhalten: | Datei | Ergebnis | |-------|----------| | `normal/old.txt` (alt) | gelöscht | | `.git/HEAD` (alt) | **bleibt erhalten** | | `.git/config` (alt) | **bleibt erhalten** | > **Merksatz:** `-prune` funktioniert nur bei der *Breitensuche* (Voreinstellung). > Sobald `-depth` im Spiel ist – etwa durch `-delete` oder `-exec` mit `{} +` – > muss der Ausschluss über `-not -path` oder `-not -name` erfolgen. ### Ausschlusslisten sicher formulieren Mehrere Ausschlüsse kombinieren: ```bash # .git und node_modules ausschließen find . -type f -atime +30 -not -path '*/.git/*' -not -path '*/node_modules/*' -delete # statt .git prüfen: nur in echten Cache-Verzeichnissen löschen find ~/.cache -mindepth 2 -type f -atime +30 -not -path '*/.git/*' -delete ``` Immer vorher mit `-print` prüfen, wie viele Dateien betroffen sind: ```bash find ~/.cache -type f -atime +30 -not -path '*/.git/*' -print | wc -l ``` ### noatime: warum -atime auf dem Root-Filesystem täuscht Bei einem `noatime`-Mount wird die Zugriffszeit beim **Lesen nicht mehr aktualisiert**. `-atime` misst dann faktisch nur noch das Alter der letzten **Schreiboperation** – also praktisch `mtime`: ```bash # Mount-Optionen prüfen findmnt -T /home # TARGET SOURCE FSTYPE OPTIONS # / /dev/... ext4 rw,noatime,commit=60 # Bei noatime ist -atime +30 == "in 30 Tagen nicht mehr geschrieben" ``` Folge: Ein `find -atime +30 -delete` löscht auch Dateien, die **täglich gelesen** werden – sofern sie 30 Tage lang nicht geschrieben wurden. Bei Cache-Bereinigungen ist `-mtime` meist die ehrlichere Wahl, weil Caches ohnehin nur geschrieben, nie gelesen werden. `atime` vs. `mtime` im Vergleich: | Mount | `-atime +30` bedeutet | |-------|----------------------| | `relatime` (Standard) | wirklich: seit 30 Tagen nicht mehr gelesen | | `noatime` | seit 30 Tagen nicht mehr **geschrieben** | | `strictatime` | wie `relatime`, aber exakter Zeitstempel | ### Sicheres Aufräumen mit Ausschlussliste Kombiniertes Rezept für Cache-Bereinigungen – zuerst prüfen, dann löschen: ```bash # 1. Was würde gelöscht? (Anzahl + Größe) find ~/.cache -type f -atime +30 -not -path '*/.git/*' -print | wc -l find ~/.cache -type f -atime +30 -not -path '*/.git/*' -printf '%s\n' \ | awk '{s+=$1} END {printf "%.2f GB\n", s/1024/1024/1024}' # 2. Abhängig vom Ziel: # a) altersbasiert: .git über -not -path ausnehmen find ~/.cache -type f -atime +30 -not -path '*/.git/*' -delete # b) nur bestimmte Artefakte: die Endungen begrenzen, .git bleibt außen vor find ~/.cache -type f \( -name '*.pkg.tar.zst' -o -name '*.AppImage' \ -o -name '*.tar.gz' \) -not -path '*/.git/*' -delete ``` > **Nicht mit `-prune` löschen:** `-delete` schaltet intern auf `-depth` um, und > `-prune` wird dann wirkungslos. Wer trotzdem `-prune` im selben Befehl nutzt, > verliert den `.git`-Schutz exakt im Moment des Löschens. Ausschlüsse immer über > `-not -path` formulieren. ### Zu viele Argumente ```bash # FALSCH: kann "Argument list too long" geben find . -name "*.txt" -exec rm {} \; # RICHTIG: mit + find . -name "*.txt" -exec rm {} + # RICHTIG: mit xargs find . -name "*.txt" -print0 | xargs -0 rm ``` ## Alternative Tools - **fd** - moderner, schneller find-Ersatz mit besserer Syntax - **ripgrep (rg)** - für Textsuche in Dateien - **locate** - schnelle Dateinamensuche (nutzt Datenbank) Beispiel mit fd: ```bash fd "*.log" # Einfacher als find fd -t f -s +100M # Dateien > 100MB fd -e jpg -x convert {} ... # Mit exec ``` ## Siehe auch - [[IT-Know-How/Linux/BASH Basics/ls]] - Dateien auflisten - [[IT-Know-How/Linux/BASH Basics/grep]] - Textsuche in gefundenen Dateien - [[IT-Know-How/Linux/BASH Basics/Dateiberechtigungen]] - Rechte und Eigentümer von Dateien - [[IT-Know-How/Linux/BASH Basics/tar]] - Gefundene Dateien archivieren - [[IT-Know-How/Linux/BASH Basics/rsync]] - Dateien synchronisieren - [[IT-Know-How/Linux/BASH Basics/sed]] - Gefundene Dateien bearbeiten - [[IT-Know-How/Linux/BASH Basics/awk]] - Ausgabe von find verarbeiten - [[IT-Know-How/Linux/yay Cache reparieren]] - Realer Anwendungsfall: Cache-Cleanup hat `.git` zerstört