bash test if a file exists

bash test if a file exists

Ein Kollege aus der Systemadministration saß vor zwei Jahren an einem Freitagnachmittag vor einem Scherbenhaufen, weil sein Skript zur automatischen Bereinigung von Log-Dateien versagt hatte. Er dachte, er hätte alles im Griff, doch sein Bash Test If A File Exists lieferte ein positives Ergebnis für eine Datei, die eigentlich ein toter symbolischer Link war. Das Skript löschte daraufhin das falsche Verzeichnis, weil die Pfadvariable leer war und der Test aufgrund mangelnder Anführungszeichen wahr zurückgab. Der Schaden? Sechs Stunden Ausfallzeit für ein kritisches Kundensystem und ein mühsames Einspielen von Backups, die natürlich genau an diesem Tag nicht tagesaktuell waren. Ich habe solche Szenarien in den letzten fünfzehn Jahren bei Dutzenden von Unternehmen gesehen. Es fängt immer mit der Annahme an, dass eine einfache Abfrage ausreicht, um die Integrität eines Workflows zu garantieren.

In der Realität ist das Dateisystem unter Linux ein tückischer Ort. Race Conditions, fehlende Berechtigungen und verschiedene Dateitypen sorgen dafür, dass eine einfache Prüfung oft nicht das tut, was man glaubt. Wer blindlings Befehle aneinanderreiht, ohne die Nuancen der Shell zu verstehen, spielt russisches Roulette mit seinen Daten. Es geht nicht darum, ob das Skript beim ersten Testlauf auf Ihrem Laptop funktioniert. Es geht darum, ob es nachts um drei Uhr unter Last auf einem Server mit vollem Speicher und Netzwerkverzögerungen immer noch sicher läuft.

Die Arroganz der einfachen eckigen Klammern

Der wohl häufigste Fehler, den ich bei Junioren und sogar erfahrenen Admins sehe, ist die Verwendung von alten Test-Konstrukten ohne Absicherung. Man schreibt [ -f $FILE ] und fühlt sich sicher. Das ist grob fahrlässig. Sobald der Pfad ein Leerzeichen enthält – was in modernen Umgebungen oder bei Benutzereingaben ständig vorkommt – bricht diese Zeile mit einem Syntaxfehler ab. Wenn dieser Fehler nicht abgefangen wird, läuft das Skript einfach weiter und führt den nächsten Befehl aus, der vielleicht zerstörerisch ist.

Ich erinnere mich an einen Fall in einem mittelständischen Logistikzentrum. Dort sollte ein Skript Frachtbriefe verarbeiten. Ein Dateiname enthielt durch einen Tippfehler ein Leerzeichen. Das Skript prüfte die Datei, warf einen Fehler, den niemand sah, und übersprang die Verarbeitung, löschte aber die Quelldatei im nächsten Schritt, weil der Programmierer davon ausging, dass der Test erfolgreich war, wenn kein explizites "Nein" kam. Wir reden hier von einem wirtschaftlichen Schaden im fünfstelligen Bereich durch verzögerte Lieferungen.

Die Lösung ist simpel, wird aber oft aus Faulheit ignoriert: Verwenden Sie immer doppelte eckige Klammern [[ ... ]] und setzen Sie Variablen grundsätzlich in doppelte Anführungszeichen. Die modernen Bash-Erweiterungen sind sicherer, da sie keine Worttrennung oder Pfadexpansion bei Variablen durchführen. Wer heute noch die alten Single-Brackets nutzt, ohne einen sehr spezifischen Grund für POSIX-Kompatibilität zu haben, handelt verantwortungslos gegenüber der Stabilität seines Systems.

Bash Test If A File Exists und das Problem mit symbolischen Links

Ein massives Missverständnis herrscht darüber, was -f eigentlich prüft. Die Dokumentation sagt, es prüft auf eine "reguläre Datei". In der Praxis bedeutet das: Wenn der Pfad auf einen symbolischen Link zeigt, der wiederum auf eine existierende Datei verweist, liefert der Test ein wahres Ergebnis. Ist der Link jedoch "tot" oder zeigt er auf ein Verzeichnis, ist das Ergebnis falsch.

Wenn der Link zur Falle wird

Ich habe in einem Rechenzentrum in Frankfurt erlebt, wie eine Backup-Routine fehlschlug, weil sie Bash Test If A File Exists für eine Konfigurationsdatei einsetzte. Die Konfiguration war über einen Symlink gelöst. Jemand hatte die Zieldatei verschoben, aber den Link nicht aktualisiert. Das Skript sah, dass der Test fehlschlug, und startete den Dienst mit Standardwerten statt mit den Sicherheitsvorgaben. Plötzlich war die Datenbank ohne Passwortschutz im internen Netz erreichbar.

Wenn Sie sicherstellen müssen, dass ein Pfad wirklich ein Link ist, müssen Sie -L verwenden. Wenn Sie wissen wollen, ob etwas überhaupt existiert – egal ob Datei, Verzeichnis, Socket oder Device – nutzen Sie -e. Der blinde Einsatz von -f ist oft nur eine halbe Wahrheit. Fragen Sie sich immer: Was soll passieren, wenn dort ein Verzeichnis liegt, wo ich eine Datei erwarte? Ein Skript sollte in so einem Fall kontrolliert sterben und eine Fehlermeldung ausspucken, anstatt einfach weiterzumachen.

Berechtigungen werden systematisch ignoriert

Ein weiterer Klassiker: Das Skript stellt fest, dass die Datei existiert, kann sie aber im nächsten Schritt nicht lesen. Warum? Weil der Test nur die Existenz prüft, nicht aber die Lesbarkeit für den aktuellen Benutzer. Viele Entwickler testen ihre Skripte als root und wundern sich dann, wenn der Cronjob des Web-Users kläglich scheitert.

Hier ist ein konkreter Vorher/Nachher-Vergleich aus einem realen Projekt zur Automatisierung von SSL-Zertifikaten:

Vorher: Das Skript prüfte mit einem einfachen Existenzcheck, ob das Zertifikat vorhanden ist. Wenn ja, versuchte es, den Webserver neu zu starten. Da das Skript jedoch unter einem eingeschränkten Benutzer lief, der zwar den Pfad sehen, aber die Datei nicht lesen konnte, schlug der Restart fehl. Der Webserver blieb offline, weil das Skript dachte, alles sei in Ordnung, und den alten Prozess bereits beendet hatte.

Nachher: Wir stellten den Prozess um. Zuerst prüften wir mit -e, ob die Datei da ist. Dann nutzten wir -r, um die Lesbarkeit sicherzustellen. Zusätzlich wurde vor dem Neustart des Dienstes mit einem Exit-Code geprüft, ob der Zugriff tatsächlich funktioniert hat. Das Skript wurde so erweitert, dass es bei fehlenden Rechten eine Alarmmeldung an das Monitoring-System schickt, anstatt den Server in den Abgrund zu reißen.

Diese zwei zusätzlichen Sekunden beim Schreiben des Codes sparen im Ernstfall Stunden bei der Fehlersuche. Verlassen Sie sich niemals darauf, dass die Existenz einer Datei auch deren Nutzbarkeit impliziert. Das ist ein Trugschluss, der in komplexen Linux-Berechtigungssystemen (ACLs, SELinux) böse endet.

Die Gefahr von Race Conditions in automatisierten Workflows

Das ist der Bereich, in dem es richtig teuer wird. Ein Skript prüft, ob eine Datei da ist, bestätigt dies und im nächsten Bruchteil einer Sekunde löscht ein anderer Prozess oder ein Nutzer diese Datei. Wenn Ihr Skript nun versucht, die Datei zu öffnen, stürzt es ab. In der Informatik nennen wir das "Time-of-check to time-of-use" (TOCTOU).

Ich habe das bei einem großen Medienhaus gesehen, das Videofiles automatisiert verarbeitete. Mehrere Instanzen desselben Skripts liefen parallel. Instanz A prüfte die Existenz einer Datei, befand sie für gut und wollte mit der Transkodierung beginnen. Genau in diesem Moment verschob Instanz B die Datei in den Zielordner. Instanz A produzierte einen kritischen Fehler, der die gesamte Queue blockierte.

Man löst das nicht durch noch mehr Tests. Man löst das durch atomare Operationen oder durch das direkte Versuchen, die Aufgabe auszuführen, und das Abfangen des Fehlers. In Bash ist das oft schwierig, aber ein guter Anfang ist es, mit temporären Dateien und dem Befehl mv zu arbeiten, der im selben Dateisystem atomar ist. Wer nur mit Prüfungen arbeitet, baut instabile Systeme.

Vernachlässigung von Exit-Codes und Fehlermanagement

Ein Bash-Skript ist nur so stark wie sein schwächstes Glied. Wenn Sie eine Prüfung durchführen, müssen Sie auch festlegen, was im negativen Fall passiert. Ein einfaches if ohne else-Zweig ist oft ein Zeichen von mangelnder Erfahrung. In produktiven Umgebungen muss jeder fehlgeschlagene Test eine Konsequenz haben: Entweder ein kontrollierter Abbruch mit exit 1 oder ein definierter Fallback-Pfad.

In meiner Zeit als Berater habe ich ein System gesehen, das Logfiles von hunderten Servern einsammelte. Das Bash-Skript prüfte die Existenz der Log-Verzeichnisse. Wenn ein Verzeichnis fehlte, machte das Skript einfach mit dem nächsten Server weiter. Das Problem? Niemand merkte, dass bei zehn Servern die Festplatten ausgefallen waren, weil das Skript keine Fehlermeldungen generierte. Es gab keine Logs, also gab es für das Skript auch nichts zu tun. Stille Fehler sind die schlimmsten Fehler. Ein guter Praktiker schreibt Skripte, die laut schreien, wenn etwas nicht passt.

Warum Bash Test If A File Exists allein niemals ausreicht

Es gibt eine unangenehme Wahrheit, die viele nicht hören wollen: Shell-Skripte sind für hochkomplexe Logik eigentlich das falsche Werkzeug. Wenn Ihre Datei-Prüfungen so kompliziert werden, dass Sie fünf verschiedene Flags kombinieren müssen, sollten Sie über Python oder Go nachdenken. Aber bleiben wir beim Bash-Kontext. Ein Test ist keine Validierung.

🔗 Weiterlesen: echo dot vs echo pop

Stellen Sie sich vor, Sie prüfen eine Datenbank-Dump-Datei. Der Test sagt: Ja, die Datei ist da. Er sagt Ihnen aber nicht, ob die Datei mitten im Schreibvorgang abgebrochen wurde und nur 0 Bytes groß ist. Ich habe erlebt, wie Unternehmen ihre Backups verloren haben, weil das Skript zwar die Existenz der Datei prüfte, aber nicht deren Größe oder Prüfsumme. Ein Backup-Skript, das nicht -s (Prüfung auf Größe > 0) verwendet, ist wertlos.

Ein weiteres Beispiel aus der Praxis: Ein Update-Skript für eine Web-Applikation. Es prüfte, ob eine neue Version als ZIP-Datei bereitlag. Der Test war positiv. Das Skript entpackte die Datei über die bestehende Installation. Leider war der Download der ZIP-Datei korrupt. Das Ergebnis war eine zerstörte Live-Umgebung. Hätte der Entwickler nach der Existenzprüfung eine MD5- oder SHA-Prüfung eingebaut, wäre das nicht passiert. Ein einfacher Existenzcheck ist nur der erste Schritt von vielen.

Strukturierte Fehlerbehandlung statt blindem Vertrauen

