Archiv des Autors: romeofox

print_r für Perl

Ich nutze unter PHP recht oft die Funktion print_r für’s Debugging. Diese Funktion gibt eine beliebige Variable strukturiert aus. Sinnvoll ist das natürlich am meisten bei Arrays und Objekten.

Für Perl gibt es kein print_r, dafür aber Data::Dumper (CPAN).

Ein kleines Beispiel:

use Data::Dumper;
my @arr = qw(foo bar foobar);
my %hash = (
             foo => 'bar',
             bar => @arr
           );
print Dumper(@arr, \%hash);

Das ergibt:

$VAR1 = [
          'foo',
          'bar',
          'foobar'
        ];
$VAR2 = {
          'bar' => $VAR1,
          'foo' => 'bar'
        };

Steht Data::Dumper mal nicht zur Verfügung geht es zur Not auch ohne.

Ein einfaches Array kann man sich so ansehen:

print "@array";

Mit dem Array aus den Beispiel von oben ergibt das:

foo bar foobar

Bei Hashs funktioniert das aber nicht. Hier geht es aber so:

my %hash =  (
              A => 1,
              B => 2,
              C => 3,
            );

{
  local $, = ' ';
  print %hash, $/;
}

Die Ausgabe sieht so aus:

A 1 C 3 B 2

Zugegeben, Data::Dumper ist da optisch besser, aber in der Not frisst der Teufel Fliegen…

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
" 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.

Script-Fehler beim IE6 finden

Meldet der IE6 einen Javascript-Fehler, der sich im Firefox nicht nachvollziehen lässt, so wird es schwierig. Der IE6 meldet zwar, er hätte in der Datei xy.php in Zeile x bei Zeichen y einen Fehler gefunden, aber diese Angaben sind irreführend. Oder doch nicht? Wie man dem Fehler auf die Spur kommen kann zeige ich hier.

Beim Aufrufen einer Seite wird zum Beispiel die folgende Fehlermeldung angezeigt:

Screenshot der 1. Fehlermeldung

Im Prinzip ist die Angabe von Zeile und Spalte gar nicht so verkehrt, nur die Datei-Angabe stimmt meist überhaupt nicht, da eine HTML-Seite zum einen oft aus verschiedenen Teilen per PHP zusammengesetzt wird und zum anderen der Javascript-Code häufig in eigene Dateien ausgelagert ist.

Um nun herauszubekommen, in welcher Datei der Teufel im Detail steckt, muss man den Debug-Modus aktivieren und die Anzeige der Script-Fehler ausschalten. Dies geschieht in Extras » Internetoptionen » Erweitert. Dort werden, wie unten gezeigt, die Häkchen bei »Skriptdebugging deaktivieren« und bei »Skriptfehler anzeigen« entfernt. Danach muss der IE neu gestartet werden, damit die Änderungen wirksam werden.

Screenshot der Internetopionen des IE6

Wird nun die betreffende Seite erneut aufgerufen, sieht die Fehlermeldung etwas anders aus:

Screenshot der 2. Fehlermeldung

Hier ist nun auch erstaunlicher Weise von Zeile 14 die Rede! Klick man nun auf »Ja«, wird der IE-Debugger gestartet und die Datei geladen und angezeigt, in der der Internet-Explorer den Fehler gefunden hat. Der Cursor wird allerdings an die Position gesetzt, die in der ersten Fehlermeldung angezeigt wurde: Zeile 15, Spalte 22.

Ausschnitt der Debug-Anzeige

Wenn wir uns aber an die Angabe aus der zweiten Fehlermeldung (Zeile 14) erinnern und unser Augenmerk auf die Zeile über dem Cursor richten, sehen wir dort in Spalte 22 einen zweiten Punkt, der dort nicht hingehört. Dies ist der Fehler!

Das Verfahren ist etwas umständlich, aber immerhin zielführend. Und wenn man sich erst einmal daran gewöhnt bzw. damit abgefunden hat, ist es recht nützlich.

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 ‚
‚ (Standard ist ein “
„):

#!/bin/bash
Newline=$'
'
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

SSH-Key auf einen anderen Rechner übertragen

SSH ist eine feine Sache, um sicher auf einen anderen Rechner zu zugreifen. Häufig wird jedoch die Passwortabfrage genutzt, die aber nicht so sicher und komfortabel wie Key-Methode.

Damit man diese aber nutzen kann, muss der Public-Key auf dem Zielrechner in die Datei authorized_keys eingetragen werden. Aber auch das lässt sich einfach und komfortabel per SSH machen:

cat ~/.ssh/id_dsa.pub | ssh user@zielrechner \n  "cd ~/.ssh && cat >> authorized_keys"

Nun wird man beim nächsten Login nicht mehr nach dem Passwort des Users auf dem Zielrechner gefragt, sondern nach der sog. Passphrase, also dem Passwort, mit dem der private Teil des oben übertragenen öffentlichen Schlüssels verschlüsselt ist.

um die Sicherheit weiter zu erhöhen, kann man nun den Zugang per Passwortabfrage verbieten, indem man in der sshd_config PasswordAuthentication no einträgt. Man sollte dann aber auch UsePAM no setzen, sonst hat PasswordAuthentication kein Effekt.

Sonderzeichen

copy_paste_character

Im Blog Webwork-Tools bin ich heute auf eine kleine aber feine Seite gestoßen: CopyPasteCharacter.com. Hier findet man die gebräuchlichsten Sonderzeichen, Ready for Copy & Paste. Man braucht die Zeichen auch gar nicht mit der Maus markieren – einfach draufgeklickt und schon ist das Zeichen in der Zwischenablage. Nie mehr umständliches Suchen „Sonderzeichen einfügen…“. So genial einfach, da hätte man eigentlich selbst drauf kommen können!

Außerdem gibt es noch HTML Entity Character Lookup. Hier gibt man einfach ein dem gesuchten ähnliches Zeichen ein und erhält eine Auswahl an Zeichen inkl. der Umschreibung als HTML-Entity. Und sowas braucht der Webworker schließlich fast täglich. Auch hier gibt es die Copy-to-Clipboard Funktion.