Archive for the ‘Perl’ Category

h1

RSPerl in Ubuntu installieren

16. März 2010

Zusammenfassung

RSPerl erlaubt die Interaktion zwischen Perl und R. Mich hätte vor allem interessiert, R-Funktionen von Perl aus zu verwenden. Das geht irgendwie, dann aber doch auch nicht so super – kurz: Ich verwende es zur zur Zeit nicht, sondern erzeuge mit print FH eine Datei (ein R-Skript), dass ich dann direkt in R ausführe. Trotzdem im folgenden die Vorgangsweise …

Die Schritte

Die in Ubuntu Desktop 9.10 vorhandene R-Installation ist als shared-library kompiliert. Das ist schon einmal gut. Dann braucht man aber noch eine lib installieren:

$ sudo apt-get install libperl-dev

Ab dann lässt sich RSPerl wie dolumentiert installieren:

$ R CMD INSTALL  --configure-args='--with-in-perl' Desktop/RSPerl

(Voraussgesetzt man hat das tar zuvor auf dem Desktop ausgepackt).

http://www.omegahat.org/RSPerl/RFromPerl.html

Das heisst aber noch nicht, dass man R von Perl aus damit erreicht …

Nacharbeiten

$ ./hello_R.pl

Can’t load ‚/home/ubuntu/R/i486-pc-linux-gnu-library/2.9/RSPerl/perl/i486-linux-gnu-thread-multi/auto/R/R.so‘ for module R: libPerlConverter.so: cannot open shared object file: No such file or directory at /usr/lib/perl/5.10/DynaLoader.pm line 196.

at ./hello_R.pl line 7

Compilation failed in require at ./hello_R.pl line 7.

BEGIN failed–compilation aborted at ./hello_R.pl line 7.

Das lässt sich dann mit Umgebungsvariablen unterbinden:

$ export PERL_MODULES="`perl -s modules.pl -modules | sed s/modperl//`"

http://tolstoy.newcastle.edu.au/R/help/03b/7997.html

Letztendlich sollten diese mit export gesetzt sein:

PERL5LIB="/home/ubuntu/R/i486-pc-linux-gnu-library/2.9/RSPerl/perl"
PERL_MODULES="Gtk2 Locale::gettext Gnome2 Cairo LibAppArmor HTML::Parser Sub::Name Glib Compress::Bzip2 Pango UUID Net::DBus Term::ReadKey XML::Parser::Expat Text::CharWidth Text::Iconv IO PerlIO::scalar PerlIO::encoding PerlIO::via SDBM_File Data::Dumper Opcode Fcntl DB_File I18N::Langinfo POSIX IPC::SysV Sys::Syslog Sys::Hostname re Cwd GDBM_File threads ODBM_File attrs Compress::Raw::Zlib Time::HiRes Time::Piece Filter::Util::Call Hash::Util Math::BigInt::FastCalc MIME::Base64 NDBM_File Encode Digest::SHA Digest::MD5 Devel::PPPort Devel::DProf Devel::Peek List::Util File::Glob Storable Socket Unicode::Normalize Text::Soundex "
LD_LIBRARY_PATH="/home/ubuntu/R/i486-pc-linux-gnu-library/2.9/RSPerl/libs:/usr/lib/R/lib"

Doch auch mit diesen Tipps mag R sich nicht von Perl aus aufrufen lassen. Ich denke man muss da andere Wege beschreiten. Zum Beispiel R-Scripts über das system-Komando aufrufen …

$ R -f hello.r --slave; open Rplots.pdf

Unschön, aber es geht.

Wie es dann doch noch lief …

Ok, wenn man den Code der Module patched (da sind grauslige pod-Fehler drin) und auf die Verwendung des eigentlich empfohlenen use RReferences verzichtet, kann man R von Perl aus aufrufen. Sehr vertrauenserweckend ist das aber nicht – schade.

Hier noch das hello_R.pl:

#!/usr/bin/perl -w
use strict;
use warnings;
use lib '/home/ubuntu/R/i486-pc-linux-gnu-library/2.9/RSPerl/perl/i486-linux-gnu-thread-multi';
use R;
#use RReferences;
&R::initR('--silent');
my $x = R::sum((1,2,3));
print $x . "\n";
h1

Perl-Skript in Windows EXE umwandeln

4. März 2010

Voraussetzung ist eine Windows-Maschine – kann auch virtuell sein. Bei ist das ein XP gewesen.

