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 |
|---|---|
|
|
erzeugt |
|
|
erzeugt |
|
|
Erzeugt |
|
|
erzeugt |
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
-
Git: https://git-scm.com
-
Gromacs: Mark J. Abraham et al., SoftwareX 2015, Vol. 1-2, S. 19ff., https://doi.org/10.1016/j.softx.2015.06.001
-
VMS: https://de.wikipedia.org/wiki/Virtual_Address_eXtension
-
Diff-Varianten: Harald Zisler, “Spurensuche”, LU 02/2020, S. 30, https://www.linux-community.de/43952






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.