Skriptbasierte Versionsverwaltung für Einzeldateien

Aus LinuxUser 04/2020

Skriptbasierte Versionsverwaltung für Einzeldateien

© Rebel, Fotolia

Hausmannskost

Wer Einzeldateien mit Git versioniert, schießt mit Kanonen auf Spatzen. Es genügen auch Bordmittel und eine zwar alte, aber trotzdem gute Idee.

Der Wunsch, eine ältere Version eines Arbeitsergebnisses wieder zu beschaffen, ist keineswegs neu: Bereits Goethe sah sich mit diesem Problem konfrontiert. Aus Anlass des 50-jährigen Jubiläums seines “Die Leiden des jungen W.” suchte er 1824 nach einer Erstausgabe von 1774 – vergeblich.

Auf dem Computer lösen Versionskontrollsysteme (VCS) dieses Problem, mit deren Hilfe sich die sukzessiven Versionen einer Datei speichern, verwalten und jederzeit wieder reproduzieren lassen. Dabei reicht die Bandbreite von rein lokal arbeitenden Varianten wie RCS über Client-Server-Systeme wie CVS oder Subversion bis hin zu hochkomplexen verteilten Systemen wie Git [1], das unter anderem für den Linux-Kernel zum Einsatz kommt.

Doch nicht nur Kernel-Entwickler möchten ihre Arbeitsergebnisse versionieren. Wer den ganzen Tag am Rechner sitzt, der schreibt gern auch Skripte, um wiederkehrende Jobs zu automatisieren. Auch hier erweist es sich oft als nützlich, eine ältere Version wieder hervorkramen zu können – etwa, um misslungene Änderungen zurückzurollen.

Aber auch Wissenschaftler oder Ingenieure möchten Dateien, die mithilfe tage- oder wochenlanger numerischer Berechnungen entstanden, nicht so mir nichts, dir nichts überschreiben. Dasselbe trifft auf die chemischen Strukturmodelle zu, mit denen sich der Autor beschäftigt. Sie umfassen bis zu mehrere Tausend Atome oder Atomgruppen. Die mühsam erstellten und durch langwierige Berechnungen modifizierten Molekülmodelle sollen keinesfalls überschrieben werden, nur weil eine andere Konfiguration oder Konformation des Modells ausgetestet werden muss – ein normaler Vorgang in der Strukturmodellierung.

Auch hier kann eine Versionsverwaltung also helfen. Dabei erscheinen VCS wie Git aber völlig überdimensioniert. Wer wie der Autor als Naturwissenschaftler beispielsweise schon mit dem Gromacs-Paket [2] (dynamische Simulationen von Molekül-Clustern) gearbeitet hat, dem ist dort das simple Prinzip der Vorgängerdateisicherung begegnet. Dabei erfolgt die Versionssicherung durch Modifizieren des Dateinamens.

Der Autor, der unter anderem auch schon mit VMS [3] gearbeitet hat, kam durch die quelloffene Variante FreeVMS auf die Idee, das dort existierende Konzept zur Einzeldateiverwaltung unter Linux für seine Skripte und Quelltexte umzusetzen.

Einzeldateien versionieren

Ruft man unter Linux vi NAME.TXT auf, editiert die Datei und schließt sie wieder, entsteht als einzige Sicherung die temporäre Datei NAME.TXT~. Sie spiegelt den Inhalt des Files beim Programmaufruf wider. Sofern man sie nicht manuell umbenennt und gesondert ablegt, gehen beim nächsten Editieren die Inhalte der ursprünglichen Datei unwiederbringlich verloren – bei Quelltexten etwa bestimmte Features, die zuvor noch funktionierten, in der neuen Version aber nicht mehr.

Wie sieht nun die Versionierung bei einer VMS-Datei aus? Im Grunde ganz einfach: Das System speichert die Versionen einer Datei fortlaufend nummeriert als NAME.TXT;1, NAME.TXT;2 und so weiter. Die Datei lässt sich dabei weiter als NAME.TXT mit dem Editor öffnen und bearbeiten. Beim Speicher erhöht VMS die Versionsnummer automatisch, sodass die neue Version als NAME.TXT;3 auf der Platte landet. Dabei wendet VMS dieses Prinzip nicht nur auf Dateien an, sondern bei Bedarf auch auf Verzeichnisse.

