Erzeuge Aliasliste aus Maildatei

Na klar: Man kann sich innerhalb der meisten Mailprogramme eine Adressliste mit Spitznamen aus den einzelnen Mails zusammenklauben. Aber wenn man einmal Ordnung schaffen will, ist es zu mühselig, sich durch die Mails zu hangeln.

Aufgabe also:

Letzter zuerst: Ermittle nur eindeutige Adressen

Das ist eine Aufgabe, die man gut GNU-Tools machen lassen kann: sort und uniq tun's. Die Ausgabe der Datei soll umgeleitet werden durch diese beiden Befehle. Nebenbei kann man gleich nur die Zeilen heraussuchen, die mit "From: " beginnen.
#!/usr/bin/perl

open ADRESSEN, "| sort | uniq";

while (<>) {
   next unless (/^From: /);
   print ADRESSEN $_, "\n";
   }
Hier kommen mehrere Aspekte von Perl zum Tragen: Nun gilt es, den Namen und die Mail-Adresse des Absenders zu ermitteln.

Ermittle Adresse und Namen des Absenders

Wenn man das bisherige Programm einmal laufen läßt, bekommt man einen Ausdruck aller Zeilen, die mit "From: " beginnen. Was hinter dem "From: " kommt, ist für die weitere Auswertung interessant. Daher kann man beim Testen auf eine zulässige Zeile sich den Inhalt gleich herausgreifen.
#!/usr/bin/perl

open ADRESSEN, "| sort | uniq";

while (<>) {
   next unless (/^From: (.+)$/);
   my $absender = $1;
   print ADRESSEN $absender, "\n";
   }

Der Befehl my deklariert und initialisiert eine Variable (default auf den Leerstring), die nur im sie umschließenden Block Gültigkeit hat (lexical scoping).

Es fällt auf, dass in dieser Liste zwei verschiedene Arten vorkommen, einen Absender anzugeben. Die eine ist von der Form "From: Hugo Habicht <hugo@habicht.de>", die andere sieht aus wie "From: hugo@habicht.de (Hugo Habicht)".

Beide Schreibarten müssen berücksichtigt werden. Die interessanten Teile sollen durch (Klammer-)Gruppierung zwischengespeichert werden. Dann muss festgehalten werden, um welche Schreibweise es sich handelt.

Das ist nicht so einfach. Der Übersicht halber verwenden wir daher den Modifizierer x wie in s///x . Er bedeutet, dass jeglicher Leerraum ignoriert wird, es sei denn, der wird explizit angegeben. Auch Kommentare werden ignoriert. Damit können reguläre Ausdrücke sehr viel übersichtlicher geschrieben werden.

Unser Skript erweitert sich nun zu

#!/usr/bin/perl -w

open ADRESSEN, "| sort | uniq";

while (<>) {
    next unless (/^From: (.+)$/);
    my $absender = $1;
    if ($absender =~ /([^<(]+)      # Erst "Freitext" ohne öffnende
		                    # spitze oder runde Klammer
		       (            # dann entweder:
		       \(([^)]+)\)  # Text in Paar runder Klammern
		       |            # oder:
		       <([^>]+)>    # Text in Paar spitzer Klammern
		       )
	/x) {
	print ADRESSEN '$1=', $1, '$2=', $2, '$3=', $3, '$4=', $4, "\n";  
   }
   else {
	warn "Fehler beim Parsen der Adresse:\n\t$absender";
	}
}
Beachte hierzu: Der reguläre Ausdruck soll noch einmal auseinandergenommen werden: /([^<(]+)\((([^)]+)\)|<([^>]+)>)/. Er bedeutet: Die erste Gruppierung enthält eine Folge von mindestens einem (+) Zeichen, das weder linke spitze noch runde Klammer ist. Diese Folge wird in $1 gespeichert. Die zweite Gruppierung enthält entweder eine Gruppierung, die innerhalb von runden Klammern steht, oder eine Gruppierung, die innerhalb von spitzen Klammern steht (entweder|oder) . Diese Gruppierung ist eine Folge ohne rechte spitze bzw. runde Klammer. Es wurden insgesamt vier Klammerpaare in diesem regulären Ausdruck gebildet. Interessant nun: Was steht wo drin? Werden überhaupt alle vier Variablen gefüllt oder nur drei, nur die ersten drei?

Lösung: Es gilt die Regel der Reihenfolge der linken Klammern! Wenn ein Klammerpaar nicht zutrifft, wird in die entsprechende Variable nicht hineingeschrieben.

Das läßt sich hier wunderbar ausnutzen!

  1. Wenn sich im vierten Klammerpaar Inhalt befindet, dann war es ein Paar spitze Klammern, also eine Mailadresse.
  2. Wenn sich im dritten Klammerpaar Inhalt befindet, dann war es ein Paar runde Klammern, also ein bürgerlicher Name.
Man kann noch eine zusätzliche Sicherung einbauen: Wenn weder spitze noch runde Klammern gefunden wurden, soll eine Warnung ausgegeben werden.
#!/usr/bin/perl -w

while (<>) {
    next unless (/^From: (.+)$/);
    my $absender = $1;
    if ($absender =~ /([^<(]+)      # Erst "Freitext" ohne öffnende
		                    # spitze oder runde Klammer
		       (            # dann entweder:
		       \(([^)]+)\)  # Text in Paar runder Klammern
		       |            # oder:
		       <([^>]+)>    # Text in Paar spitzer Klammern
		       )
	/x) {
        #  Wenn Inhalt in viertem Klammerpaar,
	#  dann waren es spitze Klammern
	#  und vier ist die Mailadresse
	my $mailadresse = $4;
	my $realname = $1;
        #  Wenn Inhalt in drittem Klammerpaar,
	#  dann waren es runde Klammern
	#  und drei ist der Name
	if ($3) {
	    $mailadresse = $1;
	    $realname = $3;
	}
	print ADRESSEN $realname, " hat die Adresse ", $mailadresse, "\n";
      }
   else {
	warn "Fehler beim Parsen der Adresse:\n\t$absender";
      }
}

Zuguterletzt: Schreibe im Alias-Format

Es gibt, abhängig vom Mailprogramm, verschiedene Formate für eine Adresszeile. Der Mailclient mutt verlangt die folgenden Angaben:
alias {Spitzname} {bürgerlicher Name} {Mailadresse}
Die Mailadresse steht dabei in spitzen Klammern.

Man kann also ein Unterprogramm definieren, in dem steht:

sub aliasline {
    my ($realname, $mailadresse, $nickname) = @_;
    print "alias $nickname $realname <$mailadresse>\n"
    }
und in das Hauptprogramm anstelle des print-Befehls das Unterprogramm aufrufen.

Wie bekomme ich aber nun den Spitznamen aus dem Namen heraus? Den wird man sich nach wie vor selbst ausdenken müssen. Man kann aber schon einmal dem Skript vorgeben, einen "sinnigen" Spitznamen zu raten, am besten aus dem ersten Wort des bürgerlichen Namens.

Weiter kann noch eine kosmetische Änderung eingebaut werden: Manchmal steht der bürgerliche Name in Anführungszeichen, die für die Adresszeile aber überflüssig sind. Diese Zeichen können auch getilgt werden. Außerdem befindet sich noch Leerraum am Ende des bürgerlichen Namens, den man ebenfalls wegnehmen kann.

#!/usr/bin/perl -w

sub aliasline {
    my ($realname, $mailadresse, $nickname) = @_;
    print "alias $nickname $realname <$mailadresse>\n"
    }

while (<>) {
    next unless (/^From: (.+)$/);
    my $absender = $1;
    if ($absender =~ /([^<(]+)      # Erst "Freitext" ohne öffnende
		                    # spitze oder runde Klammer
		       (            # dann entweder:
		       \(([^)]+)\)  # Text in Paar runder Klammern
		       |            # oder:
		       <([^>]+)>    # Text in Paar spitzer Klammern
		       )
	/x) {
        #  Wenn Inhalt in viertem Klammerpaar,
	#  dann waren es spitze Klammern
	#  und vier ist die Mailadresse
	my $mailadresse = $4;
	my $realname = $1;
        #  Wenn Inhalt in drittem Klammerpaar,
	#  dann waren es runde Klammern
	#  und drei ist der Name
	if ($3) {
	    $mailadresse = $1;
	    $realname = $3;
	}
	$realname =~ s/"//g;
	$realname =~ s/\s+$//;
	#  Grober Rateversuch für den Spitznamen:
	#  erstes Wort im Namen
	$realname =~ /(\w+)/;
	aliasline($realname, $mailadresse, lc($1));
    }
    else {
	warn "Fehler beim Parsen der Adresse:\n\t$absender";
    }
}
Fertig!
Zurück
Johannes Hüsing