Download und Installation von Strawberry Perl http://strawberryperl.com/, Datei http://strawberry-perl.googlecode.com/files/strawberry-perl-5.10.1.1.msi

In Start->Programme die CPAN-Shell öffnen

cpan> install Bundle::libwin32
cpan> install PAR::Packer

Tipp: Hier gab es einen Bug und einer der Tests meldet dass PAR::Filter::PodStrip nicht gefunden werden kann. Der Workaround dazu ist hier:

http://www.nntp.perl.org/group/perl.par/2009/12/msg4278.html

und besteht im Wesentlichen darin, ein Verzeichnis anzulegen und ein pm dorthin zu kopieren.)

Auf der Commandline geht's dann weiter:
c:>pp -o hello.exe hello.pl

Das war’s – es seit denn, in hello.pl sind irgendwelche nicht-Standard Module in use … doch das wäre dann eine andere Geschichte.

h1

Foldings für pod und HERE-Documents in TextMate

23. Juni 2009

Um die mitunter längeren HERE-Dokumente sowie die Dokumentationen zwischen =pod und =cut in TextMate einklappen zu können, muss man dessen Voreinstellungen ein wenig ergänzen:

Im Bundle-Editor die Language-Grammar für Perl öffnen und die Regex für foldingStartMarker und foldingStopMarker ergänzen:

foldingStartMarker = '(/\*|(\{|\[|\()\s*$|=pod|<<\s+"\s*END_HERE")';
foldingStopMarker = '(\*/|^\s*(\}|\]|\)|=cut|\s*END_HERE))';

Damit die Foldmarker dann auch zu sehen sind, muss man sich an die folgenden Konventionen halten:

1. POD-Abschnitte immer mit =pod einleitenund =cut beenden (letzteres wird von Perl ohnedies verlangt).

2. Here-Dkumente immer mit END_HERE terminieren.

Und falls man die Rechtschreibkontrolle auch in den PODs haben will, lese man bei der glücklichen Giraffe nach

http://happygiraffe.net/blog/2006/06/22/spellchecking-pod-with-textmate/

(Allerdings stimmt der Scopeselektor dort nicht, dieser lautet richtig comment.block.documentation.perl )

h1

Debugging Nagios Plugins

9. Juni 2009

