Defekte Verlinkungen aufspüren

Aus LinuxUser 06/2022

Defekte Verlinkungen aufspüren

© lassedesignen / 123RF.com

In der Sackgasse

Über Links lassen sich Daten aus unterschiedlichen Ordnern an einer Stelle zusammenführen. Damit die Verknüpfungen nicht ins Leere zeigen, analysieren Sie die Struktur mit ein paar hilfreichen Skripten.

Kaputte Daten(strukturen) nerven, ganz besonders in Backups, bei Softwareinstallationen und größeren Projekten mit Verlinkungen. Wir stellen Ihnen hier mehrere Wege vor, um die Konsistenz solcher Datenstrukturen zu prüfen und defekte Verlinkungen aufzuspüren.

Backups sind wichtig, eine funktionierende Wiederherstellung noch wichtiger. Nichts ist enttäuschender, als wenn Sie nach dem Einspielen der zuvor gesicherten Daten durch einen Zufall feststellen, dass einige der Verlinkungen aus dem Backup nicht mehr funktionieren: Der Verweis ist zwar noch vorhanden, doch das Ziel existiert nicht mehr, und somit zeigt der Link ins Leere.

Es macht mitunter sehr viel Mühe, die defekten Links manuell zu finden und von Hand wieder zu reparieren. Besser ist es, wenn Sie gar nicht erst in diese ärgerliche Situation kommen. Wir zeigen Ihnen im Folgenden, wie Sie mit Bordmitteln nachprüfen, ob in Verzeichnisstrukturen kaputte Verweise bestehen.

Das Gezeigte nützt Ihnen nicht nur in der Restore-Situation, sondern auch dann, wenn Sie Software entwickeln und diese in Form eines Archivs zum Herunterladen bereitstellen oder wenn Sie eine Anwendung in verschiedenen Versionen installieren. Damit vermeiden Sie Schluckauf bei Ihren Benutzern.

Beispieldaten

Als Ausgangspunkt dient uns ein beispielhaftes Software- oder Projektverzeichnis, wie Sie es in der freien Wildbahn häufig antreffen. In Abbildung 1 sehen Sie die Struktur des Verzeichnisses. Eine solche Darstellung erzeugen Sie bei Bedarf flink mit dem Kommando tree [1].

Abbildung 1: Die Struktur des in den Beispielen genutzten Softwareprojekts.

Abbildung 1: Die Struktur des in den Beispielen genutzten Softwareprojekts.

Der Verzeichnisbaum enthält zwei Versionen der Software beziehungsweise des Projekts. Es gibt drei Links: Einer zeigt auf die alte Version (genannt old), einer auf die aktuelle (genannt current), und der dritte auf eine Datendatei namens dataset3, die noch nicht oder nicht mehr existiert.

Varianten

Eine kleine, überschaubare Projektstruktur wie im gezeigten Beispiel lässt sich noch manuell testen und überprüfen. Bei größeren Projekten führt das aber schnell zu Fehlern, weil man fast zwangsläufig etwas übersieht. Daher empfiehlt es sich, die Prüfung zu automatisieren. Wir stöbern dazu im Unix/Linux-Werkzeugkasten und bauen uns ein Skript, das dann die Aufgabe zuverlässig für uns erledigt.

Nachfolgend zeigen wir vier alltagstaugliche Lösungsvarianten – ein Shell-Skript als Kombination aus einer rekursiven Funktion und einer For-Schleife über alle Dateien und Verzeichnisse, einen speziellen Find-Aufruf, ein Python-Skript sowie die Werkzeuge Symlinks, Fslint, Rmlint und Chase.

Shell-Skript

Variante 1, das Shell-Skript (Listing 1), operiert mit einer rekursiven Funktion namens check(). Dabei nimmt check() nur einen Parameter entgegen: das Verzeichnis, das es auf kaputte Links untersuchen soll (Zeile 16). In der Funktion läuft eine For-Schleife über alle Einträge (Zeile 2 bis 14).

Listing 1

find-broken-links.sh