Diese Vorgehensweise lässt sich ohne Weiteres auch auf Linux übertragen. Allerdings akzeptiert das keine Semikolons im Dateinamen; sie gelten in Skripten als Trennzeichen. Linux-VCS wie Subversion oder RCS verwenden stattdessen Kommas, die Linux ebenso wie Ziffern für Dateinamen erlaubt. Damit stellt es kein größeres Problem mehr dar, mithilfe eines Bash-Skripts ein Tool zu schreiben, das Dateien per Namensmodifikation versioniert. Das Ergebnis mit dem Namen vvi (“versioning vi”) sehen Sie in Listing 1.

Veränderungen in einem Quelltext erfolgen meist in einem Texteditor, im Fall des Autors in dessen Lieblingswerkzeug Vi [4], dessen Aufruf deshalb im Skript hart kodiert ist. Zugunsten der Lesbarkeit wurde hier auf die teils sehr mächtigen Optionen des Editors verzichtet.

Bevorzugen Sie einen anderen Editor, modifizieren Sie den Aufruf in Zeile 64 entsprechend. Für eine universelle Lösung setzen Sie in der ~/.bashrc die (üblicherweise nicht vorbelegte) Shell-Variable EDITOR und ändern den Aufruf zu $EDITOR $tmpname;. So lässt sich der verwendete Editor jederzeit durch Modifikation der Variablen ändern.

Listing 1