Heute war Debugging-Tag für die NetApp-Plugins für Nagios. Am meisten Probleme bereitete mir mein Ehrgeiz sie im ePN (Embedded Perlinterpreter for Nagios) laufen zu lassen. Bei einer Kundeninstallation wurden die Performancedaten nicht mehr verarbeitet, wenn das Script im ePN läuft – ein Fehler den ich bisher nicht nachvollziehen konnte, ich tippe hier auf eine fehlenden Kennzeichnung  (#nagios: -epn) im performance-Parser. Aber wer weiss …

Der andere Fehler waren einige Scripts, die sich im Nagios mit „Service check did not exit properly“ meldeten, während sie auf der Commandline problemlos liefen. Zunächst einmal drehte ich das Debugging im Nagios auf, indem ich in der nagios.cfg diese Werte setzte:

debug_level=16
debug_verbosity=1
debug_file=/usr/local/nagios/var/nagios.debug

Somit bekam ich dan schon alle Infos frei Haus geliefert, um die Fehler einzugrenzen. Z.B.
Embedded Perl failed to compile /usr/local/nagios/libexec/check_netapp_hardware.pl, compile error **ePN failed to compile /usr/local/nagios/libexec/check_netapp_hardware.pl: „Variable „$object“ will not stay shared at (eval 3969) line 240,“ at /usr/local/nagios/bin/p1.pl line 248.
– skipping plugin

Bei der Nachsicht in dem Plugin entdeckte ich, dass in einer Subroutine auf die mit my im Hauptprogramm definierte Variable (eben jene $object) zugegriffen wurde, was nicht 100%-ig sauber ist. Nach einer Änderung auf local our $object; lief das Plugin durch, bis zum nächsten Fehler, der dann sinngemäß gleich zu beheben war.

Anschliessend bekam ich dann noch die Meldung Check of service ‚CPU-Utilization‘ on host ’simbig‘ could not be performed due to a fork() error: ‚Cannot allocate memory‘! bzw. etwas ähnliches mit „too many open files“ was sich aber durch einen Neustart von Nagios beheben ließ.

Interessant auch, dass es schon ein Reihe von Wrapper-Scripts für Plugins gibt; die Folgenden habe ich gefunden aber sie für diese Debugging-Session dann gar nicht benötigt:

http://www.nagios.org/faqs/viewfaq.php?faq_id=379

http://www.waggy.at/nagios/capture_plugin.htm

h1

Nagios-Plugins für NetApp: Usage und Beispiele online

20. Februar 2009

Ich habe heute die Seite mit den Nagios-Plugins für NetApp-Filer wesentlich erweitert und übsichtlicher gestaltet. Die Daten dazu werden per Perlscript drirekt aus dem git gezogen und sind somit leicht und präzise aktulell zu halten.

Um die Beispiele und Usage unabhängig vom Browser und der Fenstergröße auf einen Blick erfassbar zu machen, war es wichtig, dass keine unerwünschten Zeilenumbrüche gemacht werden. Dabei hat sich das via regex eingesetzte <nobr>-Attribut als sehr hilfreich erwiesen. Die subroutine lautet:

sub nobr {
    # hält die Bereiche der Parameter (-H host, --help, ...) zusammen
    s/\[?\-+\w+[\ = \]]\S*/<nobr>$&<\/nobr>/g;
}

Link http://ingo.lantschner.name/NetappNagiosPlugins.html

h1

Mehrsprachige Perlscripte

30. Juni 2008

Ein Perlscript, wie z.B. ein Plugin für Nagios zu schreiben, das in mehreren Sprachen seine Meldungen ausgibt, ist an sich einfach. Nur die Doku dazu ist eher mager und zu allererst steht man vor dem Problem, welches Modul man denn dafür am besten nehmen soll. Ich entschied mich dann für Locale::TextDomain – eine high-level Schnittstelle zu anderen Modulen.

Folgend ein kleines Beispiel:

#!/usr/bin/perl -w
use strict;
use warnings;
use lib "/opt/local/lib/perl5/site_perl/5.8.8";  # plattformspezifische Zeile
use Locale::TextDomain "en"; #  "./LocaleXData";
## einfaches Beispiel
my $translated = __"Hallo Welt!";
print "$translated\n";
## Anhängen eines Zeilenumbruchs (der zu übersetzende String muss in Klammern!)
print __("Mir geht's gut.") . "\n";
# Schön ist, dass man auch Umlaute verwenden kann -
# bei der Erstellung der po-Datei mit xgettext
# --out-format=UTF-8 angeben!
print __("Täglicher Unsinn mit scharfem ß.") . "\n";
## Variablen können auch übersetzt werden
my $msg = "Hallo Welt!";
print __$msg;
print "\n";
my $world = "Welt!";
print __"Hallo $world";
print "\n";

Dieses Script gibt folgende Ausgabe aus, wenn man ein paar Vorbereitungen getroffen hat:

$ export LANG=de_AT
$ ./world2.pl
Hallo Welt!
Mir geht's gut.
Täglicher Unsinn mit scharfem ß.
Hallo Welt!
Hallo Welt!

So und nun schalten wir auf English um:

$ export LANG=en_US
$ ./world2.pl
Hello World!
Mir geht's gut.
Daily nonsens with non-ASCII
Hello World!
Hello World!

Es wurden also die Texte „Hallo Welt!“ und „Täglicher Unsinn …“ übersetzt – letzterer etwas eigenwillig. Für „Mir geht’s gut.“ gabe s keine vor bereitet Übersetzung – daher wurde der deutsche Originaltext ausgegeben.

Die nötigen Vorarbeiten für dieses kleine „Wunder“ waren der Reihe nach:

  1. Installation von Locale::Textdomain mit cpan
  2. Anlegen eines Unterverzeichnisses LocaleData im Arbeitsverzeichnis des Scripts. Heisst das Verzeichnis anders, oder liegt es nicht in einem der @INC-Pfade muss es dediziert angegeben werden:
  3. use Locale::TextDomain "en",  "./mylocales";
  4. Export der zu übersetzenden Strings mit xgettext in ein po-File
  5. Übersetzeung dieser Strings (mit einem Editor) – der Text „Mir geht’s gut“ wurde dabei ausgelassen.
  6. Umwandlung des po in ein mo-File (binär) mittels msgfmt. Dieses ist exakt mit diesem Namen und Pfad LocaleData/<locale_name>/LC_MESSAGES/<localename>.mo abzulegen. Der locale-Name kommt also sowohl im Verzeichnis unterhalb von LocaleData als auch im Namen der Datei vor! Es kann allerdings auch en für en_US usw. verwendet werden.
  7. Im Script dann das Locale referenzieren. (Will man mehrere refrerenzieren, muss man das mit einer if-Abfrage machen.)

ABER: So ganz komplett ist die Sache nicht, denn vor allem die Mischung aus Fixtext und Variablen kann man man so nicht sauber übersetzen. Wie das nun geht sieht man am besten in diesem Beispiel:

#!/usr/bin/perl -w

use strict;
use warnings;

use lib "/opt/local/lib/perl5/site_perl/5.8.8";  # plattformspezifische Zeile
use Locale::TextDomain "en"; #  "./LocaleXData";

## einfaches Beispiel

my $translated = __"Hallo Welt!";
print "$translated\n";

## Anhängen eines Zeilenumbruchs (der zu übersetzende String muss in Klammern!)

print __("Mir geht's gut.") . "\n";

# Schön ist, dass man auch Umlaute verwenden kann - 
# bei der Erstellung der po-Datei mit xgettext 
# --out-format=UTF-8 angeben!
print __("Täglicher Unsinn mit scharfem ß.") . "\n";

## Variablen können auch übersetzt werden

my $msg = "Hallo Welt!";
print __$msg;
print "\n";

# Schwieriger wird es bei Variablen, die gemeinsam mit Fixtext ausgegeben
# werden sollen. Das folgende Beispiel funktioniert mehr oder weniger zufällig,
# und nur hier, da "Hallo Welt!" oben schon übersetzt wurde. Aber xgettext 
# würde sich dagegen sträuben ("world2.pl:37: ungültige Interpolation einer 
# Variablen bei »$«") 
my $world = "Welt!";
#print __"Hallo $world";  # muss für parsing mit xgettext auskomentiert werden
						  # danach könnte man es wieder aktivieren - Perl käme damit zurecht
						  # aber Lösung ist das natürlich keine ...
print "\n";

# Richtig ist die Mischung von Fixtext und Variablen so:
print __x ("Hallo {person}!\n",
	person => "Franz");

Dieses Script kann man nun so parsen:

$ xgettext world3.pl --from-code=UTF-8 --keyword=__ --keyword=__x

Und bekommt dann ein po-file, das man so übersetzt:

#: world3.pl:12
msgid "Hallo Welt!"
msgstr "Hello World!"

#: world3.pl:18
msgid "Mir geht's gut."
msgstr "I am fine."

#: world3.pl:23
msgid "Täglicher Unsinn mit scharfem ß."
msgstr "Daily nonsens with non-ASCII."

#: world3.pl:44
#, perl-brace-format
msgid "Hallo {person}!\n"
msgstr "Hello {person}!\n"

Und es dann ganz einfach in ein mo umwandelt und dieses wie oben schon beschrieben ablegt.

Und Die Ausgabe schaut dann so aus:

$ ./world3.pl 
Hallo Welt!
Mir geht's gut.
Täglicher Unsinn mit scharfem ß.
Hallo Welt!

Hallo Franz!

$ export LANG=en_US

$ ./world3.pl 
Hello World!
I am fine.
Daily nonsens with non-ASCII.
Hello World!

Hello Franz!
h1

Net::SSH::Perl und known_hosts

21. April 2008

Bei der Verwendung des Perl Moduls Net::SSH-Perl tritt folgendes Problem auf:

Eine Verbindung zu einem Host mit SSH1 erzeugt die folgende Meldung:

Argument "ssh-rsa" isn't numeric in numeric eq (==) at modules/Net/SSH/Perl/Key/RSA1.pm line 103, <FH> line 23.

Nach längerem Suchen entdeckte ich, dass das Modul Hosts.PM die Methode equal aus dem Modul RSA1.pm verwenden. Dabei wird folgende Ausgabe erzeugt, wenn man das Modul RSA1 kurzfristig umschreibt wie folgt:

sub equal {
my($keyA, $keyB) = @_;
print "$keyA->{rsa}
$keyB->{rsa}
bitsA: $keyA->{rsa}{bits}
bitsB: $keyB->{rsa}{bits}
keyA: $keyA->{rsa}{n}
keyB: $keyB->{rsa}{n}
$keyA->{rsa}{e}
$keyB->{rsa}{e}\n";
$keyA->{rsa} && $keyB->{rsa} &&
$keyA->{rsa}{bits} == $keyB->{rsa}{bits} &&
$keyA->{rsa}{n} == $keyB->{rsa}{n} &&
$keyA->{rsa}{e} == $keyB->{rsa}{e};
}

Ausgabe:

Use of uninitialized value in concatenation (.) or string at modules/Net/SSH/Perl/Key/RSA1.pm line 95, <FH> line 23.
HASH(0x96fe78)
HASH(0x9659e4)
bitsA: 768
bitsB: ssh-rsa
keyA: 1295114094267464959040618524315923090715719842401896780056623074041026914313434353851674959103867550024637278939333976156853100989243816594892986096194402545568572126024189142175098585647756961269395154835682416927930811763277092609
keyB:
35
AAAAB3NzaC1yc2EAAAABIwAAAGEAoDtFmzlg44nJ6JdBs218W3xUmR1TXvTpQYLnAn6djlQPj2d6+Ev0nPObZ0cucvyRqanlkcT4XI6CmKozv4+7v5X3NxLMxA82llPqBQJFUJx+pHJYkOiKiP1cAtI0wXu1
Argument "ssh-rsa" isn't numeric in numeric eq (==) at modules/Net/SSH/Perl/Key/RSA1.pm line 103, <FH> line 23.
HASH(0x96fe78)
HASH(0x952f74)
bitsA: 768
bitsB: 768
keyA: 1295114094267464959040618524315923090715719842401896780056623074041026914313434353851674959103867550024637278939333976156853100989243816594892986096194402545568572126024189142175098585647756961269395154835682416927930811763277092609
keyB: 1295114094267464959040618524315923090715719842401896780056623074041026914313434353851674959103867550024637278939333976156853100989243816594892986096194402545568572126024189142175098585647756961269395154835682416927930811763277092609
35
35

Weitere Recherchen ergaben, dass hier die Datei .ssh/known_hosts gelesen wurde, und diese erlaubt laut man sshd zwei verschiedene Formate:

Examples
closenet,…,130.233.208.41 1024 37 159…93 closenet.hut.fi
cvs.openbsd.org,199.185.137.3 ssh-rsa AAAA1234…..=

Net::SSH::Perl kommt aber mit zweiterem nicht zurecht!

Workarund: In meinem konkreten Fall möchte ich den Hostcheck komplett deaktivieren, um das Perl-Skript von der SSH-Konfig des Hosts vollkommen unabhängig zu machen. Das erreiche mit einem einzigen # an der richtigen Stelle des SSH1.pm:

    $ssh->{session}{id} = $session_id;
# disabled von Ingo: $ssh->check_host_key($keys{host});
my $session_key = join '', map chr rand(255), 1..32;

Die Security-Implications sind hoffentlich klar: Es findet keine Überprüfung mehr statt, ob der kontaktierte Host nicht in irgend einer Form gespooft ist. Die Verbindung wird ohne Warnung und Prüfung aufgebaut und das Passwort übertragen! Nameresolution und Routing tragen somit die gesamte Verantwortung für die Vertraulichkeit des Passwortes. (Bei automatisierten Scripts in einer an sich gesicherten Umgebung erscheint mir das vertretbar)

http://rt.cpan.org/Public/Bug/Display.html?id=25175

h1

Net::SSH::Perl installieren

21. April 2008

Im Gegensatz zu Net::SSH bietet das Modul Net::SSH::Perl eine voll in Perl integrierten SSH-Client, der in den Perl-Scripts verwenet werden kann. D.h. dieser ist von der installierten SSH-Version unabhängig, forked nicht bei jedem Aufruf was für Performance und Sicherheit sorgt.

Das große Problem an diesem Modul ist die Installation. Es gibt zahlreiche Abhängigkeiten und die Installation via CPAN scheiterte sowohl auf Mac OS X (10.5.2) als auch im Ubuntu Server 6.01 (LTS). Als Troublemaker hab ich die Module Math::Pari, Math::BigInt::GMP und Crypt::DSA geortet. Diese habe ich nun manuell installiert. Dazu die folgenden Schritte zunächst für Ubuntu und dann für OS X:

Installation von Net::SSH::Perl auf Ubuntu 6.01.1 Server

Falls man das nicht schon hinter sich hat müssen perl und cpan installiert werden:

# apt-get install perl

Dann geht’s los …

Math::Pari

$sudo -s
# apt-get update
# apt-get upgrade
# apt-get install gcc
#apt-get install g++

Dann hole man sich das Tar von der Pari-Homepage und packe es im CPAN-Build-Verzeichnis aus:

# cd ~/.cpan/build
# wget http://pari.math.u-bordeaux.fr/pub/pari/unix/pari-2.3.3.tar.gz
# tar -xzf pari-2.3.3.tar.gz

Nun kann man den Anweisungen aus dem INSTALL folgen:

b) ./Configure

