Perl hat sooo einen Bart! Warum lernen?

Wenn du dich ein wenig in die Geschichte von Perl reinliest, wirst du sehr bald mit einer ausgesprochen alten Sprache konfrontiert. Fast 30 Jahre sind eine Steinzeit im Maßstab der IT.

Warum also sollte man sich mit dieser Programmiersprache noch beschäftigen?

Weil sie immer noch Anwendung findet und Einfluss ausübt!

Enterprise Perl
Enterprise Perl von Curtis Poe (CC BY-SA)

Insbesondere bei der Verarbeitung von regulären Ausdrücke ist Perl zu spüren. Daneben ist die Sprache prädestiniert zur Verarbeitung von Text-Dateien – wovon es (vor SystemD) bei GNU/Linux-Systemen eine ganze Menge gab. Deswegen finden sich vor allem SysAdmins mit dieser Sprache.

Ich möchte mich in diesem Artikel von daher auch zunächst den regulären Ausdrücken widmen:

  1. Das Problem wird mit einem regulären Ausdruck in Angriff,
  2. dieser auseinander genommen
  3. und anschließend erläutert.

Das Problem

In den Kommentaren zu einem der letzten Artikel auf diesem Blog wurde ich gefragt, welche Programmiersprache ich als Einstieg empfehlen könne. Meine Antwort: Kommt drauf an. Sie hat sich für Perl entschieden. Kurze Zeit später gab es diese Hausaufgabe:

someprocess | sed -r “s/([0-9]+)\/([0-9]+)\/([0-9]+)/\2\.\1\.\3/”

Reguläre Ausdrücke arbeiten mit Mustern. Dieser Mix aus verschiedenen Symbolen ist mit ein Grund dafür, warum die Kommandozeile auf viele arkan wirkt. Ist es aber nicht. Man muss sich nur einmal Zeit nehmen und vielleicht Papier und Stift zur Hand.

Auseinandergenommen

Reguläre Ausdrücke (hier eine Ersetzung) sind zwischen Trennzeichen eingefasst. Quasi-Standard ist /. Das Grundkonstrukt ist also s///. Brechen wir es also auf:

  1. ([0-9]+)\/([0-9]+)\/([0-9]+)
  2. \2\.\1\.\3

Und wieder finden wir Muster. Unter erstens gibt es drei davon, unter zweitens zwei. Ich erkläre sie hinter dem Symbol.

  1. ([0-9]+)\/([0-9]+)\/([0-9]+)
    1. () – Damit wird eine Gruppe gebildet, auf die später zugegriffen werden kann.
    2. []+ – Hier finden wir zwei Konstrukte vor. Zum einen eine Zeichenauswahl [] und dazu ein Quantor + für mindestens einen Treffer aus der Auswahl. Die Zeichenauswahl beschränkt sich auf Ziffern 0 bis 9.
    3. \/ – Weil / bereits den regulären Ausdruck unterteilt, müssen wir das Zeichen an sich vor dieser Interpretation schützen. Dies geschieht mit einem vorangestellten \.
  2. \2\.\1\.\3
    1. \Zahl – Jede der Gruppe aus 1.1. wird durchnummeriert. Wenn wir den Inhalt benutzen möchten, greifen wir per \Index darauf zu.
    2. \. – Nicht nur / ist ein Metazeichen, sondern beispielsweise auch .. Dieses trifft auf ein beliebiges Zeichen zu. Zahlen, Buchstaben, Leerraum. Genaueres hängt von der Implementierung des Interpreters (hier sed, welches Reguläre Ausdrücke nach Perl verwendet) ab und findet sich im Handbuch. In diesem Falle alles außer Zeilenumbruch. Wir möchten aber einen Punkt haben und schützen also das Zeichen.

Zusammengesetzt

Übersetzen wir es also in unsere Sprache:

Suche nach einer Folge von drei Ziffern (1.2), die alle mindestens einmal vorkommen (1.2) und mit einem Schrägstrich von einander getrennt (1.3) sind. Merke dir jede einzelne Zifferngruppe (1.1).

