Schlagwort-Archive: Linux

Dateien eines Jahres archivieren

Heute stand ich (mal wieder) vor der Aufgabe, in einem Verzeichnis alle Dateien vom Vorjahr zu archivieren.

Im Artikel „Dateien an Hand des Datums und der Uhrzeit finden“ habe ich gezeigt, wie man mit find Dateien eines bestimmten Zeitraums finden kann. Da ich aber für die Archivierung keine Rekursion benötige, kann ich das Ganze etwas einfacher mit einem Einzeiler machen:

stat -c '%n Date:%y' * | perl -n -e 'print "$1
" if /^(.+)sDate:2008/' \n | zip archiv_2008.zip -@

Ich verwende hier stat, um das Ausgabeformat bestimmen zu können. Da die Dateinamen Leerzeichen enthalten können, kann ich keine Leerzeichen als Trenner zwischen Name und Datum verwenden, daher die Kennzeichnung mittels „Date:“. Perl filtert mir erstens alle Dateien von 2008 heraus und gibt auch nur den Dateinamen aus. Zip liest diese von der Standardeingabe, wenn die Dateiliste als -@ angegeben ist.

Soll Zip die Dateien auch gleich entsorgen, so kann man das über die Option -m erreichen.

Nur einen bestimmten Zeitabschnitt einer Logdateien ausgeben

In einer Logdatei lässt sich ja prima mit grep nach einer Zeichenkette suchen. Was aber, wenn man nur einen bestimmten Zeitraum der Logdatei durchsuchen möchte?

Nun, da hilft uns mal wieder das Universalwerkzeug Perl mit seinem sog. Bereichsoperator (..). Wird dieser nämlich im skalaren Kontext verwendet, liefert er uns solange falsch, solange der linke Ausdruck falsch ist. Sobald der linke Ausdruck (einmal) wahr wird, liefert der Bereichsoperator solange wahr zurück, bis der rechte Ausdruck wahr wird. Er ist also so etwas wie ein bistabiler Schalter mit einer Einschalt- und einer Ausschaltbedingung (der Elektroniker nennt so etwas ein Flip-Flop). Genug der Theorie, hier die praktische Anwendung für unseren Fall:

perl -n -e 'print $_ if /^Aug 18/ .. /^Aug 31/' /var/log/messages

Dies liefert uns alle Einträge vom 18. August bis zum 31. August (oder bis zum Ende der Datei, wenn heute erst der 28. August ist).

Mit einem nachgeschalteten grep kann man nun prima in diesem Abschnitt suchen. Hier ein Beispiel, welches alle anfragende Rechner für den o.g. Zeitraum aus der Logdatei des DNS-Servers ermittelt:

perl -n -e 'print $_ if /^Aug 18/ .. /^Aug 31/' named.queries | sed -n 's/^.* client ([^#]*)#.*$/1/p' | sort | uniq

Alle Fehlermeldungen in einem Script umleiten

Man kann in einem Shell-Script ja Ausgaben, die an den Standard-Fehler-Kanal STDERR gehen, mit 2>>dateiname.log in eine Datei umleiten. Dies muss aber für jeden Befehl einzeln gemacht werden. Es gibt aber bei der Bash eine Methode, die Umleitung für das gesamte Script zu machen.

Dazu nutzt man eine Spezialform des Befehls exec: Werden als Parameter nur Umleitungen angegeben, so leitet die Shell die gewünschten Kanäle permanent um.

#!/bin/bash
exec 2>> dateiname.log

echo "Normale Ausgabe"
echo "An stderr" >&2
echo "das folgende macht einen Fehler"
TEST=`gibtsnicht  `
echo "Das geht auch in Pipes:"
LINES=`lsx -1 $TREE | egrepx -v '^.+$' | wc -l`

Startet man das Script, so erhält man auf der Konsole diese Ausgabe:

Normale Ausgabe
das folgende macht einen Fehler
Das geht auch in Pipes:

Das sind die Ausgaben auf der Standard-Ausgabe.

Die Log-Datei enthält die Ausgabe der Fehlermeldungen:

An stderr
./logtest: line 7: gibtsnicht: command not found
./logtest: line 9: lsx: command not found
./logtest: line 9: egrepx: command not found

Sollen alle Ausgaben in die Logdatei umgeleitet werden, so verwendet man

exec >> dateiname.log 2>&1

Das Quartal mit date berechnen

Das Unix-Kommando date kennt viele Format-Parameter aber leider keinen, der uns das Quartal ausgibt. Zum Glück lässt sich das aber mit Hilfe von expr berechnen.

Wir können mit dem date-Befehl einen Ausdruck erstellen, den expr dann für uns ausrechnet. Wir bekommen das das aktuelle Quartal oder, wenn wir mit -d ein anders angeben, das des gefragten Datums:

expr `date "+( %m - 1 ) / 3 + 1"`

expr `date -d "2007-05-01" "+( %m - 1 ) / 3 + 1"`

Dateien an Hand des Datums und der Uhrzeit finden

Manchmal muss man eine Datei finden, von der man nur weiß, wann sie zuletzt geändert wurde. Weiß man, dass das vor 3 Tagen war, hilft einem die Option -mtime +3 des Befehls find. Hat man aber ein Datum und eine Uhrzeit, muss man ein wenig tricksen.

Man kann hier die Option -newer zusammen mit einer Referenzdatei verwenden. Diese Referenzdatei kann mit touch erstellt werden. Da wir einen bestimmten Zeitpunkt suchen, brauchen wir noch eine zweite Referenzdatei, da -newer sonst alle neueren Dateien ausgibt.

$ touch -t 11142104 /tmp/tmp_start
$ touch -t 11142105 /tmp/tmp_end
$ find /opt -newer /tmp/tmp_start ! -newer /tmp/tmp_end

Dies findet Dateien im Verzeichnis /opt und darunter, die zuletzt am 14.November um 21:04 Uhr modifiziert wurden.

Dieses Beispiel funktioniert unter Linux, aber auch unter HP-UX, dessen Befehle find und touch leider nicht so viele Optionen kennen wie die Linux-Versionen

Zwei verschiedene MySQL-Server auf einem Rechner

Wie bringt man einen 4.1-Server und einen 5.0-Server auf einem Linux-Rechner gleichzeitig ans laufen? Diese Frage stellte sich mir kürzlich, da ich verschiedene Intra- und Extra-Websites betreue und hier gibts bei einer nun auch Mysql 5.0. Die anderen laufen aber noch mit der ‚alten‘ Version 4.1

Nach einiger Recherche fand ich zwar eine Anleitung von Kristian Köhntopp, wie man viele verschiedenen MySQL Instanzen auf einem Server recht komfortabel zum Laufen bringt, aber ich wollte nur eine zusätzliche Instanz und hatte auch keine Lust, den Server selbst zu kompilieren (was von MySQL AB auch nicht wirklich empfohlen wird). So suchte ich nach einer Lösung, das Ganze mit den von MySQL bereitgestellten RPMs zu machen.

Zzt. ist auf meinem Linux-Rechner die Version 4.1 installiert (von den SuSE-10.0 CDs) und läuft.

Zwei Dinge mussten bewerkstelligt werden:

  1. Die Installation des zweiten Servers muss in andere Verzeichnisse erfolgen, als im RPM-Paket vorgesehen.
  2. Der 5.0er Server muss einen anderen TCP-Port und einen anderen Socket benutzen. Dazu muss eine eigene my.cnf benutzt werden.

Punkt 1 kann mit der relocate-Funktion von RPM bewerkstelligt werden. Zusätzlich mussten noch ein paar Dateien gepatcht werden, damit der Server auch die neuen Verzeichnisse findet. Doch dazu später mehr.

Die Sachen mit den Config-Dateien ist relativ einfach, der Server (und auch der Client) sucht sie entweder in /etc, im sog. Datadir (normalerweise /var/lib/mysql) oder als .my.cnf im Home-Verzeichnis des Users. Wenn also in /etc nichts liegt aber im jeweiligen Datadir (was ja unbedingt unterschiedlich sein muss), findet jeder Server automatisch die richtige Config-Datei. Na ja, so ganz automatisch nicht, dazu muss ein wenig an den Start-Scripten geschraubt werden. Doch zunächst mal zur

Installation.

Man besorge sich die aktuellen RPMs bei www.mysql.com, bei mir waren es

  • MySQL-server-5.0.21-0.glibc23.i386.rpm
  • MySQL-Max-5.0.21-0.glibc23.i386.rpm
  • MySQL-client-5.0.21-0.glibc23.i386.rpm

Wer die speziellen Funktionalitäten des Max-Servers nicht braucht lässt ihn weg. Ebenso kann auch auf das Client-Paket verzichtet werden, aber wo man gerade schon mal dabei ist …

Als Nächstes sollte man die bestehenden Datenbanken sichern, man weiß ja nie 😎

Dazu wird erstmal der laufenden Server gestoppt:

rcmysql stop

Danach die Datenbanken sichern:

cd /var/lib
cp -prv mysql mysql41

Die Config-Datei und das Start-Script sollten auch vorsichtshalber in Sicherheit gebracht werden:

cp -pv /etc/my.cnf /etc/my41.cnf
cp -pv /etc/init.d/mysql /etc/init.d/mysql41

Nun die Verzeichnisse anlegen:

mkdir -p /usr/local/mysql50 /var/lib/mysql50

Wir sehen, das Installationsverzeichnis soll /usr/local/mysql50 sein und das Datadir, in dem später u.a. die Datenbank-Dateien liegen, soll /var/lib/mysql50 sein.