function check {
  for eintrag in $1/*; do
    # echo "prüfe $eintrag ... "
    if [ -d "$eintrag" ]; then
      check $eintrag
    else
      if [ -L "$eintrag" ]; then
        ziel=$(readlink "$eintrag")
        if [ ! -e "$ziel" ]; then
          echo "kaputter Link: von $eintrag zu $ziel"
        fi
      fi
    fi
  done
}
check $1

Bei jedem Eintrag prüft check() zunächst, ob es sich um ein Verzeichnis handelt (Zeile 4). Falls ja, wird die Funktion mit diesem Verzeichnis als Parameter wieder aufgerufen (Zeile 5). Ansonsten erfolgen zwei weitere Tests: Handelt es sich um einen Link (Zeile 7), und falls ja, wo zeigt er hin (Zeile 8 und 9)? In Zeile 8 gibt das Kommando readlink das Ziel zurück, auf das der Link zeigt, und speichert das Ergebnis in der lokalen Variablen ziel.

In Zeile 9 prüft das Skript, ob das Link-Ziel existiert. Falls nicht, gibt die Funktion eine entsprechende Fehlermeldung auf Stdout aus (Zeile 10). Einträge im Verzeichnis, bei denen es sich nicht um Links handelt, ignoriert die Routine. Ist die gesamte Liste abgearbeitet, kehrt check() zum Aufrufpunkt zurück. Nach dem Abarbeiten des gesamten ursprünglichen Verzeichnisses beendet sich das Skript.

Rufen Sie nun das Skript auf, erhalten Sie eine ähnliche Ausgabe wie in Listing 2. Hier erfolgte der Aufruf mit . für das aktuelle Verzeichnis als Ausgangspunkt. Die Ausgabe umfasst zwei Zeilen, da current auf version2 zeigt und unsere Funktion dem Verweis folgt.

Listing 2

Ausgabe von find-broken-links.sh

$ ./find-broken-links.sh .
kaputter Link: von ./project/current/data/dataset3 zu project/version1/data/dataset3
kaputter Link: von ./project/version2/data/dataset3 zu project/version1/data/dataset3

Alternative

Alte Hasen werden jetzt einwenden, dass Variante 1 viel zu komplex ist und dass mit dem Werkzeug Find eine viel elegantere Lösung bereitsteht. Dem Einwand gehen wir gern nach. Wir verwenden bei Find den Schalter -xtype l, der genau dazu gedacht ist, kaputte Links aufzuspüren. Listing 3 zeigt, dass das auch klappt.

Listing 3

Suche per Find

$ find . -xtype l
./project/version2/data/dataset3

Jetzt wissen wir, welcher Link kaputt ist, aber noch nicht, wohin er zeigt. Daher kombinieren wir in Listing 4 nun Find und eine For-Schleife, womit wir Listing 1 um die Hälfte verkürzen. In Zeile 1 lassen wir Find die ganze Arbeit erledigen und erhalten eine Liste aller gefundenen, defekten Links unterhalb des Startverzeichnisses. In der For-Schleife von Zeile 2 bis Zeile 5 ermittelt Readlink noch das jeweilige Verweisziel.

Listing 4

find-broken-links2.sh

eintragsliste=$(find "$1" -xtype l)
for eintrag in $eintragsliste; do
  ziel=$(readlink "$eintrag")
  echo "kaputter Link: von $eintrag zu $ziel"
done

Rufen Sie nun das Skript aus Listing 4 auf, reduziert sich die Ausgabe auf den einzigen kaputten Verweis in unserem Projektverzeichnis (Listing 5).

Listing 5

Ausgabe von find-broken-links2.sh

$ ./find-broken-links2.sh .
kaputter Link: von ./project/version2/data/dataset3 nach project/version1/data/dataset3

Python-Skript

Sagt Ihnen die Shell zum Programmieren nicht zu, ist Python vielleicht eine passendere Wahl. Listing 6 zeigt ein Python-Skript, das Listing 1 von der Funktionsweise her sehr ähnelt. Es benutzt Funktionen aus den beiden Standardmodulen os und sys, die Zeile 1 in den aktuellen Namensraum importiert. In den Zeilen 3 bis 19 ist eine Funktion namens walk() definiert, die das als Parameter top übergebene Verzeichnis durchläuft und alle Einträge darin überprüft. Der Aufruf von walk() erfolgt in Zeile 22, nachdem zuvor in Zeile 21 das übergebene Verzeichnis als Parameter ausgewertet wurde.

Listing 6

find-broken-links.py

import os,sys
def walk(top):
  try:
    entries = os.listdir(top)
  except os.error:
    return
  for name in entries:
    path = os.path.join(top, name)
    if os.path.isfile(path):
      pass
    if os.path.isdir(path):
      walk(path)
    if os.path.islink(path):
      destination = os.readlink(path)
      if not os.path.exists(path):
        print("kaputter Link: von %s points nach %s" % (path, destination))
  return
startingDir = sys.argv[1]
walk(startingDir)

Zunächst erstellt und validiert das Programm ein Listing (Zeile 4 bis 7). Im Fehlerfall bricht die Funktion walk() hier ab. Danach prüft der Code jeden Eintrag im Verzeichnis daraufhin, ob es sich dabei um eine Datei (Zeile 11), ein Verzeichnis (Zeile 13) oder einen Link (Zeile 15) handelt. Dateien überspringt die Routine. Bei Verzeichnissen wird rekursiv erneut die Funktion walk() mit dem Verzeichnisnamen als Parameter aufgerufen.

Bei einem Link hingegen ermittelt die Funktion readlink() aus dem Modul os in Zeile 16 das Ziel. Ist es leer, handelt es sich um einen defekten Link, und die Funktion liefert eine entsprechende Fehlermeldung. Nachdem sie alle Einträge im Verzeichnis geprüft hat, kehrt die Funktion zum Aufrufpunkt zurück. Wenn Sie das Skript in dem Verzeichnisbaum aus unserem Beispiel aufrufen, erhalten Sie eine Ausgabe mit zwei Einträgen wie in Listing 2.

Symlinks

Das Tool Symlinks [2] ist dafür gedacht, symbolische Links zu bereinigen, indem es beispielsweise absolute in relative Links umwandelt und defekte Links entfernt. Mit den beiden Parametern -r und -v erreichen Sie, dass Symlinks rekursiv eine Verzeichnisstruktur durchsucht und ausführliche Informationen zu den Verweisen ausgibt.

Listing 7 zeigt den Aufruf für unser Projektverzeichnis. Dabei findet Symlinks einen Verweis, den es als kaputt einstuft (“dangling” heißt so viel wie baumelnd), sowie zwei relative Verweise. Ein Blick auf die Laufzeit zeigt keinen wesentlichen Unterschied zu Listing 1 und Listing 3. Um nur die defekten Verweise herauszufiltern, kombinieren Sie den Symlinks-Aufruf mit Egrep (Listing 8):

Listing 7

Symlinks

$ symlinks -rv .
dangling: /home/frank/project/version2/data/dataset3 -> project/version1/data/dataset3
relative: /home/frank/project/old -> project/version1
relative: /home/frank/project/current -> project/version2

Listing 8

Symlinks und Egrep

$ symlinks -rv . | egrep "^dangling:"
dangling: /home/frank/project/version2/data/dataset3 -> project/version1/data/dataset3

Fslint

In dieselbe Kategorie wie Symlinks fällt das GUI-basierte Werkzeug Fslint [3], das auf dem Kommandozeilenwerkzeug Findbl (im Paket fslint) aufsetzt. Rufen Sie findbl ohne weitere Parameter (oder -d) auf, durchsucht es das aktuelle Verzeichnis nach kaputten Links und gibt die Suchtreffer nacheinander aus.

Listing 9 zeigt das Ergebnis des Aufrufs, das mit denen aus Listing 2 und Listing 8 praktisch identisch ist. Das Verhalten von Findbl klärt sich nach einem genaueren Blick darauf: Es handelt sich schlicht um ein Shell-Skript, das sich zur Recherche auf Find stützt.

Listing 9

Findbl

$ /usr/share/fslint/fslint/findbl .
project/version2/data/dataset3 -> project/version1/data/dataset3

Rmlint und Chase

In einem Seitenfach der Werkzeugkiste tauchen noch Rmlint [4] und sein grafisches Frontend Rmlint-gui sowie Chase [5] auf. Die Bilder der GUI auf der Rmlint-Projektwebseite sehen wirklich toll aus, allerdings konnten wir das unter Debian GNU/Linux 11 nicht reproduzieren.

Rmlint ähnelt Fslint und zielt darauf ab, Inkonsistenzen bei Einträgen im Dateisystem zu finden und zu bereinigen. Das beinhaltet auch das Erkennen von kaputten Links. Den Aufruf dazu sehen Sie in der ersten Zeile von Listing 10.

Mit dem Schalter -T wählen Sie aus, wonach Rmlint sucht, bl steht als Abkürzung für “broken links”. Die Option -o bestimmt das Ausgabeformat, mit dem Wert pretty:stdout erhalten Sie eine aufgehübschte Darstellung. Abbildung 2 zeigt einen Beispielaufruf, bei dem Rmlint zwei defekte Links aufspürt – so soll es sein.

Abbildung 2: Das Suchergebnis vom Rmlint für unser Beispiel.

Abbildung 2: Das Suchergebnis vom Rmlint für unser Beispiel.

Das Werkzeug Chase übernimmt ebenfalls eine spannende Aufgabe: Es spürt die Datei auf, auf die ein symbolischer Link tatsächlich verweist. Es liefert im Fehlerfall 1 zurück, wenn das Verweisziel nicht existiert. Die zweite Zeile in Listing 10 zeigt den Aufruf auf den Verweis old aus unserem Beispiel, das Ergebnis ist der Dateiname.

Listing 10

Rmlint und Chase

$ rmlint -T bl -o pretty:stdout .
$ chase old
/project/version1

Fehler vermeiden

Bleibt noch die Frage, wie Sie das Entstehen defekter Links verhindern. Im Grunde genommen bleibt hier nur der Rat zu mehr Sorgfalt, da es abgesehen vom Dateisystem keinen Ort gibt, in dem alle Verweise hinterlegt sind. Einen Dienst, der im Hintergrund prüft, dass Links intakt bleiben und Sie warnt, bevor ein Link kaputtgeht, kennen wir nicht.

Sehen Sie besser vor dem Löschen einer Datei mit einem der vorgestellten Werkzeuge nach, ob Symlinks darauf existieren. Stehen Ihnen lediglich Find und Readlink zur Verfügung, gehen Sie wie in Listing 11 gezeigt vor. Der Aufruf listet beide Komponenten – den Verweis und das Verweisziel – nebeneinander auf. Dazu benutzt Find die Option -exec, um mittels Echo den Namen und danach das via Readlink ermittelte Link-Ziel anzuzeigen.

Listing 11

Find und Readlink

$ find . -type l -exec echo -n {} "-> " ';' -exec readlink {} ';'
./project/version2/data/dataset3 -> project/version1/data/dataset3
./project/old -> project/version1
./project/current -> project/version2

Behalten Sie dabei im Hinterkopf, dass Symlinks über Dateisystemgrenzen hinweg gehen können. Somit bleibt Ihnen nur, alles zu prüfen, was im Dateisystem eingehängt ist.

Fazit

Defekte Verweise in Dateistrukturen zuverlässig aufzuspüren, erfordert etwas Aufwand, lässt sich aber machen. Die hier vorgestellten Werkzeuge helfen Ihnen dabei, diese Situation leichter zu meistern. (cla)

Über den Autor

Frank Hofmann arbeitet zumeist von unterwegs, bevorzugt in Berlin, Genf und Kapstadt, als Entwickler, Trainer und Autor. Er gehört zu den Verfassern des Debian-Paketmanagement-Buchs.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
LinuxUser 06/2022 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben