Archiv der Kategorie: Linux

Dateien mit Sym- oder Hardlinks finden

Symlinks finden

Das Verlinken von Dateien oder Verzeichnissen per Symlink ist eine prima Sache. Schwierig wird es nur, wenn man Dateien löscht oder verschiebt, auf die Symlinks zeigen. Diese laufen dann nämlich ins Leere. Um zu prüfen, ob es Symlinks auf eine Datei oder ein Verzeichnis gibt, nutz man das folgende Kommando:

find / -type l -print | xargs ls -ld | grep 'dateiname'

Das kann ich auch verwenden, wenn ich z.B. ein Verzeichnis verschieben will und nicht weiß, ob es Links auf Dateien oder Unterverzeichnisse im fraglichen Verzeichnis gibt. Ich setze dann als ‚dateiname‘ den Namen des Verzeichnisses ein, das ich verschieben will.

Siehe auch http://www.grymoire.com/Unix/Find.html#uh-6

Hardlinks finden

Mitunter benutzt man auch Hardlinks, um Dateien zu verlinken (z.B. Backup mit rsync, wie in der c’t 07/2009, S. 212 beschrieben). Hat man nun eine Datei, bei der man anhand des Linkcount erkennt, dass sie Hardlinks hat, will man evt. auch wissen, wo die anderen Links liegen. Mit

find . ! -type d -links +1 -ls|sort -n

findet man alle Dateien, deren Linkcount größer 1 ist. Durch die Sortierung anhand des Inodes (erste Spalte) kann man gut zusammengehörende Dateien erkennen.

Will man für eine Bestimmte Datei die zugehörigen Links finden, geht das auch so:

f=`ls -i dateiname |awk '{print $1}'`
find / -inum $f

Siehe auch: The UNIX and Linux Forums: search for hardlinks based on filename via find command.

Videos der ZDF-Mediathek mit Linux speichern

Dieser Tage lief wieder ein sehr interessanter Beitrag im ZDF, den ich leider aus zeitlichen Gründen nicht sehen konnte. Zum Glück konnte ich ihn zwei Tage später in der ZDF-Mediathek finden. Aber leider ruckelte der Stream sehr stark, so dass ich ihn so nicht sehen wollte. Nach einiger Recherche im Netz und ein paar nicht so erfolgreichen Versuchen, den Stream mit dem VLC zu speichern, fand ich meine Lösung in einem Blog-Artikel von Stefan Waidele.

Hier die Kurzfassung:

  1. Beitrag in der Mediathek finden.
  2. Oben rechts auf die HTML-Version wechseln und dann den 2000er Mediaplayer-Link kopieren.
  3. Den kopierten Link in den folgenden Einzeiler einbauen, warten, fertig!
curl VIDEO-URL | grep mms: | cut -d \" -f 2 | xargs -n1 mplayer -dumpstream -dumpfile DATEINAME.wmv

Zum Betrachten muss man nicht zwangsläufig den MPlayer benutzen, das geht dann auch wieder mit dem VLC.

Sicherlich kann man zum Aufzeichnen auch den VLC benutzen, aber ich hatte bislang weder die Zeit noch die Lust, mich mit den vielen und verwirrenden Kommandozeilen-Optionen des VLC auseinander zu setzen. Aber vielleicht hat ja jemand einen Tipp.

set -x Ausgaben nur in eine Datei schreiben

Der Schalter set -x (auch xtrace genannt) ist ein hilfreiches Werkzeug zum Debuggen von Shellskripten. Aber manchmal sieht man auf Grund der Vielzahl an Meldungen den Wald von lauter Bäumen nicht. Und der normale Anwender sollte davon besser auch nichts zu sehen bekommen. Daher sollte man nach getaner Kammerjägerei den Schalter wieder aus dem Skript entfernen oder auskommentieren.

Was aber, wenn man die schönen Meldungen auch in der normalen Anwendung sammeln möchte, um z.B. einem von einem Anwender gemeldeten Fehler auf die Spur zu kommen. Mit ein paar zusätzlichen Zeilen kann man dies bewerkstelligen.

Hier erst mal das „normale“ Script:

#!/bin/sh

echo "Ich melde einen Fehler" >&2
echo "Eine ganz normale Meldung"

Es macht nichts wirklich sinnvolles, es gibt nur eine Meldung auf STDERR und eine auf STDOUT aus.

Nun die modifiziere Variante:

#!/bin/sh
{
  echo "+++++++++ `date "+%F %T"`: ${0##*/} $1 $2 $3 +++++++++"
  set -x
  echo "Fehler" >&2
  echo "normal"
  set +x
} 2>&1 | tee -a /tmp/${0##*/}.log | grep -v '^+'

Dieses Skript schreibt die komplette Ausgabe (mit den Debug-Zeilen von xtrace) in eine Logdatei mit gleichen Namen wie das Script selbst (mit angehängtem .log) in das Verzeichnis /tmp. Die xtrace-Zeilen werden für die normale Ausgabe über grep ausgefiltert.

Durch den Schalter -a bei tee wird die Ausgabe immer an die bestehende Logdatei angehängt. Dem trägt auch die echo-Zeile Rechnung, durch die zum Einen der Beginn eines neuen Laufs markiert wird und zum Anderen die zusätzlichen Parameter protokolliert werden.

Firefox 3.6 und Java auf SuSE 11.1

Ich habe Anfang dieser Woche auf meinem Laptop den Firefox auf die Version 3.6 gebracht. Abgesehen von einem Addon, das den Browser zum unregelmäßigen Absturz brachte (ich hab noch nicht raus, welches Addon es ist – zzt. sind die meisten deaktiviert) funktionierte auch Java nicht mehr mit dieser Version.

Nach stundenlangem Googlen und Ausprobieren hab ich es nun endlich wieder am Laufen! Die Lösung ist eigentlich ganz einfach: ich musste einen Symlink von /usr/lib64/jvm/java-1.6.0-sun-1.6.0/jre/lib/amd64/libnpjp2.so in mein Mozilla-Plugin-Verzeichnis ~/.mozilla/plugins anlegen und den Browser neu starten. Das Plugin-Verzeichnis musste ich aber erst noch erstellen.

Diesen entscheidenden Hinweis fand ich per Google auf der Seite „Camp Firefox • Thema anzeigen – FF 3.6 Java Plugin“ — allerdings ist diese Seite zzt. nicht erreichbar, so dass ich den Google-Cache bemühen musste.

Die o.g. Datei stammt aus dem Paket java-1_6_0-sun-plugin-1.6.0.u17-1.20. Dieses wiederum hatte ich in meiner Verzweiflung per One-Click-Install aus dem Non-OSS openSUSE Factory-Repository geholt, aber das Standard-Paket 1.6.0.u17-1.1.1 sollte m.E. auch gehen.

Da ich bei der ganzen Herumprobiererei auch die aller-aktuellste JRE-Version von Sun heruntergeladen und installiert hatte und mich die Plugin-Testseite auch darauf aufmerksam gemacht hatte, dass es eine neuere Version des Plugins geben soll, haben ich den Link noch mal auf eben jene aktuelle Version geändert:

libnpjp2.so -> /usr/java/latest/lib/amd64/libnpjp2.so

Das funktioniert auch un die Testseite ist nun vollständig zufrieden.

Eine Reihe von IP-Adressen per Ping prüfen

Mitunter hat man das Problem, eine nicht kleine Anzahl an Rechnern auf Erreichbarkeit zu überprüfen. Die Adressen und Hostnamen liegen als Excel-Liste vor, aber diese alle von Hand zu pingen … dazu bin ich echt zu faul. Also habe ich mir ein kleines Script gebaut, welches diese Aufgabe elegant erledigt:

#!/bin/sh
#
# Sendet einen Ping an alle in der übergebenen Datei aufgeführten Adressen.
# In der Liste muss pro Zeile zuerst die IP-Adresse und danach durch Leerzeichen
# getrennt der Hostname stehen (optional).
# Die Ausgabe von Ping kann unterdrückt werden, indem STDERR ins Null-Dev
# geschickt wird:
# ping_list liste.txt 2>/dev/null
#

#set -x

Newline='
'

IFS=$Newline
for LINE in `cat $1`
do
  #echo "'$LINE'"
  ADR=${LINE%% *}
  HOST=${LINE##* }
  #echo "Adr: $ADR"
  #echo "Host: $HOST"
  if [ "$OSTYPE" = "linux" ]
  then
    ping -q -W 3 -c 1 $ADR 1>&2
  elif [ "$OSTYPE" = "hpux11.00" ]
  then
    ping $ADR -n 1 -m 3 1>&2
  else
    echo "Unknown OS"
    exit 99
  fi
  if [ $? -eq 0 ]
  then
    echo "$ADR ($HOST) is up"
  else
    echo "$ADR ($HOST) FAILED"
  fi
done
IFS=

Es arbeitet zzt. unter Linux (SuSE 11.1) und HP-UX 11i und kann auch hier heruntergeladen werden:
ping_list

munin auf centOS 5.2 installieren

Ich war kürzlich in der Verlegenheit, ein System mit centOS 5.2 überwachen zu müssen. Ich habe mir als Tool dazu munin auserkoren, das sich an Hand der Anleitung „Überwachung eines CentOS 5.2 Servers mit munin und monit“ einfach installieren und in Betrieb nehmen lässt (auf monit habe ich verzichtet).

Allerdings bekam ich danach alle fünf Minuten eine E-Mail mit der Meldung:

Pango-WARNING **: Invalid UTF-8 string passed to pango_layout_set_text()

Zunächst habe ich alle relevanten Dateien in /etc/munin mittels iconv von ISO nach UTF8 konvertiert – was nichts genützt hat, da die Dateien bereits in UTF8 sind bzw. keine Umlaute etc. enthalten.

Eine weitere Recheche im Netz der Netze brachte mich auf ein Post im Gentoo-Forum, in dem ein Patch auf die Datei munin-graph erwähnt wird. Dieser Patch ist leider für die Munin-Version 1.3.4, auf meinem System ist aber die Stable-Version 1.2.5 installiert. Ich habe mir aber einmal den Patch angesehen und festgestellt, dass er recht einfach ist. Er baut  an sich nur eine Erkennung ein, ob die verwendete RRD eine Version größer oder gleich 1.3. Nur dann werden die an RRD übergebenen Parameter nach UTF8 gewandelt. Netterweise war der Patch im Context-Format erstellt worden, so dass es mir ein Leichtes war, die entsprechenden Code-Stellen auch in der Datei /usr/share/munin/munin-graph der Version 1.2.5 zu finden. Ich habe die beiden Code-Fragmente an den entsprechenden Stellen eingebaut – und es geht!

Hier nun der entsprechend angepasste Patch für die Munin-Version 1.2.5:

*** munin-graph.old     2009-03-25 01:28:10.000000000 +0100
--- munin-graph 2009-10-01 15:42:36.000000000 +0200
***************
*** 29,34 ****
--- 29,35 ----
  use Digest::MD5;
  use Getopt::Long;
  use Time::HiRes;
+ if ($RRDs::VERSION >= 1.3) { use Encode; }

  my $graph_time= Time::HiRes::time;
  my $DEBUG = 0;
***************
*** 832,837 ****
--- 833,848 ----
                    push @complete, "--end",(int($lastupdate/$resolutions{$time}))*$resolutions{$time};
            }
            print "

rrdtool "graph" "", join (""
	"",@complete), ""
" if $DEBUG;
+
+           # Since version 1.3 rrdtool uses libpango which needs its input
+           # as utf8 string. So we assume that every input is in latin1
+           # and decode it to perl's internal representation and then to utf8.
+           if ( $RRDs::VERSION >= 1.3 ) {
+               @complete = map {
+                   $_ = encode("utf8", (decode("latin1", $_)));
+               } @complete;
+           }
+
            RRDs::graph (@complete);
            if (my $ERROR = RRDs::error) {
                logger ("Unable to graph $filename: $ERROR");

Den Patch gibt es natürlich auch zum Download: munin-1.2.5_utf8.patch

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\n" if /^(.+)\sDate:2008/' \
 | 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

Leerzeichen bei For-In-Schleifen in der bash

Im Artikel Dateinamen mit Leerzeichen auf der Kommandozeile verarbeiten habe ich beschrieben, wie man Ärger mit Leerzeichen in Dateinamen umgeht. Heute geht es um Daten, die innerhalb einer For-In-Schleife aus einer Datei gelesen werden und die Leerzeichen enthalten.

Wenn wir eine Datei mit folgendem Inhalt haben:

klaus rechner1
martin rechner3
dieter rechner7

Und wir lassen diese von folgendem Script einlesen und wieder ausgeben (was natürlich recht sinnfrei ist, hier aber nur zur Veranschaulichung dienen soll):

#!/bin/bash
for ZEILE in `cat daten`
do
  echo $ZEILE
done

Dann kommt sowas raus:

klaus
rechner1
martin
rechner3
dieter
rechner7

Durch die Leerzeichen werden die Daten-Zeilen zerteilt. Damit das nicht passiert, setzt man den „Input Field Separator“ $IFS auf ‚\n‘ (Standard ist ein “ \n\t“):

#!/bin/bash
Newline=$'\n'
IFS=$Newline
for ZEILE in `cat daten`
do
echo $ZEILE
done
IFS=

Dann sieht das Ergebnis wie erwartet und gewünscht aus:

klaus rechner1
martin rechner3
dieter rechner7

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