Ersetze (s///) den Ausschnitt, so dass die zweite Gruppe (\2) am Anfang steht, dann die erste Gruppe (\1) folgt und der Ausdruck mit der dritten Gruppe (\3) abschließt. Trenne die Gruppen durch einen Punkt (2.2).

Benutzt habe ich den regulären Ausdruck dafür, Datumsangaben von calcurse im US-Amerikanischen Format der Art 11/14/2014 umzuwandeln in unsere Schreibweise, dass heißt 14.11.2014.

Und jetzt deine Hausaufgabe: Wie kann ich den Ausdruck auf dieses Problem beschränken? Wie schreibe ich es in Perl?

17 Gedanken zu “Perl hat sooo einen Bart! Warum lernen?

  1. TIMTOWTDI

    zum Beispiel mit regex, ähnlich, wie mit sed:
    #!/usr/bin/perl
    $_ = ’11/14/2014′ =~ s|(\d+)/(\d+)/(\d+)|$2.$1.$3|r;
    print „$_\n“;

    $ time perl regex.pl
    14.11.2014
    real 0m0.008s
    user 0m0.003s
    sys 0m0.006s

    als one-liner:
    $ echo „11/14/2014“ | perl -e ‚$_ = =~ s|(\d+)/(\d+)/(\d+)|$2.$1.$3|r;‘ -e ‚print „$_“;‘
    $ perl -e ‚$_ = =~ s|(\d+)/(\d+)/(\d+)|$2.$1.$3|r;‘ -e ‚print „$_“;‘ strptime($date, ‚%m/%d/%Y‘)->strftime(‚%d.%m.%Y‘), „\n“;

    $ time perl module.pl
    14.11.2014
    real 0m0.335s
    user 0m0.050s
    sys 0m0.020s

    • Danke.

      Darf ich das kommentieren?
      Ich hab noch nicht mit Perl gearbeitet (okay, einmal versucht, ein Perl-Script für LaTeX zu modifizieren), aber ich hab eine ungefähre Vorstellung, was es tut.

      Kann man die Ausgabe von real/user/sys unterdrücken?

      Gruß, André

      • Meine Antwort kam merkwürdigerweise nicht ganz durch… poste den Rest später.
        Die Angabe, wie viel Zeit die Ausführung des Scripts in Anspruch nimmt, kommt von „time“, nicht von Perl. Siehe „man time“ 😉
        Wollte damit nur die Effektivität der Lösungen veranschaulichen.
        Man braucht daher nur den Befehl „time“ wegzulassen.

  2. Regex-Lösung als One-Liner:
    $ echo „11/14/2014“ | perl -e ‚$_ = =~ s|(\d+)/(\d+)/(\d+)|$2.$1.$3|r;‘ -e ‚print „$_“;‘

    $ perl -e ‚$_ = =~ s|(\d+)/(\d+)/(\d+)|$2.$1.$3|r;‘ -e ‚print „$_“;‘ <(echo "11/14/2014")

  3. Das erste Script, nur strict:

    #!/usr/bin/perl
    use strict;
    use warnings;
    my $date = ’11/14/2014′ =~ s|(\d+)/(\d+)/(\d+)|$2.$1.$3|r;
    print „$date\n“;

    $ time perl regex_strict.pl
    14.11.2014
    real 0m0.015s
    user 0m0.011s
    sys 0m0.003s

    Wie man sieht, ist es schon etwas langsamer.

  4. Man kann dafür auch ein Modul verwenden, die Ausführung nimmt dann aber (viel) mehr Zeit in Anspruch. Es gibt mehrere taugliche, hab hier das Time::Piece genommen:

    #!/usr/bin/perl
    use strict;
    use warnings;
    use Time::Piece;
    my $date = ’11/14/2014′;
    print Time::Piece->strptime($date, ‚%m/%d/%Y‘)->strftime(‚%d.%m.%Y‘), „\n“;

    $ time perl module.pl
    14.11.2014
    real 0m0.335s
    user 0m0.050s
    sys 0m0.020s

    Ich bin auch nur dabei Perl zu entdecken. Es hat etwas in sich, was es für mich viel anziehender macht, als andere Programmiersprachen.

    • Falls du auf Twitter sein solltest, kannst du es mit #PerlLernen taggen.
      Wir (@kulervo oben und ich) arbeiten uns da gerade ein.

      Ich mag Perl vor allem wegen seiner regulären Ausdrücke (siehe oben) und weil ich mit Text-Datein unter GNU/Linux so viel anstellen kann.
      Für gewöhnlich hab ich dafür Python-Scripte geschrieben. Ich werd mal schauen, ob ich die Implementierungen gegeneinander vergleichen kann 🙂

  5. Es gibt eine weitere Möglichkeit das Problem zu lösen, diesmal ohne reguläre Ausdrücke zu verwenden.
    Das Datum kann auch mit dem Bash-Befehl „date“ in die gewünschte Form umwandelt werden und Perl kann auf dieses durchgreifen.

    In der Konsole geht es so:
    date -d 11/14/2014 +%d.%m.%Y

    Das Perl-Skript sieht folgend aus:
    #!/usr/bin/perl
    use strict;
    use warnings;

    my $date = ’11/14/2014′;
    my $format = ‚%d.%m.%Y‘;

    system ‚date‘, „-d $date“, „+$format“;

    Weiß nicht inwiefern das Ergebnis von der LANG Einstellung abhängig ist, benutze hier en_US.UTF-8

    • Lässt sich der Befehl pipen?
      Mein Szenario ist dieses:

      Ich lass mir von calcurse eine Übersicht der Termine für die folgenden drei Tage ausspucken. Dabei benutzt die Anwendung aber US-Amerikanische Notation, die ich ungeschickt finde. Von daher hab ich sie mit sed in unser Format überführt. Ich mag reguläre Ausdrücke 🙂

      Was du hier nutzt ist strftime (http://strftime.net). Geht auch. Aber ich hab’s nicht probiert, ob es Eingaben aus einer Pipe entgegennehmen kann.

      • date kann nicht Eingaben aus einer Pipe entgegennehmen. Es kann aber eine Datei lesen.
        date -f dates.txt
        oder
        date -f <(echo '11/14/2014')

        Das perl Skript kann von STDIN lesen, am besten man benutzt hier eine while Schlaufe:
        #!/usr/bin/perl
        use strict;
        use warnings;
        my $format = '%d.%m.%Y';
        while () {
        system ‚date‘, „-d $_“, „+$format“;
        }

        Eine Idee wäre es calcurse mit ins Skript einzubauen.

      • Diese Skriptversion liest von STDIN, date kann es offensichtlich nicht.

        #!/usr/bin/perl
        use strict;
        use warnings;

        my $format = ‚%d.%m.%Y‘;

        while () {
        system ‚date‘, „-d $_“, „+$format“;
        }

        Eine Idee wäre es calcurse gleich mit ins Skript einzubauen.

      • Zwischen den runden Klammern am Anfang der while-Schlaufe sollen natürlich spitze Klammern stehen.
        Oder STDIN in spitzen Klammern.
        Keine Ahnung, warum sie gelöscht wurden… wie soll man denn Codesnippets teilen?

Hinterlasse eine Antwort zu Andre Jaenisch Antwort abbrechen