Files
it-know-how/Linux/BASH Basics/find.md
T

473 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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