Um wirklich robuste Skripte zu schreiben, müssen Sie weg vom "Hoffnungs-Programmieren". Das bedeutet, dass Sie nicht hoffen, dass die Datei da ist, sondern den Fehlerfall als Standard ansehen.

  1. Prüfen Sie nicht nur auf Existenz, sondern auf den spezifischen Typ (-f, -d, -L).
  2. Validieren Sie den Inhalt oder zumindest die Größe (-s).
  3. Stellen Sie die Berechtigungen sicher (-r, -w, -x).
  4. Nutzen Sie immer doppelte Anführungszeichen um Variablen, um "Empty String"-Fehler zu vermeiden.
  5. Definieren Sie klare Exit-Strategien für den Fehlerfall.

Ich habe oft gesehen, dass Leute versuchen, mit set -e ihre Skripte sicherer zu machen. Das ist ein zweischneidiges Schwert. Wenn ein Test innerhalb eines if-Statements fehlschlägt, bricht das Skript zwar nicht ab, aber viele andere kleine Fehler führen zu einem sofortigen Stopp. Das kann erwünscht sein, führt aber oft dazu, dass notwendige Aufräumarbeiten nicht mehr ausgeführt werden. Ein erfahrener Admin nutzt trap, um bei einem Abbruch dennoch temporäre Dateien zu löschen oder Benachrichtigungen zu versenden.

Ein Wort zur Pfad-Sicherheit

Ein oft unterschätztes Risiko ist die Manipulation von Pfaden. Wenn Ihr Skript eine Datei prüft, deren Pfad aus einer Benutzereingabe oder einer externen API kommt, öffnen Sie Tür und Tor für Symlink-Attacken oder Directory Traversal. Ein Angreifer könnte einen Symlink von einer harmlos aussehenden Datei auf /etc/shadow legen. Wenn Ihr Skript dann prüft, ob die Datei existiert und sie anschließend mit erhöhten Rechten verarbeitet, haben Sie ein Sicherheitsproblem. In professionellen Umgebungen müssen Pfade vor der Prüfung normalisiert und validiert werden. Das klingt nach viel Arbeit für ein einfaches Bash-Skript, ist aber der Unterschied zwischen einem Profi-Tool und einem Hobby-Bastelprojekt.

Realitätscheck

Kommen wir zum Punkt: Bash-Skripte zu schreiben ist einfach, sie stabil zu betreiben ist extrem schwer. Wenn Sie glauben, dass ein schnelles Skript mit ein paar Datei-Tests Ihre Probleme dauerhaft löst, liegen Sie falsch. Die meiste Zeit bei der Entwicklung eines produktionsreifen Skripts sollte in die Fehlerbehandlung fließen, nicht in den eigentlichen "Happy Path".

In meiner Laufbahn habe ich gelernt, dass die besten Skripte diejenigen sind, die 80 % ihres Codes für Randfälle und Fehlerszenarien aufwenden. Es braucht Disziplin, jedes Mal doppelte Klammern zu setzen, Variablen zu quoten und Exit-Codes zu prüfen. Es gibt keine Abkürzung. Wenn Sie diese Details ignorieren, werden Sie früher oder später einen Anruf am Wochenende bekommen, weil ein System steht. Erfolg in diesem Bereich bedeutet nicht, dass das Skript läuft, sondern dass es sicher scheitert. Wenn Sie nicht bereit sind, diese Tiefe an Absicherung zu investieren, sollten Sie die Finger von Automatisierungen lassen, die kritische Daten berühren. Es ist hart, aber es ist die einzige Wahrheit, die Sie vor kostspieligen Fehlern bewahrt. Wer den Test des Dateisystems als triviale Aufgabe abtut, hat die Komplexität moderner IT noch nicht verstanden. Behandeln Sie jeden Pfad als potenzielles Risiko und jeden Test als eine Frage, die das Betriebssystem Ihnen vielleicht falsch beantwortet, wenn Sie nicht präzise genug fragen.

HH

Hannah Hartmann

Mit faktenbasierter Arbeitsweise liefert Hannah Hartmann Beiträge, die Leserinnen und Lesern Orientierung im Nachrichtengeschehen geben.