Entsprechend werden die Installationspfade per –relocate=<alt>=<neu> verbogen (siehe man rpm).
Mit –test macht man erstmal ein Testlauf:

rpm -vv --test
  --relocate=/usr/share/mysql/=/usr/local/mysql50/share/mysql/
  --relocate=/usr/share/man/=/usr/local/mysql50/man/
  --relocate=/usr/share/info/=/usr/local/mysql50/info/
  --relocate=/usr/sbin/=/usr/local/mysql50/sbin/
  --relocate=/usr/lib/mysql/=/usr/local/mysql50/lib/
  --relocate=/usr/bin/=/usr/local/mysql50/bin/
  --relocate=/etc/=/usr/local/mysql50/etc/
    -ih MySQL-server-5.0.21-0.glibc23.i386.rpm

Wenn das soweit gut aussieht kann man den Test-Schalter weglassen und installieren.
Nach dem Server kann man nun auf die gleiche Art das Max- und das Client-Paket installieren, so man will.

Konfiguration und Einstellung

Nun sollte man eine Konfigdatei erstellen und nach /var/lib/mysql50 legen. Wichtig ist, dass sich die Angaben für Port und Socket von denen der 4.1er Konfigdatei unterscheiden.
Desweiteren ganz wichtig: in der my.cnf für den 5.0er Server muss die folgenden Zeile beim Abschnitt [mysqld] rein:

language = /usr/local/mysql50/share/mysql/english

Sonst sucht der Server diese Datei in /usr/share/mysql/english/errmsg.sys und findet logischerweise die der 4.1er-Version, was nicht passt (im error-log steht dann: „Error message file ‚/usr/share/mysql/english/errmsg.sys‘ had only 304 error messages, but it should contain at least 463 error messages.“)
Meine Konfig-Datei sieht so aus: my.cnf

Die alte my.cnf aus /etc wird nach /var/lib/mysql verschoben, damit jeder Server seine eigene Datei benutzt. In /etc darf keine mehr sein!

Nun das Startscript ins richtige Verzeichnis kopieren und umbenennen:

cp /usr/local/mysql50/etc/init.d/mysql /etc/init.d/mysql50

In dieser Datei müssen basedir und datadir entspr. gesetzt werden:
basedir=/usr/local/mysql50
datadir=/var/lib/mysql50

Dann muss ein wenig gebastelt werden, denn das Hilfs-Startscript mysqld-safe in /usr/local/mysql50/bin interessiert sich nicht richtig für die vom eigentlichen Startscript übergebenen Werte sondern versucht selbst basedir und datadir zu ermitteln und findet, da dort die falschen Vorgaben drin stehen, dabei die falschen Pfade. Dadurch wird schlussendlich die falsche my.cnf gefunden und somit die falschen Vorgaben verwendet (bei SuSEs mysqld-safe sind andere defaults eingebaut). Ich habe das korrigiert:

116c116
< if test -f ./share/mysql/english/errmsg.sys -a -x ./bin/mysqld
---
> if test -f ./share/mysql/english/errmsg.sys -a -x ./sbin/mysqld
119c119
<   ledir=$MY_BASEDIR_VERSION/bin               # Where mysqld is
---
>   ledir=$MY_BASEDIR_VERSION/sbin      # Where mysqld is
150c150
<   DATADIR=/var/lib/mysql
---
>   DATADIR=/var/lib/mysql50

Zum Einspielen des Patches benennt man mysqld_safe in mysqld_safe-orig um und verwendet das Programm patch zum editieren:

patch -o mysqld_safe mysqld_safe-orig mysqld_safe.patch

… 3 – 2 – 1 – Start

Nun sollte sich der Server mit

/etc/init.d/mysql50 start

zum Leben erwecken lassen. Mit netstat -ltn kann man prüfen, ob der Server auf dem neuen Port lauscht.

Dem Client muss man nun natürlich sagen, mit welchem Server er in Verbindung treten soll:

mysql -S /var/lib/mysql50/mysql.sock -u root -p

Dabei wird der alte Client der SuSE-Installation verwendet. Um den neuen Client zu verwenden, muss man ihn mit vollen Pfad ansprechen:

/usr/local/mysql50/bin/mysql -S /var/lib/mysql50/mysql.sock -u root -p

Klappt natürlich nur, wenn man ihn wie oben angegeben installiert hat.

Man kann das Ganze aber mit Hilfe der entspr. cnf-Datei machen:

/usr/local/mysql50/bin/mysql --defaults-file=/var/lib/mysql50/my.cnf -u root -p mysql

