Ruby-Programmierung: Regular Expressions

Zurück zum Inhaltsverzeichnis.

Regular Expressions, oder reguläre Ausdrücke, sind eine Beschreibungssprache für Strings. Sie erlauben bestimmte Muster in Zeichenketten zu finden, zu ersetzen oder einen String auf seine Gültigkeit zu überprüfen. Eine komplette Beschreibung regulärer Ausdrücke füllt eigene Bücher, daher soll es in diesem Kapitel um einführende Beispiele und einen groben Überblick gehen.

Initialisierung und einfache Beispiele

Bearbeiten

Regular Expressions werden zwischen zwei / definiert. Möchte man ein Slash innerhalb des regulären Ausdrucks verwenden, so ist es nötig diesen durch eine Escapesequenz (zum Beispiel /\//) darzustellen. Dabei wird das / durch ein vorangestelltes \ maskiert. Es stehen einige Vergleichsoperatoren zur Verfügung, die in ihrer Bedeutung an denen von Zahlen angelehnt sind.

s = "Hallo Welt!"
if s =~ /Hallo/
  puts s + " enthält Hallo"
end
if s !~ /hallo/
  puts s + " enthält kein hallo"
end

Regular Expressions unterscheiden Groß- und Kleinschreibung und es gibt einen Unterschied zwischen den Rückgabewerten der beiden Operatoren =~ und !~, der im obigen Skript nicht deutlich wird. !~ prüft ob der reguläre Ausdruck zu dem String passt oder nicht und gibt entsprechend true oder false zurück. =~ prüft, ob der Ausdruck passt und gibt die erste passende Position im String zurück, sie ist also im Allgemeinen nützlicher als !~.

Zeichenklassen

Bearbeiten

Eine erste Form der Abstraktion regulärer Ausdrücke ist es, Zeichenklassen zu definieren. Möchte man Beispielsweise prüfen, ob ein String Ziffern enthält, dann ist es unzweckmäßig, auf jede einzelne Ziffer zu prüfen, sondern kann dies wie folgt tun:

s = "abc0123"
if s =~ /[0-9]/
  puts "Enthält Ziffern"
end

Zeichenklassen werden mit eckigen Klammern begrenzt und es ist möglich, Bereiche von Ziffern und Buchstaben durch - anzugeben. Wenn der Bindestrich das erste Zeichen der Klasse ist, so wird es nicht als Sonderzeichen interpretiert. Der reguläre Ausdruck beschreibt eine beliebige Ziffer zwischen 0 und 9. Zeichenklassen ersetzen in ihrer unmodifizierten Form nur ein Zeichen. Möchte man zum Beispiel prüfen, ob ein String nur Ziffern enthält, braucht man zusätzliche Sonderzeichen.

Sonderzeichen

Bearbeiten

Sonderzeichen sind Zeichen innerhalb eines regulären Ausdrucks, welche die Bedeutung der anderen Zeichen verändern. Die Wichtigsten von ihnen sind in der folgenden Tabelle erklärt. Diese Tabelle ist natürlich nicht vollständig, insbesondere fehlen zahlreiche Escapesequenzen, diese ersetzen unter anderem Zeichenklassen (/\d/ für Ziffern) oder erfüllen andere Aufgaben (/\b/ für Wortgrenze).

Zeichen Erklärung
+ Das vorhergehende Zeichen kommt mindestens einmal aber belieig oft vor.
* Das vorhergehende Zeichen kommt beliebig oft vor.
? Das vorhergehende zeichen ist optional.
^ Verneinung der nachfolgenden Zeichenklasse
. Beliebiges Zeichen
^ Zeilenanfang
$ Zeilenende

Wenn Sie bereits mit regulären Ausdrücken vertraut sind, dann wird Ihnen die Benutzung von ^ und $ als Zeilenanfang und -beginn eventuell komisch vorkommen. In Ruby ist der Multilinemode von regulären Ausdrücken standardmäßig aktiviert. Wenn Sie sicher gehen wollen, dass ein regulärer Ausdruck tatsächlich auf den gesamten String zutrifft, dann können die Escapesequenzen \A und \z angewendet werden.

Daneben gibt es noch zahlreiche andere Escapesequenzen, deren Aufzählung unübersichtlich und nicht zielführend ist. Es soll daher auf entsprechende Spezialliteratur verwiesen sein.

Die Variablen $1, $2, ...

Bearbeiten

Betrachten Sie einmal folgenden Sourcecode:

s = "Hallo"
if s =~ /(H|h)allo/
  puts "#{s} ist kleingeschrieben" if $1 == "h"
  puts "#{s} ist grossgeschrieben" if $1 == "H"
end

Drei neue Dinge wurden hier eingefügt. Das erste ist die Alternation /(H|h)/. Sie trifft zu, wenn einer der beiden Teilausdrücke zutrifft (wie das logische ||), dabei können die Teilausdrücke wieder selbst beliebig komplexe reguläre Ausdrücke sein. Die Klammern dienen hier zum einen zur Begrenzung der Alternation und zum anderen sind dies sogenannte einfangende Klammern. Das bedeutet, dass der entsprechende Treffer in den besonderen Variablen $1, $2, usw. gespeichert wird, je nachdem wie viele einfangende Klammern es gibt.

Named Groups

Bearbeiten

Reguläre Ausdrücke können sehr schnell unübersichtlich werden und auch die Verwendung der pseudoglobalen Variablen $1, $2, etc. trägt nicht zu einer besonderlich hohen Lesbarkeit des Programms bei.

In Ruby 1.9 wurde daher den regulären Ausdrücken eine Möglichkeit hinzugefügt, dass man einfangende Klammern bennent und somit sowohl Teile des Ausdrucks besser lesbar macht, als auch die Treffer in Variablen speichert.

Im folgenden Beispiel werden Usernamen eines Programms nach folgendem Schema in einem Array als String gespeichert: id: name. Zum Extrahieren der beiden Daten aus dem String wird ein regulärer Ausdruck benutzt und das Ergebnis ausgegeben.

users = ["1: Paul", "2: Max"]

r = /(?<id>\d+): (?<name>\w+)/i

users.each do |u|
  result = r.match(u)
  puts "#{ result[:id] }. #{ result[:name] }"
end

Längenkonverter

Bearbeiten

Mit regulären Ausdrücken ist es einfach, Benutzereingaben zu analysieren und zu verarbeiten. In dieser Aufgabe wollen wir ein Programm entwickeln, das es ermöglicht, zwischen Zoll und Zentimetern umzurechnen. Während die eigentliche Umrechnung (1 Zoll sind 2,54 Zentimeter) kein Problem ist, stellt die Interaktion mit dem Benutzer das größere Problem dar, welches sich mit regulären Ausdrücken dann wiederum sehr schön implementieren lässt.

Die Benutzereingabe soll dem folgenden Format entsprechen: Zunächst soll der Wert (wenn Dezimalzeichen, dann als Punkt) eingegeben werden, der durch beliebig viele Whitespaces von der Einheit getrennt wird (Zum Beispiel 0.3 I, oder 32cm).

In regulären Ausdrücken bedeutet das:

  • /^\d+/ Nach dem Stringanfang kommen beliebig viele Ziffern, mindestens jedoch eine
  • /\.?\d*/ Optional folgt dann ein Punkt gefolgt von beliebig vielen Ziffern
  • /\s*/ Die Escapesequenz \s steht für beliebigen Whitespace, also neben Leerzeichen auch Tabulatoren, Zeilenumbrüche, ...
  • /(i|cm)/i Auswahl der Einheit als entweder i für Inch oder cm für Zentimeter. Der Parameter /i verändert die Regexp, sodass sie unabhängig von Groß- und Kleinschreibung zutrifft.

Der gesamte Ausdruck inklusive benannten Klammern lautet: /^(?<val>\d+\.?\d*)\s*(?<unit>(i|cm))/i. Und der dazugehörige Quelltext:

#Encoding: utf-8

r = /^(?<val>\d+\.?\d*)\s*(?<unit>(i|cm))/i

loop do
  puts "Bitte geben Sie eine Länge ein.\nEine leere Eingabe beendet das Programm."
  input = gets.chomp
  break if input.empty?
  
  if (result = r.match(input))
    if result[:unit] =~ /i/i
      inch = result[:val].to_f
      sicm = result[:val].to_f/2.54
    else
      sicm = result[:val].to_f
      inch = result[:val].to_f*2.54
    end
    puts "#{ sicm } cm = #{ inch } I"
  else
    puts "#{ input } ist eine fehlerhafte Benutzung dieses Programms."
  end
end