#!/bin/bash
# (C) Peter Friedel, 2020
#
declare -i ok;
declare -i version;
oldsum="";
newsum="";
version=1;
ok=0;
display_usage() {
  echo "script $0: vi with version control for single files,"
  echo "similar like in VMS"
  echo "The filename version control is performed by modifying"
  echo "the filenames of the argument list by"
  echo ""
  echo "usage: $0 filelist"
  echo "with filelist = filename | filelist filename;"
}
if [ $# -lt 1 ]; then
  display_usage;
  exit 1
fi
for var in "$@"; do
  dateien=$(ls -1 $var);
  for datei in "${dateien[@]}"; do
    currentdir=$(pwd);
    dateidir=$(dirname $datei);
    dateibase=$(basename $datei);
    if [ "${dateidir%%[a-zA-Z0-9]*}" == "/" ]; then
      cd ${dateidir};
    else
      cd ${currentdir}/${dateidir};
    fi
    if [ ! -e ${dateibase} ]; then
      touch ${dateibase};
    fi
    if [ ! -e .${dateibase} ]; then
      mkdir .${dateibase};
    fi
    cd .${dateibase};
    version=1;
    ok=0;
    while [ $ok -eq 0 ]; do
      name="$dateibase,$version";
      if [ -e ../$name ]; then
        mv ../$name ./;
      fi
      if [ ! -e $name ]; then
        echo "$name does not exist";
        tmpname="$dateibase,tmp";
        initname="$dateibase,0";
        if [ -e ../$initname ]; then
          mv ../$initname ./;
        fi
        oldsum=$(sha1sum ../$dateibase | awk '{print $1}');
        cp ../$dateibase $tmpname;
        if [ $version -eq 1 ] && [ ! -e $initname ]; then
          cp $tmpname $initname;
        fi
        #
        /usr/bin/vi $tmpname;
        #
        newsum=$(sha1sum $tmpname | awk '{print $1}');
        if [ "$oldsum" != "$newsum" ]; then
          cp $tmpname $name;
          cp $tmpname ../$dateibase;
        else
          echo "No modifikation!";
        fi
        rm $tmpname;
        ok=1;
      else
        echo "$name does exist";
        ((version++));
      fi
    done
    cd ${currentdir};
  done
done

Das Bash-Skript

Das im Listing 1 dargestellte Skript wurde praktisch mit sich selbst erstellt und hat mittlerweile die Version 47 erreicht, was wohl als Proof of Concept der prinzipiellen Vorgehensweise gelten darf.

Das Skript ist dafür ausgelegt, auch mehrere Dateien zu behandeln (äußere Schleife ab Zeile 26), deren Namen Wildcards enthalten dürfen (innere Schleife ab Zeile 28). Das Skript reicht die Dateien dann einzeln an den Editor durch, sie lassen sich also nicht im Vi-Befehlsmodus mit :n! nachladen.

Im Inneren der Doppelschleife werden zunächst ein paar Verzeichnisdaten und der eigentliche Dateiname genau spezifiziert (Zeilen 29 bis 31) – zum einen, um Verzweigungen zu ermöglichen, zum anderen, um die genauen Rücksprünge am Ende der Schleife zu garantieren. Das Skript prüft hier auch, ob es sich beim Verzeichnisnamen der zu bearbeitenden Datei um einen absoluten Pfad handelt, und springt entsprechend in das jeweilige Verzeichnis (Zeilen 32 bis 36).

In der While-Schleife ab Zeile 46 wird nun geprüft, ob es bereits eine Datei mit einem Namen dateibase,version gibt. Beginnen wir mit dem Else-Zweig (ab Zeile 75), der kürzer ist: In diesem Fall existiert die Datei schon und trägt eine bestimmte Versionsnummer. Diese zählt das Skript dann so lange nach oben, bis es bei einer noch nicht existierenden Version anlangt, die sich zum Editieren nutzen lässt.

Dann wird die Datei angelegt. Falls Sie zum ersten Mal daran arbeiten, erhält sie die Versionsnummer 0, die das Skript auch nicht mehr anrührt (Zeile 54). Daneben entsteht eine temporäre Datei zum Editieren (Zeile 53). Ein Vergleich zwischen alter und neuer (modifizierter) Version mit Sha1sum prüft die Inhalte (Zeilen 58 und 66).

Stimmen sie nicht überein, kopiert das Skript den Inhalt des temporären Files sowohl in die Datei mit neuer Versionsnummer als auch in jene ohne Versionsnummer (Zeilen 68 und 69). Andernfalls gab es keine Änderungen, was eine entsprechende Meldung dokumentiert (Zeile 71). Die temporäre Datei wird dann gelöscht und die Variable ok auf 1 gesetzt (Zeilen 73 und 74), sodass das Skript anschließend die While-Schleife verlässt.

Weitere mögliche Features

Die Tabelle “Aufrufkette” zeigt die Möglichkeiten mehrerer sukzessiver Aufruf des Skripts. Dabei lassen sich sogar Verzweigungen einer schon erstellten Version erzeugen, ganz ähnlich wie in Git (dritte Zeile). Ein Wiedervereinigen (“merge”) wäre bei Bedarf noch zu implementieren.

Aufruf

Wirkung

vvi text

erzeugt .text/text,0 und .text/text,1

vvi text

erzeugt .text/text,2 aus .text/text,1

vvi .text/text,2

Erzeugt .text/.text,2/text,2,0 und .text/.text,2/text,2,1 aus .text/text,2 (rekursiv behandelte Verzweigung)

vvi text

erzeugt .text/text,3 aus .text/text,2 etc.

Eine andere Möglichkeit zur Erweiterung könnte darin bestehen, die Dateiversionen nicht platzraubend komplett abzuspeichern. Mithilfe der Tools Diff und Patch [5] ließe sich ein Mechanismus einbauen, der lediglich die Veränderungen sichert. Das Skript müsste dann die letzte Version aber auch wieder komplett aufbauen, sobald sie modifiziert werden soll. Ein Kollege des Autors brachte die Idee ein, die Verwaltung der Versionen in ein Unterverzeichnis zu stecken (im vorliegenden Skript bereits implementiert), sodass es auf der Arbeitsebene wieder halbwegs ordentlich aussieht.

Die Möglichkeiten der Einzeldateiversionierung von VMS schöpft das Skript (noch) nicht aus, wie zum Beispiel ein PURGE oder DELETE, um Dateiversionen ab beziehungsweise bis zu einer bestimmten Versionsnummer oder einem Datum zu behalten oder zu löschen. Das Purge-Prinzip kennen Sie sicher vom Einspielen eines neuen Linux-Kernels, bei dem das System ältere Kernel entfernt.

Auf der To-do-Liste des Autors steht seit Kurzem die Idee einer Fehlerbehandlung für den Fall, dass der Editor einmal während der Arbeit abstürzt. Dazu ließe sich das Skript so erweitern, dass es die Swap-Datei des verwendeten Editors nutzt, um die Versionierung wieder in einen konsistenten Status zu bringen.

Zu guter Letzt kann man mit diesem Skript statt eines Texteditors auch andere Werkzeuge verwenden, die Veränderungen an Dateien vornehmen (Sed, Awk etc.), ohne dabei die vorhergehende Version zu überschreiben. Um mehrere Dienste mit einer Versionierung zu ergänzen, müsste das Skript allerdings die programmspezifischen Optionen der jeweils benutzten Software von der zu bearbeitenden Dateiliste (filelist, siehe Listing 1) separieren.

Fazit

Das Skript vvi zeigt, wie sich komfortabel einzelne Dateien kleiner und mittlerer Größe versionieren lassen, ohne dazu die steile Lernkurve eines VCS wie Git absolvieren zu müssen. Über die vorliegende Fassung hinaus stehen dabei zahlreiche Möglichkeiten der Erweiterung (Purge, Delete, Merge) offen. Als der Autor seinen Kollegen dieses Skript zum Korrekturlesen gab, entstanden dabei sofort eine ganze Reihe von neuen Ideen für solche Erweiterungen und Modifikationen. Bei seiner täglichen Arbeit möchte der Autor das Werkzeug nicht mehr missen und ist sich ziemlich sicher, dass auch andere Anwender davon profitieren können. (jlu)

Der Autor

Peter Friedel arbeitet am Leibniz-Institut für Polymerforschung Dresden e.V. als theoretischer Chemiker, Strukturmodellierer und Administrator eines Linux-Rechner-Clusters am Institut für Theorie der Polymere. Darüber hinaus interessiert ihn aus naturwissenschaftlicher und philosophischer Sicht die Frage, wie das Leben auf unserem Planeten entstanden ist.

Glossar

VCS

Version Control System. Versionskontrollsystem zur Erfassung von Änderungen an Dokumenten oder Dateien. Ein VCS sichert alle Versionen mit Zeitstempel und Benutzerkennung in einem Archiv, sodass sie sich später wiederherstellen lassen.

VMS

Virtual Memory System. 1977 erstmals veröffentlichtes klassisches Multiuser- und Multitasking-Betriebssystem für VAX-Rechner des Herstellers Digital Equipment Corporation (DEC). Zahlreiche Konzepte aus VMS finden sich in Microsoft Windows wieder.

Infos

  1. Git: https://git-scm.com

  2. Gromacs: Mark J. Abraham et al., SoftwareX 2015, Vol. 1-2, S. 19ff., https://doi.org/10.1016/j.softx.2015.06.001

  3. VMS: https://de.wikipedia.org/wiki/Virtual_Address_eXtension

  4. Vim: https://github.com/vim/vim

  5. Diff-Varianten: Harald Zisler, “Spurensuche”, LU 02/2020, S. 30, https://www.linux-community.de/43952

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
LinuxUser 04/2020 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.

1 Kommentar
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Enno
6 Jahre her

Ein nützliches kleines Skript. Das Analyseprogramm ShellCheck merkt noch an, dass bei fehlgeschlagenem Verzeichniswechsel das Skipt besser abbreche (cd … || exit) und im Zweifelsfall die Variablen, etwa Dateinamen, zur Verarbeitung von Leerzeichen in Anführungszeichen zu setzen sind.

Nach oben