Bei PHP muss man entweder in der php.ini den entsprechenden Socket angeben oder die PHP-Variable mysql.default_socket bzw. mysql.default_port in der Apache-Konfig oder per htaccess definieren. Natürlich kann man auch den Port oder Socket beim Aufruf von mysql_connect() an dem Hostnamen anhängen. Man sollte bei der Verwendung von Port aber beachten, das man „127.0.0.1“ statt „localhost“ benutzt, mysql_connect zieht sonst u.U. den (falschen) Socket vor. Ich empfehle die Lektüre der entsprechenden PHP-Doku.

So, und nun wünsche ich viel Spaß beim nachbasteln…

Verzeichnisse auf einen anderen Rechner übertragen

Im vorigen Artikel habe ich gezeigt, wie man mit TAR ein komplettes Verzeichnis so verschieben bzw. kopieren kann, dass alle Rechte erhalten bleiben. Hier kommt nun die erweiterte Fassung zum kopieren von Verzeichnissen auf einen anderen Rechner mittels SSH und TAR. Und als Bonbon gibt’s ein Script zum Erstellen von Backups von anderen Rechnern.

Um das Verzeichnis foo vom eigenen Rechner auf dem Rechner kermit als Unterverzeichnis von /bar zu speichern benutzt man den folgenden Befehl:

tar cpSf - foo | ssh kermit "cd /bar && tar xpSvf -"

Wie man sieht, ähnelt der Aufruf sehr dem Befehl zum kopieren auf dem lokalen Rechner, lediglich das „ssh kermit“ ist neu (und die Klammern brauchen wir hier nicht). Ich gehe hier davon aus, dass der User des lokalen Rechners auch auf dem Rechner kermit bekannt ist. Soll auf kermit ein anderer Account, z.B. klaus, verwendet werden, schreibt man einfach „ssh klaus@kermit“. Der User muss auf jeden Fall ausreichende Rechte im Zielverzeichnis haben. Da ein Ziel ist, die Rechte beizubehalten, sollte das root sein.

Man kann tar die Daten auch komprimieren lassen und bei geringer Bandbreite das Ganze somit beschleunigen:

tar zcpSf - foo | ssh kermit "cd /bar && tar zxpSvf -"

Man kann das Ganze auch umkehren, also Verzeichnisse des entfernten Rechners auf den lokalen kopieren:

ssh kermit tar cSpf - /foo | (cd /bar && tar xpSvf -)

kopiert das Verzeichnis /foo von kermit auf dem lokalen Rechner ins Verzeichnis /bar.

Man kann dem ersten tar-Befehl übrigens auch mehrere Verzeichnisse übergeben. Das eignet sich hervoragend für ein einfaches Backup, insbesondere zusammen mit gzip:

ssh kermit tar cSpf - /foo /bar | gzip -f9 > kermit_backup.tgz

Das sichert die Verzeichnisse /foo und /bar des Rechners kermit als komprimiertes Tar-Archiv in der Datei kermit_backup.tgz auf dem Lokalen Rechner.

Ich mache so täglich Sicherungen von wichtigen Verzeichnissen meiner Server.

Auch hier kann man tar die Komprimierung übernehmen lassen:

ssh kermit tar zcSpf - /foo /bar > kermit_backup.tgz

Diese Variante ist schneller, die andere komprimiert dafür geringfügig besser.

Komplette Verzeichnisbäume verschieben

Ich musste mal wieder ein Verzeichnis von der einen Partition auf eine andere schieben um Platz zu schaffen. Wichtig dabei ist, dass die Rechte der Dateien und Verzeichnisse nicht verändert und Sym-Links korrekt kopiert werden. Dazu kann man sehr gut den tar-Befehl nutzen.

Nehmen wir mal an, wir wollen das komplette Verzeichnis foobar unterhalb von /var nach /usr kopieren, so dass es hinterher als /usr/foobar zu finden ist.
Man wechselt dazu als root in das übergeordnete Verzeichnis der Quelle (hier also nach /var) und gibt den folgenden Befehl ein:

tar cSpf - foobar | ( cd /usr && tar xSpvf - )

Wie man sieht, wird hinter den „cd“ das Zielverzeichnis angegeben.
Der Linke Teil packt das Verzeichnis foobar in einen tar-Stream und gibt ihn auf der Standardausgabe aus (dafür sorgt das – hinter dem f ). Die Parameter S und p sorgen dafür, dass die Rechte unverändert bleiben (p) und dass leere bzw. kleine Dateien effizient übertragen werden (S). Dieser Stream wird dann mit der Pipe (|) an den rechten Teil weitergereicht. Hier wird dann erstmal in das Zielverzeichnis gewechselt und der über die Standardeingabe (wieder f – ) empfangene Stream dort ausgepackt.

Anschließend kann das Quelleverzeichnis gelöscht werden.

Wie das ganze auch über Rechnergrenzen mittels ssh funktioniert zeige ich in einem weiteren Artikel.