Dieser Beitrag ist erschienen in freiesMagazin (Link) 01/2010 / Lizenz: GNU Free Documentation License (GNU FDL) (Link) / Autor: Dominik Wagenführ.
Wenn Sie diesen Artikel weiterverwenden möchten, beachten Sie bitte die Lizenzbedingungen. Vielen Dank.
Raus aus der Ubuntu-Paketabhängigkeitshölle
von Dominik Wagenführ
Wer ein „normales“ Ubuntu-System, d. h. eines mit Internetanschluss besitzt, wird das Problem nicht kennen. Alle anderen Nutzer sind aber sicherlich schon des Öfteren verzweifelt, als sie ein Paket installieren wollten, aber keinen direkten Internetzugang hatten. Das Paket herunterladen ist schön und gut, aber es fehlen immer die ganzen Abhängigkeiten.
Die native Lösung, bei der man ein Paket nach dem anderen per USB-Stick von einem PC mit Internetanschluss zu dem ohne trägt, ist alles andere als komfortabel. Die zweite direkte Lösung wäre die Installation der Pakete auf dem ersten Ubuntu-System, um diese dann per USB-Stick zu System B zu tragen. Der Haken an der Sache: Welche Pakete waren schon auf System 1 installiert, welche sind wirklich notwendig? Wenn man zwei ähnliche Systeme betreibt, ist das meist noch ein lösbares Problem, aber wenn diese sich total unterscheiden, z. B. ein Ubuntu-System als Basis und ein Kubuntu-System als Ziel, dann ist unklar welche GTK- oder GNOME-Abhängigkeiten nachinstalliert werden müssen.
Aus diesem Grund wäre es gut, wenn man alle Pakete für eine Offline-Installation herunterladen kann, die man als Abhängigkeiten für ein zu installierendes Paket benötigt. Das klingt nach Rekursion und genau darin liegt die Lösung.
Hinweis: Die hier vorgestellte Lösung sollte auf allen Systemen funktionieren, die APT für die Paketverwaltung einsetzen (z. B. Debian, alle Ubuntu-Derivate etc.).
Zielsetzung
Das Ziel ist es also, ein kleines Bash-Skript zu schreiben, welches einem zu einer vordefinierten Paketliste A alle abhängigen Pakete rekursiv heraussucht. Rekursiv bedeutet, dass zuerst die normalen Abhängigkeiten der ersten Pakete A gesucht werden, die eine Paketliste B ergeben. Zu der Vereinigung von A und B sucht man dann wieder alle Abhängigkeiten C und so weiter. Das macht man solange, bis man keine neuen Abhängigkeiten dazu gewinnt.
Für die Umsetzung wird der Befehl apt-cache depends -i genutzt, der einem zu einer Liste von Paketen die wichtigen Abhängigkeiten auflistet, d. h. ohne empfohlene und vorgeschlagene Pakete.
Abhängigkeiten suchen
Die erste Version des Skriptes soll nur die Abhängigkeiten der, in einer Datei packages durch Zeilenumbrüche getrennte, übergebenen Pakete filtern. Der Befehl dazu lautet:
$ xargs -a packages apt-cache depends -i | nawk '{ print $NF }' | sort | uniq
|
xargs liest dabei die Argumente aus der Datei packages aus und übergibt diese an apt-cache. Da bei diesem Aufruf aber Zeilen wie
texlive-common |
entstehen, filtert man mit nawk noch das letzte Wort jeder Zeile über $NF heraus (siehe „Keine Angst vor awk“, freiesMagazin 07/2009 [1]). Zum Schluss wird die Liste noch sortiert (sort) und doppelte Einträge entfernt (uniq
).
Im Skript steht aber noch mehr: am Anfang gibt es Abfragen, ob die Anzahl der Argumente stimmt oder die übergebene Paketdatei überhaupt existiert. Darüber hinaus werden noch ein paar Variablen deklariert, die später etwas sinnvoller genutzt werden.
#!/bin/bash |
Listing: apt-depends-pre.sh
Nachdem man die Datei per
$ chmod +x apt-depends-pre.sh |
ausführbar gemacht hat (dies wird in Zukunft nicht mehr extra erwähnt), kann man die Abhängigkeitsliste per
$ ./apt-depends-pre.sh packages |
erstellen. Das Ergebnis ist in der Datei packages-all zu finden.
Rekursion einbauen
Das Skript muss nun noch so erweitert werden, dass es rekursiv arbeitet.
Die Idee wurde oben bereits beschrieben. Realisiert wird das Ganze, indem man die Anzahl der Gesamtpakete nach jeder Abhängigkeitenbestimmung auswertet und mit dem vorherigen Schritt vergleicht. Ist sie identisch, sind keine neuen Pakete dazu gekommen und das Skript kann beendet werden.
Zusätzlich muss man auf einige Ausgaben von apt-cache aufpassen. Es gibt beispielsweise Ausgaben wie
x11-common |
Hier arbeitet apt-cache leider etwas missverständlich. Obige Ausgabe bedeutet, dass das Paket x11-common von debconf oder (|) von <debconf-2.0> abhängt. Das heißt, das Oder steht immer vor dem Paket, welches „verodert“ wird. <debconf-2.0> selbst ist dabei kein echtes, sondern ein virtuelles Paket. Das bedeutet, dieses Paket existiert nicht wirklich, sondern stellt nur zwei weitere Pakete cdebconf und debconf zur Verfügung.
Da die Veroderung (vorerst) zu kompliziert zu filtern ist, macht man es sich einfach und entfernt einfach nur die virtuellen Pakete mittels egrep -v "<.*>" aus der Liste (die Option -v negiert den Suchparameter und findet somit alles, außer dem Suchbegriff).
#!/bin/bash |
Listing: apt-depends.sh
Weitere Erläuterungen zum Skript
Die einzelnen Zeilen des Skripts sollen noch kurz erläutert werden.
Für den Vergleich, ob man keine weiteren neuen Pakete gefunden hat, wird in jeder Iteration die Anzahl der Pakete (= Anzahl Zeilen) mit dem Befehl wc -l gesucht. Da dieser aber immer auch noch den Dateinamen ausgibt, benötigt man nur die erste Ausgabe, welche wieder mit nawk gefiltert wird:
NUMBER=`wc -l "$ORIGIN" | nawk '{ print $1 }'`
|
Die Iteration findet dann über eine while-Schleife statt und vergleicht die alte Anzahl der Pakete mit der neuen:
while [ $OLDNUMBER != $NUMBER ] |
Damit die temporären Dateien einen eindeutigen Namen bei jeder Iteration bekommen, wird ein Zähler (COUNTER) eingeführt, der bei jedem Schritt hochgezählt und in den neuen Dateinamen integriert wird:
COUNTER=$(( $COUNTER + 1 )) |
Zur Sicherheit werden bei jeder Iteration neben den Abhängigkeiten auch die alten Pakete, die als Basis für die Abhängigkeitssuche benutzt wurden, in das Endergebnis geschrieben:
cat "$LAST" >> "$TEMPFILE" | exit 1 |
Das - exit 1 bedeutet im Übrigen, dass im Fehlerfall das Skript mit einem Fehlerstatus 1 verlassen wird. Im Erfolgsfall ist der Rückgabewert 0 (exit 0 in der letzten Zeile).
Herunterladen der gefilterten Pakete
Ruft man das Skript auf, erhält man eine Ausgabe ähnlich der folgenden:
$ ./apt-depends.sh packages |
Die letzte Zeile gibt bereits an, wie man mittels apt-get die gefilterten Pakete herunterladen kann (durch die Option -d wird nichts installiert). Die Option --reinstall sorgt dafür, dass ein Paket wirklich neu heruntergeladen wird, auch wenn es bereits im System installiert war.
Nach der Ausführung der Zeile (mit Root-Rechten) findet man alle heruntergeladenen Pakete im Verzeichnis /var/cache/apt/archives:
# xargs -a "packages-all" apt-get install --reinstall -d |
Hinweis: Das Verzeichnis sollte man vor der Ausführung mittels apt-get clean komplett leeren, damit man nur die Pakete am Ende dort liegen hat, die man wirklich benötigt.
Lokale Paketquelle erstellen
Die Pakete kann man nach dem Herunterladen auf einen USB-Stick kopieren, zum Zielrechner tragen und dort mittels
# dpkg -i *.deb |
installieren.
Etwas praktikabler ist es aber vielleicht, wenn man eine lokale Paketquelle eröffnet.
Dazu führt man im Verzeichnis /var/cache/apt/archives/ folgenden Befehl aus:
# dpkg-scanpackages ./ /dev/null | gzip > Packages.gz |
Dies erstellt eine Paketliste der vorhandenen Pakete und packt diese in eine neue Datei Packages.gz
.
Diese Datei kopiert man dann zusammen mit den Paketen auf den USB-Stick - z. B. in ein Unterverzeichnis karmic, wenn man diese Ubuntu-Version nutzt.
Auf dem Zielrechner fügt man danach in der Paketverwaltung (Synaptic etc.) oder direkt in der Datei /etc/apt/sources.list die folgende neue Paketquelle ein:
deb file:/media/USB-STICK/karmic ./ |
Dabei ist USB-STICK natürlich der Einhängepunkt des USB-Sticks.
Nach dem Neueinlesen der Paketquellen, z. B. über
# apt-get update |
stehen die neuen Pakete vom USB-Stick in der Paketverwaltung zur Verfügung und man kann ganz normal die Pakete installieren, die man ganz am Anfang in die Datei packages geschrieben hatte. Die Abhängigkeiten werden automatisch aufgelöst.
Achtung: Man sollte natürlich nicht verschiedene Ubuntu-Versionen mischen. Pakete von Ubuntu 9.04 „Jaunty Jackalope“ haben nichts auf Ubuntu 9.10 „Karmic Koala“ verloren und umgekehrt.
Schlussbemerkung
Noch ein wichtiger Hinweis zum Schluss: Das hier vorgestellte Skript arbeitet fern von perfekt und erkennt viele Dinge nicht. So wird weder auf Veroderungen noch auf Konflikte eingegangen.
Dies kann in manchen Fällen dazu führen, dass die Installation bzw. das Herunterladen der gefilterten Pakete nicht ohne Weiteres möglich ist:
Die folgenden Pakete haben nicht erfuellte Abhaengigkeiten: |
Hier muss man die Paketliste packages-all am Ende noch einmal manuell editieren, bevor man die Pakete installieren kann, da im obigen Beispiel die beiden Pakete debconf-english und debconf-i18n in Konflikt stehen und nur eines der beiden installiert werden darf.
Eine Verbesserung des Skriptes bleibt dem geneigtem Leser zur Übung überlassen - oder ist vielleicht irgendwann Teil eines neuen Artikels.
apt-cache depends hat im Übrigen bereits einen Parameter --recurse, dessen Verwendung diesem Artikel aber jede Grundlage entzogen hätte. ;)
$ xargs -a packages apt-cache depends -i --recurse | \ |
Die Paketanzahl unterscheidet sich auch von der des Skriptes und es ist unklar, wieso einige Abhängigkeiten bei der apt-cache-eigenen Methode nicht gelistet werden.
Links
http://www.freiesmagazin.de/freiesMagazin-2009-06
Autoreninformation:
Dominik Wagenführ besitzt auch Rechner ohne Netzwerkanschluss, wozu so ein kleines Skript ganz hilfreich ist, wenn man Pakete installieren will, die nicht auf der Installations-CD vorliegen. Zum Großteil entstand das Skript aber aus Spaß an der Freud.
| < Zurück | Weiter > |
|---|





