473 lines
12 KiB
Markdown
473 lines
12 KiB
Markdown
# 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
|