c) make all, make bench

d) make install

e) cp misc/gprc.dft /etc/gprc

Tipp: Wenn man diese Fehlermeldung bekommt „C compiler does not work. PARI/GP requires an ANSI C compiler“ dann hat man vergessen den c++-Kompiler zu installieren. Siehe oben!

Math::BigInt::GMP

Damit dieses Modul installiert werden kann, muss man erst einmal eine GMP-Library im OS installieren:

# apt-get install libgmp3-dev

cpan> install Math::BigInt::GMP

Crypt::DSA

Crypt::DSA muss manuell (ohne CPAN) installiert werden, da make test sich aufhängt.

# wget http://search.cpan.org/CPAN/authors/id/B/BT/BTROTT/Crypt-DSA-0.14.tar.gz
# tar xzf Crypt-DSA-0.14.tar.gz
# cd Crypt-DSA-0.14
# perl Makefile.PL
# make
# make install

Nun kann man den Rest mit CPAN installieren:

cpan> install Net::SSH::Perl

Bei der Auswahl der Algorithmen würde ich noch DES3 hinzunehmen – NetApp z.B. unterstützt das out-of-the-box:

    [1] IDEA
[2] DES
[3] DES3
[4] Blowfish
[5] RC
Enter your choices, separated by spaces: [1] 1 3

Möchte man Tripple-DES ergänzen, nachdem man das Modul Net::SSH::Perl schon einmal nur mit IDEA installiert hat, so ruft man einfach in der cpan-Shell make Net::SSH::Perl auf.

Installation von Net::SSH::Perl auf Mac OS X

Zunächst ein Hinweis: Die Developer-Tools waren bei mir installiert (siehe http://developer.apple.com/tools/xcode/index.html ) – das muss so sein, schon alleine damit die später benötigte port-Software funktioniert.

Ansonsten versuchte ich sinngemäß zu verfahren wie beim Ubuntu-Server. Die Installation von Crypt::DSA mittels CPAN war kein Problem (OS X 10.5.2). Somit bleiben Math::Pari und GMP als „Sonderfälle“. Und da war nichts zu machen. Weder mittel CPAN noch durch den Weg „zu Fuß“ mittels Download, Auspacken und dann ./Configure und make. Somit änderte ich die Strategie komplett und installierte das ganze mittels darwin-ports. Falls noch nicht geschehen, ist die Darwinport-Software zunächst zu installieren. (Siehe http://darwinports.com/ bzw. http://guide.macports.org/)

Installation von Pari

ich habe zunächst einmal Pari installiert. Das Paket liegt hier (http://pari.darwinports.com/), muss aber nicht heruntergeladen werden. Das macht das Komando:

$ sudo port install pari

Installation von Convert::PEM und Net::SSH::Perl

http://p5-convert-pem.darwinports.com/

Da hat sich ein Problem ergeben: Nach einiger Zeit bleibt der Installationsprozess hängen, die CPUs laufen auf jeweils 100% und nichts geht mehr weiter. Auch viel Geduld ändert da nichts. Wenn man die Installation im Debugmodus startet, sieht man die Ursache des Problems:

$sudo port -d install p5-convert-pem

Wenn CPAN für diesen User noch nicht konfiguriert ist, frägt Darwinport nach dem Kontinent für Downloads. Und aus dieser Schleife findet er icht mehr heraus. Die Lösung ist also, cpan zuvor zu konfigurieren und sicher zustellen, dass diese Konfiguration dauerhaft gespeichert wurde. (Ich hab dazu sudo -s und dann als root cpan gestartet, Bundle::CPAN installiert, CPAN nochmal gestartet und die diversen Dialoge beantwortet wobei ich die Frage nach der Speicherung der Konfiguration entgeggen dem Default mit yes statt mit no beantwortet hatte.)

Nach diesem Heckmeck durfte ich dann endlich Convert::PEM und Net::SSH::Perl installieren; und zwar so:

$ sudo port install p5-net-ssh-perl

Nacharbeiten

Die Module werden so out of the Box nicht gefunden. Man kann deren Verzeichnisse (/opt/local/lib/perl5/5.8.8) aber mit lib im Perlscript hinzufügen.

Veränderungen an den Modulen

Wenn man diese mit port installierten Module allerdings editiert und die Veränderten in ein eigenes Verzeichnis kopieren, funktioniert das ganze nur noch teilweise. Net::SSH::Perl kommt damit zurecht. Math::Pari sträubt sich aber. Das führt dazu, dass in diesem Fall nur SSH1 Verbindungen mit Net::SSH::Perl möglich sind.

h1

Perl-Doku in Textmate

3. April 2008

In Textmate gibts eine feine Funktion für Perlprogrammierer: Ctrl+H öffnet die Dokumentation zu dem Begriff, auf dem gerade der Cursor steht. Das geht aber nur, wenn Perldoc installiert ist – sonst sieht man nur ein leeres Fenster.

Also sudo cpan im Terminal und dann install Perldoc

h1

SEO-Monitoring 3: Daten auswerten

27. Februar 2008

Wie in „SEO-Monitoring 2: Daten holen“ beschrieben, erhalten wir mit dem Skript get_google.pl jeden Tag ein neues Verzeichnis, in dem die Suchergebnisse als HTML-Files angelegt sind. Ein Vorteil des modularen Ansatzes (One task one tool) ist, dass es keineswegs nötig ist diese Verzeichnisstruktur mit dem Skript aufzubauen und zu befüllen. Genausogut kann man das manuell mit dem Browser machen, und die Seiten mit „Speichern als …“ als HTML-File ablegen. Das Skript get_google.pl macht ja nichts anderes, als einen „geskripteten“ Browser zur Verfügung zu stellen.

Results von get_google.pl (kleiner Ausschnitt)

In dem Bild oben sehen wir einen Ausschnitt aus dem Ergebnis von get_google.pl bei der Suche nach irgendwas in 1030 Wien um 9h05. Somit sind die Suchergebnisse für einen bestimmten Zeitpunkt und Begriff einmal vollständig archiviert, nur die Übersicht hat man so natürlich nicht … Daher ein weiteres Skript, das diese Verzeichnisse und alle Dateien systematisch durchsucht und daraus ein CSV-File (Kommaseperierte Datei) generiert. Dieses CSV-Ausgabefile soll z.B. aussehen wie folgt:

"Zeit[Epochenekunden]","Datum","Uhrzeit","Suchbegriff(e)","tad-position","ad-position","s-position","Anz. der Vorkommen in den S.E.",
"1204013102","2008-02-26","09h05","foo","0","1","11","2",
"1204013102","2008-02-26","09h05","bar","0","1","3","4",
"1204013102","2008-02-26","09h05","foo+bar","0","0","1","2",

So was sehen wir da oben nun: Zu dem Suchbegriff „foo“ wurde am 26. Februar um 9h05 eine bestimmte URL in den Google-Ergebnissen an der 11 Position angezeigt. In den Anzeigen (AdWords) war diese URL im rechten Block an 1. Stelle und im oberen Block gar nicht gelistet. In den ersten 100 Ergebnissen (ohne AdWords-Bereich), war diese URL 2 mal gelistet. Das alles entnehmen wir der ersten Zeile nach den Spaltenüberschriften (zweite Zeile im o.g. Ausschnitt).

Das bei Eingabe des Suchwortes „bar“ wird diese URL in den AdWords-Anzeigen oben nicht, auf der rechten Seite an erster Position und in den Suchergebnissen an 3. Stelle gelistet. Insgesamt ist diese URL 4 mal in den ersten 100 Ergebnissen zu finden. Das entnehmen wir der zweiten Zeile nach den Spaltenüberschriften (dritte Zeile im Beispiel oben).

Und nun die letzte Zeile: Hier wurde nach „foo bar“ gesucht. Die URL taucht in den Anzeigen gar nicht auf, dafür in den Suchergebnissen an erster Stelle und das zwei mal.

Dieses Beispiel ist übrigens fiktiv! Wenn jetzt wer verwundert innehält und frägt, was daran jetzt übersichtlich sein soll, sag ich – nur Geduld, denn eine CSV-Datei ist eine hervorragende Kandidatin für den Import in die unterschiedlichsten Anwendungen zur weiteren Verarbeitung.

Und damit zu dem Skript. Dieses benötig klarerweise einige Angaben: Nach welchen URLs in den Suchergebnissen gesucht werden soll; wo diese Suchergebnisse zu finden sind und wohin das CSV-File zu schreiben ist. Das meiste davon habe ich in dem Beispielskript fest kodiert – lediglich welches Unterverzeichnis in den Ergbnissen durchsucht werden soll wird beim Aufruf mitgegeben.

Damit das Skript ordentlich arbeiten kann, müssen beim Ablegen der Suchergebnisse als HTML-Files einige Konventionen eingehalten werden:

  • Das Basisverzeichnis für alle nun folgenden Verzeichnisse ist mit der Variable $basedir anzugeben. Es empfielt sich hier einen absoluten Pfad wie „/Users/Ingo/…./SEO-Monitor“ einzusetzen, wenn man das ganze mit einem Scheduler starten will.
  • In dem über die Variable $htmldir definierten Verzeichnis wird je befragtem Server ein eigenes Verzsichnis angelegt, z.B. http://www.google.ch. Je Tag ist ein eigenes Unterverzeichnis anzulegen im Format yyy-mm-dd.
  • Je Abfrage sind alle Ergebnisseiten zu benamsen wie folgt: Epochensekunden_Uhrzeit_Suchbegriffe_SSeite.html. Also z.B. sind die Ergebnisse 11-20 (zweite Seite) einer Suche nach „foo bar“ am 27 Februar 2008 um 15h55 auf google.at in dieser Datei abzulegen: /Users/Ingo/…/SEO-Monitor/google-results/www.google.at/2008-02-27/1204124118_15h55_foo+bar_S2.html

Aus dieser Datei bzw. ihrem Namen werden nun einige Informationen extrahiert: Die Zeit beispielsweise und die Seitennummer aber auch der Suchbegriff und der befragte Google-Server. Sehr wichtig ist die Kombination aus Zeit (Epochensekunden) und Suchbegriffen – diese definieren gemeinsam den Filegroupindex, legen also fest, welche Dateien zu einem bestimmten Suchbegriff und Zeitpunkt angelegt wurden. Alle Ergebnisse einer Filegroup werden dann in einer Zeile der ausgegeben CSV zusammengefasst!

Beispiel: Diese Dateien gehören zu ein und der selben Filegroup die den Namen „1204124118_foo+bar“ trägt:

1204124118_15h55_foo+bar_S1.html
1204124118_15h55_foo+bar_S2.html
….
1204124118_15h55_foo+bar_S100.html

Je wie oft nun die gesuchten URLs in den Suchergebnissen vorkommen, findet sich dann in einer Zeile des ausgegeben CSV-Files.

Das Skript durchsucht in seinem Hauptteil nun Token für Token jeder Datei. zunächst wird der Suchbegriff extrahiert. (Dieser ist zwar auch im Dateinamen zu finden, aber wir nehmen lieber den aus der Datei). Dann wird gesucht:

  • Auf Seite 1:
  • Anzeigen rechts, hier gibt es das Tag „<a id=an1, …“ – das ist praktisch, denn die Nummer gibt die Position wieder. Ablegen in einen Hash (%ad_pos) mit dem Filegroupindex als Schlüssel.
  • Anzeigen oben, hier gibt es das Tag „<a id=pa1, …“ – siehe oben. Ablegen in einen Hash (%tad_pos) mit dem Filegroupindex als Schlüssel.
  • Auch wenn es unwahrscheinlich ist, dass die selbe URL in den Anzeigen auf Seite 1 mehrfach vorkommt, so wird doch vom Skript sichergestellt, dass die jeweils beste Position erhalten bleibt.
  • Auf Seite 1 und allen folgenden Seiten
    • Die Tags der Suchergebnisse; wenn href und class=l, dann in diesem Tag nach den URLs suchen; das jeweils beste Position wird in einen Hash (%s_pos) mit dem Filegroupindex als Schlüssel abgelegt.

    Es werden also Anzeigen auf den Seiten 2-100 ignoriert – das entspricht aber dem normalen Userverhalten – wer schaut schon auf Seite 2 auf eine Anzeige?!

Sonst ist noch anzumerken, dass natürlich mehrere URLs gesucht werden können, z.B. apartments-wien.at und apartments-vienna.at – die jeweils beste Position wird in der Ausgabe angezeigt. Das ist sinnvoll, da so manche Organisation ja den selben Webauftritt unter verschiedenen URLs anbietet.

Am Ende des Skriptes wird dann ermittelt, welche Dateigruppen es gab – diese landen in einem Array namens @uFGIs (uniq FileGroupIndices). Dessen Elemente werden dann der Reihe nach in die Hashes %ad_pos usw. eingesetzt und so das CSV-File geschrieben.

That’s it. Zur Auswertung dieses CSVs dann wann anders.

P.S. Ja der Quellcode, den veröffentliche ich demnächst hier:

http://ingo.lantschner.name/Nagios.html