Virtual OS/2 International Consumer Education
VOICE-Homepage: http://de.os2voice.org
Januar 2004

Inhaltsverzeichnis
< Vorherige Seite | Nächste Seite >
Artikelverzeichnis

editor@os2voice.org


DrDialog, oder wie ich lernte, REXX zu lieben - Teil 11

Von Thomas Klein © Januar 2004

Willkommen zurück zu unserer Serie über REXX und DrDialog. Für die Unterbrechung entschuldige ich mich, aber ich brauchte eine kleine Auszeit, um die anderen Dinge zu erledigen, die sich um mich herum zu stapeln begannen. Wie dem auch sei - hier sind wir also wieder. Zu unserem letzten Teil, der von Schleifenkonstrukten handelte, gibt es übrigens noch eine Sache, die mir nachträglich eingefallen ist:

Verändern Sie niemals manuell eine Schleifen- (bzw. Zähler-) variable!
Obwohl dies in manch anderen Programmiersprachen verwendet wird, um eine Schleife vorzeitig zu verlassen, sollten Sie in REXX von solchen "Tricks" Abstand nehmen, da dies unter Umständen zu nicht vorhersehbaren (bzw. erwünschten) Ergebnissen führen könnte. Verwenden Sie stattdessen die dafür eigens vorgesehene EXIT-Anweisung oder prüfen Sie, ob eine andere Schleifenkonstruktion besser geeignet ist.

Heute widmen wir uns der Fülle an Funktionen, die REXX für das Arbeiten mit Zeichenketten ("strings") bereitstellt. Allerdings werden wir nicht die vollständige Liste abarbeiten, da einige Funktionen so spezifisch sind, daß Sie nur sehr selten damit werden arbeiten müssen. Sobald die Zahl Ihrer selbst geschriebenen Programme zunimmt, werden Sie entdecken, daß einige Funktionen ständig darin auftauchen und andere weniger. Es hängt immer von Ihrem persönlichen Ansatz ab, ein Problem zu lösen. Um eine gewisse thematische Gruppierung zu haben, ordnen wir die Funktionen in folgende Gruppen:

Falls Sie den Artikel kurz überfliegen, werden Sie sich bestimmt wundern, wo die Anweisung PARSE denn geblieben ist. Nun, PARSE ist wohl einen eigenen Artikel wert, schätze ich. Diese Anweisung alleine ist ein mächtiger Bestandteil von REXX - sowohl in Hinblick auf Funktionalität als auch Komplexität. In einem späteren Teil der Serie werden wir uns ausführlich mit den grundlegenden Funktionen von PARSE beschäftigen. Natürlich kann PARSE noch einiges mehr, als das, was wir dann besprechen werden, aber da diese Serie sich in erster Linie an REXX-Anfänger richtet, halte ich es für besser, Sie nicht mit zu viel Details und Tricks zu verwirren. Selbstverständlich kann man nahezu jeder Anforderung gerecht werden, wenn man PARSE bis in den "letzten Winkel" beherrscht, aber die meiste Zeit werden Sie sich nur mit einem Bruchteil des Funktionsumfangs beschäftigen müssen und das ist es auch, was wir an der Stelle tun werden.

Informationen zu Strings

Bevor man sich mit dem Inhalt von Zeichenketten ("Strings") einläßt, sollte man einige Informationen zu ihnen zusammentragen.

LENGTH gibt Aufschluß darüber, wie viele Bytes (bzw. Zeichen) in einem String enthalten sind. Dies beinhaltet sowohl führende als auch folgende Leerzeichen:

/* length beispiel */
text = " Ich bin 97 jahre alt.  "
say length(text)

Wenn Sie dieses Skript ausführen, wird 24 angezeigt.

VERIFY wird verwendet um zu prüfen, ob ein String bestimmte Zeichen enthält oder nicht. Damit das klappt, gibt man neben dem zu prüfenden String einen weiteren an, der die "Vergleichszeichen" enthält sowie zusätzliche Optionen. Beispielsweise können Sie damit prüfen, ob ein String eine gültige Telefonnummer enthält - d.h., ob nur die Ziffern 0 bis 9, Leerzeichen, Bindestrich und/oder das Pluszeichen (für internationale Schreibweise) enthalten sind. Der Vergleichsstring würde dann wie folgt aussehen:

"0123456789 -+"

Nun kann VERIFY sowohl dazu verwendet werden zu prüfen, ob der String nur aus den angegebenen Vergleichszeichen besteht oder keine der Zeichen enthält. Im Prinzip gibt VERIFY lediglich die Position des Zeichens im zu prüfenden String zurück, die einem bzw. keinem der Vergleichszeichen entspricht. (Je nachdem, welchen Vergleichstyp Sie gewählt haben). Ein bißchen verwirrend, hm? Okay:

/* VERIFY beispiel */
vergleich = "0123456789 -+"
PARSE PULL tel
if VERIFY(tel, vergleich, "NOMATCH") = 0 then
   say "Telefonnr. ist okay."
else
   say "Telefonnr. enthält ungültige Zeichen"

Wenn im obigen Beispiel jemand +44-123 456 / 789 eingeben würde, würde VERIFY 13 zurückgeben, da das 13. Zeichen ("/") nicht im Vergleichsstring enthalten ist. Folglich gilt, daß VERIFY in Verbindung mit dem Parameter NOMATCH zurück gibt, welches Zeichen nicht im Vergleichsstring enthalten ist. Wird daraufhin eine 0 (Null) zurückgegeben, bedeutet das, daß kein Zeichen enthalten ist, welches nicht auch im Vergleichsstring enthalten ist... oder daß die Nummer quasi okay ist, um es so zu sagen.
Verwendet man stattdessen den Vergleichstyp "MATCH", gibt VERIFY die Position des ersten Zeichen im Prüfstring zurück, welches auch im Vergleichsstring enthalten ist. Welchen der beiden Vergleichstypen Sie verwenden, hängt davon ab, was in Ihrer Programmlogik besser geeignet ist (bzw. was einfacher umzusetzen ist). Meistens werden Sie aber wohl den Typ NOMATCH bevorzugen, da dieser einfacher zu lesen und seine Arbeitsweise quasi intuitiv zu verstehen ist. Hier noch einige Hinweise zu VERIFY - die vollständige Syntax lautet:

ergebnis = VERIFY ( <prüfstring>, <vergleichsstring> [, "MATCH" | "NOMATCH" ] [, START] )

Vom Parameter "MATCH" bzw. "NOMATCH" braucht nur der erste Buchstabe angegeben zu werden und es ist egal, ob dies in Groß- oder Kleinbuchstaben erfolgt. Außerdem gibt's da noch den optionalen START-Parameter, mit dem angegeben werden kann, ab welcher Stelle (Zeichen) der Prüfstring verglichen werden soll. Standardmäßig beginnt der Vergleich mit dem ersten Zeichen, aber je nachdem, wie Sie Ihren Prüfstring zusammenbasteln, könnte es von Vorteil sein, bestimmte führende Zeichen nicht in den Vergleich einzuschließen. Als Beispiel dafür stellen Sie sich ein Adreßbuchprogramm vor, welches intern mit Einträgen in der Form "Müller,Peter/123-4567" arbeitet. Um nun die enthaltene Telefonnummer zu prüfen, können Sie VERIFY mitteilen, diesen Vergleich erst ab Stelle 14 durchzuführen:

if VERIFY(tel, vergleich, "NOMATCH",14) = 0 then

Oder "abgekürzt" in der Form:

if VERIFY(tel, vergleich, "N", 14) = 0 then

Da "Nomatch" der Standardwert für den Vergleichstyp ist, können Sie ihn im obigen Beispiel auch einfach "unterschlagen". Falls Sie in solchen Fällen aber mit dem optionalen Parameter START arbeiten, müssen Sie dennoch ein zusätzliches Komma davor angeben, damit der REXX-Parser "versteht", daß der Vergleichstyp-Parameter wirklich "ausgelassen" wurde und "14" der START-Parameter ist:

if VERIFY(tel, vergleich, ,14) = 0 then

Falls Sie den kompletten String ab der ersten Stelle unter Verwendung von "Nomatch" prüfen wollen, verwenden Sie quasi alle Vorgabewerte und Sie können den ganzen Rest weglassen. Sie geben also einfach ein:

if VERIFY(tel, vergleich) = 0 then

Bezüglich des Beispiels von Herrn Peter Müller fragen Sie sich vielleicht, was Sie denn mit dem START-Parameter machen sollen, wenn ein Name verwendet wird, der länger oder kürzer ist. Eine gute Frage. Dafür benötigen wir eine zusätzliche Funktion, die wir gleich kennenlernen werden. Für's erste schauen wir uns noch die letzte Funktion der Gruppe "String-Informationen" an.

WORDS ist eine sehr nützliche Funktion. Das Konzept der "Wörter" in Strings, daß REXX bietet, ist verglichen mit den Stringfunktionen in BASIC z.B. fast schon der Himmel auf Erden für Programmierer. Ein Wort ist ein Teil eines Strings, der entweder durch Leerzeichen und/oder den Anfang bzw. das Ende des Strings begrenzt wird. Unter den WORD-Funktionen ist WORDS noch mit die einfachste: Sie gibt die Anzahl der in einem String enthaltenen (erkannten) Wörter zurück:

/* words beispiel */
text = "Dies ist ein words() Beispiel. "
say words(text)

WORDS würde in diesem Fall folgende Teilstrings erkennen: Dies   ist   ein   words()   Beispiel.
Demnach würde WORDS für das obige Beispiel eine 5 zurückgeben.
Um zu verdeutlichen, um was es bei WORDS geht, lassen Sie es mich so erklären: Wenn Sie eine Zeichenkette anschauen, dann erkennen Sie die Wörter darin, richtig? Die Funktion WORDS() arbeitet ziemlich genau in derselben Weise. Solange mindestens ein Leerzeichen zwischen zwei anderen Strings steht, werden diese als zwei einzelne Wörter erkannt. Es spielt also auch keine Rolle, ob diese Wörter durch - sagen wir mal - zwanzig Leerstellen getrennt sind. Es sind trotzdem immer noch zwei Wörter. Eine Ausnahme ist vielleicht, daß Sie einen einzelnen Punkt nicht als Wort erkennen würden. Bei WORDS() hingegen ist das der Fall, wenn dieser Punkt entsprechend durch Leerzeichen und/oder Stringende begrenzt ist, wie z.B. in
"Es   gibt  57 Programme, aber   nichts  vernünftiges  . "
WORDS() würde hier 8 Wörter erkennen - beachten Sie den getrennt stehenden Punkt.

Substrings (Teilzeichenketten) und Suchfunktionen

Von allen Stringfunktionen sind dies jene, die man am meisten verwendet - zumindest gilt das für mich. Lassen Sie uns mit einer relativ einfachen beginnen:
POS wird verwendet, um die Stelle in einem String zu finden, an der ein bestimmter anderer String beginnt. IBM verwendet in der REXX-Hilfe das "Nadel und Heuhaufen"-Prinzip - das ist eine ziemlich gute Methode, um sich die Syntax zu merken:

ergebnis = POS( <nadel>, <heuhaufen> [, START])
POS

sucht im <heuhaufen>-String nach der ersten Stelle, die <nadel> enthält. Entweder wird dann die Startposition von <nadel> innerhalb <heuhaufen> zurückgegeben (wobei 1 der erste Buchstabe ist usw.) oder eine 0 (null), wenn <nadel> in <heuhaufen> nicht gefunden wurde. Optional können Sie POS auch eine Position mitgeben, ab der <heuhaufen> durchsucht werden soll - standardmäßig ist das 1, also die erste Stelle bzw. der erste Buchstabe. Das ist zum Beispiel dann nützlich, wenn Sie bestimmte Teilstrings aufstöbern möchten - obwohl die WORD-Funktionen, die wir gleich besprechen, hierbei einen erheblich besseren Dienst verrichten.
Stellen Sie sich beispielsweise einmal vor, daß Sie einen String namens "datensatz" haben, der folgendes enthält:

"vorname=Peter, nachname=Müller, tel=123-45678"

Wenn Sie daraus jetzt die Telefonnummer benötigen (und wir davon ausgehen, daß es nur eine gibt und diese immer am Ende von 'Datensatz' steht), würden Sie mit folgender Anweisung

telbeginn = POS("tel=", datensatz)

die Startposition des gesuchten Teilstrings innerhalb 'datensatz' erhalten. Oder eine NULL, falls keine Angabe enthalten ist. Wenn Sie also irgendetwas anderes als eine Null zurückbekommen, addieren Sie 4 (die Länger von "tel=") zu diesem Wert und Sie wissen, an welcher Stelle innerhalb von 'datensatz' die Telefonnummer beginnt. Dann bräuchten Sie nur noch die Länge der Telefonnummer zu bestimmen und schon könnten Sie sich diese in einem anderen String übertragen - allerdings benötigen Sie hierfür noch eine andere Funktion ("substr"), die wir erst weiter unten behandeln.
Persönlich verwende ich diese Funktion meist nur zur Prüfung, ob ein bestimmter String in einem anderen überhaupt enthalten ist - egal wo er denn steht, wie z.B. in:

IF POS("/?", parameterstring) \= 0 then call DisplayHelp

...was soviel bedeutet wie "wenn 'parameterstring' ein '/?' enthält, rufe die Funktion DisplayHelp auf (die dann wohl einen klugen Text ausgibt)".

LASTPOS macht so ziemlich dasselbe, allerdings sucht man hiermit den <heuhaufen> von hinten nach vorne durch - also rückwärts. Diese Funktion verwendet dieselben Parameter (START) und gibt auch dieselbe Information zurück. LASTPOS ist der komfortable Weg, die letzte Stelle im <heuhaufen> zu finden, an der eine <nadel> herumliegt. Natürlich könnte man hierzu auch eine variable Abfolge von POS-Aufrufen verwenden, deren START-Parameter jeweils auf die vorher gefundene Stelle angepaßt wird, aber hey: Wozu unnötig komplizieren?
Persönlich verwende ich LASTPOS meist dann, wenn ich es mit voll-qualifizierten Dateinamen zu tun habe (also Dateinamen, die Laufwerk- und volle Pfadangabe enthalten). Sobald ich die Position des letzten backslash ("\") kenne, weiß ich, daß alles "dahinter" der Dateiname sein muß und alles "davor" entsprechend die Laufwerks- und Pfadangabe. Ja - ich könnte natürlich auch die Funktion FILESPEC() verwenden, aber je nach Programmsituation benötigt man manchmal eben andere Informationen wie bspw. den Namen der letzten Verzeichnisebene...

Die "WORD-Funktionen" (word, wordpos, wordindex, wordlength und subword) sind extrem nützlich, wenn man sich mit Strings herumschlagen muß, die aus mehreren, durch ein oder mehrere Leerzeichen voneinander getrennten Teilstrings bestehen. Wenn Sie jemals versucht haben, solche Strings "von Hand" zu zerlegen, z.B. in einem älteren BASIC-Dialekt, dann werden Sie nach der Lektüre der folgenden Absätze wohl zustimmen, daß man sich mit REXX' Funktionsumfang fast schon wie im "Programmiererparadies" fühlt. ;)
Als Beispiel für alle folgenden Abschnitte (soweit nicht anders vermerkt), nehmen wir an, daß wir einen String namens "eingabe" vorliegen haben, der mehrere Teilstrings (bzw. "Wörter") hat, die durch eine unbekannte Anzahl von Leerstellen getrennt sind... also um es kurz zu machen: "Mary  hat    5  kleine Lämmer."

WORDS (wie wir bereits wissen) gibt uns die Anzahl der "Teile" (also "Wörter") zurück, die im String enthalten sind.

SAY WORDS(eingabe)

würde ausgeben:  5
WORD wird verwendet, um ein einzelnes Wort aus dem String zu ermitteln, wobei es gleichzeitig noch von führenden und folgenden Leerstellen "befreit" wird. Damit das funktioniert, müssen Sie WORD natürlich auch sagen, welches Wort ermittelt werden soll, indem Sie einfach die "Nummer" des Worts innerhalb des Strings angeben (1 für das erste, 2 für das zweite usw.). Somit würde also

SAY WORD(eingabe, 2)

folgendes ausgeben: hat

WORDPOS funktioniert ähnlich wie das weiter oben beschriebene POS - mit der Ausnahme, daß es nicht zeichenorientiert arbeitet, sondern eben auf Basis von Wörtern: Der <heuhaufen> wird nach der ersten (von links nach rechts) auftretenden <nadel> durchsucht. Als Rückgabewert erhält man entsprechend die "Nummer" des Wortes in <heuhaufen>, das der <nadel> entspricht. Genau wie bei POS, kann auch WORDPOS mittels eines optionalen Parameters dazu bewegt werden, den <heuhaufen> nicht vom Anfang, sondern erst ab einer bestimmten Stelle zu durchsuchen. Auch hierbei handelt es sich wiederum um eine "Wortnummer". Die vollständige Syntax lautet also:

ergebnis = WORDPOS(<nadel>, <heuhaufen> [ ,START ] )

Beachten Sie, daß <nadel> mit dem zu suchenden Wort in <heuhaufen> exakt übereinstimmen muß - das bedeutet, auch die Groß-/Kleinschreibung muß identisch sein.

SAY WORDPOS("HAT", eingabe)

würde daher nur 0 ausgeben, da "HAT" nicht identisch mit "hat" ist... wohingegen

SAY WORDPOS("hat", eingabe)

ausgeben würde: 2
Weiter ist auch zu beachten, daß mehr als nur ein Wort als <nadel> verwendet werden kann. In diesem Fall behandelt WORDPOS den Inhalt von <nadel> genau wie alle WORD-Funktionen den <heuhaufen> behandeln: Die Inhalte werden intern in einzelnen Wörtern abgelegt. Beispiel:

SAY WORDPOS("hat 5", eingabe)

gibt aus: 2
Dies funktioniert also, obwohl 'eingabe' tatsächlich "Mary  hat    5  kleine Lämmer." enthält (wobei 4 Leerstellen zwischen 'hat' und '5' stehen), unsere <nadel> jedoch nur 1 Leerstelle enthält. Da jedoch <nadel> und <heuhaufen> jeweils als Folge von voneinander getrennten Wörtern behandelt werden, funktioniert der Vergleich.

WORDINDEX ermittelt die Anfangsposition eines bestimmten Wortes innerhalb des kompletten Strings - mit eingerechnet aller evtl. davor vorhandenen Leerstellen.

SAY WORDINDEX(eingabe, 2)

würde demnach ausgeben: 7

Die SUBSTR Funktion ermittelt einen Teilstring eines Strings auf Basis von Zeichenzahl und Länge:

SAY SUBSTR(eingabe, 3, 17)

gibt beispielsweise den Teil von 'eingabe' zurück, der mit dem dritten Zeichen beginnt und 17 Zeichen lang ist. Das wäre also
ry  hat    5  kle
Falls Sie mit der MID$-Funktion von BASIC vertraut sind, beachten Sie, daß im Gegensatz dazu SUBSTR nicht zum Verändern eines Strings verwendet werden kann, sondern lediglich zur Abfrage. Optional kann SUBSTR auch dazu bewegt werden, "nicht existierende" Teile des Teilstrings mit einem angegebenen Zeichen zu füllen. "Nicht existierende Teile" sind in diesem Fall solche, die außerhalb des eigentlichen Strings liegen. Beispiel? Wenn Ihr Programm die Zeichen 3, 4 und 5 eines Strings auswertet, und dieser String zufälligerweise gerade nur einmal 3 Zeichen lang ist erhalten Sie keine Fehlermeldung! Statt dessen erhalten Sie Zeichen 3 und zwei Leerstellen - denn der Vorgabewert (falls Sie kein Füllzeichen angeben) ist das Leerzeichen. Wenn Sie also ein Füllzeichen angeben würden, erhielten Sie Zeichen 3 und zwei Füllzeichen zurück:

SAY SUBSTR(eingabe, 25, 10)

würde ausgeben: "ämmer.    "
(ohne die Anführungszeichen - die habe ich nur hinzugefügt, damit man die angehängten Leerzeichen erkennt.)

SAY SUBSTR(eingabe, 25, 10, "-")

würde ausgeben: "ämmer.----"
Eine nette Eigenschaft von SUBSTR ist übrigens, daß, wenn man keinen Längenparameter angegeben hat, der ganze "Rest" ab der angegebenen Stelle ermittelt wird:

SAY SUBSTR(eingabe, 17)

gibt demnach aus: kleine Lämmer.

Die beiden Stringfunktionen, die ich selbst in fast jedem Programm irgendwie verwende sind left und right. Hiermit wird ein Teilstring in der angegebenen Länge ermittelt, wobei entweder vom rechten oder linken Ende des Ausgangsstrings begonnen wird - je nachdem, welche der beiden Funktionen man eben verwendet:

SAY LEFT(eingabe, 7)

würde "Mary  h" ausgeben

SAY RIGHT(eingabe, 7)

entsprechend "Lämmer." ausgeben würde (beide Male jeweils ohne die Anführungszeichen natürlich).
Genau wie bei SUBSTR, können nicht vorhandene Bereiche mit left und right aufgefüllt werden (also solche, die außerhalb des zu verarbeitenden Strings liegen). Hierfür werden ebenfalls standardmäßig Leerzeichen verwendet, wenn kein anderes explizites Füllzeichen angegeben wird wie beispielsweise in

SAY LEFT("abcdefg",10,"-")

Das würde dann nämlich resultieren in der Ausgabe von abcdefg---

SUBWORD arbeitet ähnlich wie SUBSTR, da auch hier mit Startposition und Länge gearbeitet wird. Abgesehen von der Tatsache, daß hier nun wieder auf Basis von Wörtern statt Zeichen ermittelt wird, gibt es noch ein paar weitere Unterschiede: Hier findet kein Auffüllen von nicht vorhandenen Bereichen statt wie in SUBSTR. Wir erinnern uns, daß unser String 'eingabe' fünf Wörter enthält. Wenn wir nun die Wörter 4, 5 und 6 aus 'eingabe' ermitteln wollen, indem wir codieren

SAY SUBWORD(eingabe, 4, 3)

würde uns die Funktion schlicht "kleine Lämmer."
ausgeben (natürlich wieder ohne die Anführungszeichen - die habe diesmal eingebaut, damit man erkennt, daß keine Füllzeichen vorhanden sind).
Ein weiterer wichtiger Punkt hierbei ist, daß SUBWORD die zwischen den zu ermittelnden Wörtern enthaltenen Leerzeichen genau so zurück gibt, wie sie im Ausgangsstring enthalten sind - das bedeutet also, daß hier intern keine Aufteilung in einzelne Wörter passiert:

SAY SUBWORD(eingabe, 1, 3)

würde demnach ausgeben:  "Mary  hat    5"
Allerdings wird wie in SUBSTR der ganze "Rest" des Ausgangsstrings ab der angegebenen Startposition (Startwort) zurückgegeben, wenn man den Längenparameter einfach weg läßt.

WORDLENGTH schließlich ermittelt, aus wie vielen Zeichen ein angegebenes Wort besteht:

SAY WORDLENGTH(eingabe, 3)

würde also (für das Wort "5") ausgeben: 1

Strings erzeugen oder verändern

Abgesehen davon, daß man neue Strings dadurch erzeugt, daß man Teile vorhandener Strings mittels bestimmter Funktionen ermittelt, gibt es noch andere Wege, das zu erreichen. Was übrigens das "Verändern" von Strings betrifft, muß ich gestehen, daß wir so etwas in Wirklichkeit gar nicht machen, sondern vielmehr auch hier neue Strings aus anderen erstellt werden. Manchmal sieht es so aus, als ob das anders wäre, wenn wir beispielsweise das Ergebnis sofort in die Ausgangsvariable zurücklegen, wie in

meintext = left(meintext, 5)

aber im Prinzip "ändern" wir den String nicht wirklich: Es wird "gleichzeitig" intern ein neuer String erzeugt und sofort wieder zurück übertragen. Aber das ist jetzt nicht so wichtig - machen wir lieber weiter mit dem Artikel.

COPIES erzeugt einen String, indem eine Verkettung mehrerer Kopien eines anderen Strings erfolgt:

SAY COPIES("bla", 3)

würde beispielsweise ergeben: blablabla
Na super. COPIES ist meistens dann recht praktisch, wenn man im VIO-Modus mit schönen Trennzeilen arbeiten will, die eine bestimmte Länge haben sollen:

SAY COPIES("-", 18)

ergibt dann ------------------

XRANGE ist extrem nützlich, wenn man mit Zeichenumsetzung arbeitet. Wie Sie wahrscheinlich wissen, ist jedes Zeichen Bestandteil einer "Zeichentabelle" und wird darin durch einen Index (eine Nummer) identifiziert. Wir nennen das 'mal die "ASCII-Tabelle". XRANGE macht sich diese Tatsache zunutze und erzeugt einen String, der aus einer Abfolge von Zeichen besteht, die sich an der Reihenfolge der Zeichentabelle orientiert und durch Start- und Endzeichen angegeben wird:

kleinesalphabet = XRANGE("a", "z")
SAY kleinesalphabet

gibt beispielsweise aus: abcdefghijklmnopqrstuvwxyz
Beachten Sie, daß die ASCII-Tabelle aus 256 Zeichen besteht (mit den Nummern 0 bis 255). Wenn Sie jetzt die komplette Tabelle anzeigen wollen, müssen Sie die Zeichen in Hex-Schreibweise angeben, da es sich bei Zeichen Nummer 0 und Zeichen Nummer 255 um nicht-druckbare Zeichen (und somit auch nicht-eingebbare) handelt:

SAY XRANGE("00"x, "FF"x)

zeigt Ihnen den kompletten ASCII-Zeichensatz an (sofern die jeweiligen Zeichen am Bildschirm darstellbar sind...).
Beachten Sie bitte auch, daß, wenn das Endzeichen in der ASCII-Tabelle "vor" dem angegebenen Startzeichen steht, XRANGE eine kurze Runde dreht: Zunächst werden die Zeichen beginnend mit dem Startwert ausgegeben bis zum Zeichen Nummer 255, dann wird bei Zeichen 0 fortgesetzt und alle Zeichen bis zum Endzeichen werden ausgegeben. Sie bekommen also nicht etwa die "umgekehrte" Zeichenfolge, sondern vielmehr etwas, das Sie nicht unbedingt so erwartet hätten...;)

STRIP kennen wir schon aus anderen Beispielen: Hiermit werden führende und folgende Zeichen aus einem String entfernt. Oder vielmehr, wie ich weiter oben bereits erklärt habe, wird ein neuer String erzeugt, der diese Zeichen nicht mehr enthält. Standardmäßig (wenn nichts anderes angegeben) werden hiermit Leerzeichen entfernt, man kann aber auch andere Zeichen angeben. Außerdem kann auch angegeben werden, ob nur führende, nur folgende oder beide Typen entfernt werden sollen. Hierfür ist der Standardwert "Beide".

SAY STRIP("  Mary.  ")

würde ausgeben Mary.
Dies passiert genau genommen deshalb, weil keine optionalen Parameter angegeben wurden und somit die Vorgabewerte "entferne führende und folgende Leerzeichen) zur Geltung kommt.

SAY STRIP("0012.850", "L", "0")

würde ergeben (anzeigen) 12.850 während

SAY STRIP("0012.850", , "0")

12.85 ausgeben würde.
Beachten Sie im obigen Beispiel, daß auch hier wieder das Komma explizit angegeben werden muß, damit REXX' Parser versteht, daß es sich bei "0" um das zu entfernende Zeichen handelt und der Typ-Parameter "unterschlagen" wurde. Würde man stattdessen

SAY STRIP("0012.850", "0")

schreiben, erhielte man eine Fehlermeldung, denn "0" würde dann (zu Recht) als Typ-Parameter (führend/folgend) erkannt, was leider nicht erlaubt ist da nur "L", "T" und "B" erlaubt sind. Eigentlich lauten die Werte für den Typ-Parameter "Leading" (führend), "Trailing" (folgend) bzw. "Both" (beide), aber wer hat schon die Zeit, alle diese Buchstaben zu tippen, wenn es auch genügt, jeweils den ersten anzugeben? ;)

INSERT sieht bei erster Betrachtung ziemlich komplex aus. Die vollständige Syntax lautet

ergebnis = INSERT ( <eingabe> , <text> [, START ] [, LÄNGE ] [, FÜLLZEICHEN ] )

Im Prinzip wird damit ein angegebener Text in einen anderen eingefügt unter Angabe der Einfügeposition, also genau so, als wenn Sie in einem Texteditor den Cursor an eine bestimmte Stelle setzen und anfangen würden, Text einzugeben:

SAY INSERT("123", "abcde", 3)

würde beispielsweise ausgeben: abc123de
Beachten Sie, daß der Vorgabewert für START 0 (null) ist, d.h. der einzufügende Text würde dann vor den bestehenden Zieltext geschrieben:

SAY INSERT("123, "abcde")

ergibt 123abcde
Solange die Standardwerte für Sie ausreichen, müssen Sie also nichts weiter beachten. Wenn Sie allerdings etwas mehr Funktionalität brauchen, müssen Sie verstehen, wie sich die optionalen Parameter LÄNGE und FÜLLZEICHEN auf das Verhalten der Funktion auswirken... also: Mit LÄNGE geben Sie den Platz an, den der einzufügende Text <eingabe> im Zieltext <text> belegen soll. Standardmäßig wird zum Auffüllen des nicht-belegten Bereichs dann das Leerzeichen verwendet...

SAY INSERT("123", "abcde", 3, 5)

würde also ergeben abc123  de
Wenn Sie ein (abweichendes) Füllzeichen angeben, wird dieses zum Auffüllen der durch LÄNGE angegebenen Zeichen verwendet, die von <eingabe> nicht belegt werden, wie in:

SAY INSERT(123, "abcde", 3, 5, "#")

Dies ergibt dann entsprechend: abc123##de

DELSTR entfernt einen Teilstring aus einem anderen String. Im Prinzip arbeitet diese Funktion genau wie ihr Texteditor, wenn Sie den Cursor an eine bestimmte Position stellen, eine Anzahl von Zeichen markieren und dann die Entf-Taste drücken würden. Es werden dieselben Parameter verwendet wir in SUBSTR - nämlich Startposition und Länge (in Zeichen):

SAY DELSTR("abcde", 3, 2)

würde ergeben: abe
Und genau wie bei SUBSTR, bedeutet auch hier ein fehlender Längenparameter, daß "der ganze Rest" ab der angegebenen Position gelöscht werden soll:

SAY DELSTR("abcde", 3)

bewirkt: ab

DELWORD ist das entsprechende Gegenstück zu DELSTR, wenn's um die WORD-Funktionen geht. Wir wenden uns wieder Mary und ihren Lämmern zu, damit Sie uns hilft, das Verfahren zu verdeutlichen:

SAY DELWORD(eingabe, 2, 2)

würde zwei Wörter aus 'eingabe' entfernen, beginnend mit dem zweiten Wort... übrig bliebe dann (die Ausgabe): Mary kleine Lämmer.
DELWORD führt kein internes "Parsen" aus- das bedeutet, daß die Leerstellen zwischen dem zu entfernenden Bereich und dem Rest nicht vollständig aufgelöst werden. Vielmehr werden nur der zu löschende Bereich inklusive der ihm folgenden Leerstellen beachtet. Um es zu verdeutlichen:

SAY DELWORD("abc   def  ghi   jkl", 2, 2)

ergäbe: abc   jkl
Warum ist eine Lücke von 3 Leerzeichen zwischen den beiden verbliebenen Wörtern? Das geschieht wir folgt: "def   ghi" sind die zwei zu löschenden Wörter. Zuzüglich der drei Leerstellen hinter "ghi". Also wird "def   ghi   " aus dem Text ausgeschnitten. Übrig bleiben "abc   " vor und "jkl" dahinter. Klebt man die zusammen, ergibt das "abc   jkl".

CENTER ist quasi eine spezielle Variante von INSERT. Hiermit wird ein String innerhalb eines neu zu erstellenden Strings zentriert. Die verbleibenden "Ränder" links und rechts können mit einem angegebenen optionalen Zeichen gefüllt werden. Damit lassen sich im VIO-Modus beispielsweise prima Überschriften erzeugen:

SAY CENTER("Mary's Lämmer", "20", "-")

will display ---Mary's Lämmer----
Lustige Sache. Hier 'mal ein kleines Programm für einen möglichen Verwendungszweck von COPIES und CENTER:

/* das mary beispiel */
lambname.0 = 5
lambname.1 = "itchy"
lambname.2 = "sparky"
lambname.3 = "joey"
lambname.4 = "samantha"
lambname.5 = "lou"
say center("Mary's Lämmerliste", 30, "-")
do i = 1 to lambname.0
     say center(lambname.i, 30)
end
say copies("-", 30)

Dieses unglaubliche, kleine Programm gibt dann folgendes aus:

------Mary's Lämmerliste------
            itchy
            sparky
             joey
           samantha
             lou
------------------------------

Super, oder? ;)
Sie fragen sich wahrscheinlich, was denn passiert, wenn man einen String in einem Bereich zentrieren will, der kleiner ist, als der String selbst, nicht wahr? Aber kein Sorge - nööö, es gibt keine Fehlermeldung, sondern das ganze wird einfach passend zurechtgestutzt:

SAY CENTER("Das ist nicht lustig!", 7)

gibt " nicht " aus.
Wenn eine gerade Anzahl von Zeichen auf eine ungerade Anzahl trifft (und eine exakte Zentrierung mit dem gleichen Abstand links und rechts nicht möglich ist), erfolgt eine Anpassung (hinzufügen oder entfernen eines Füllzeichens) grundsätzlich auf der rechten Seite.
Interessant ist übrigens, daß man diese Funktion sowohl als "center" als auch als "centre" aufrufen kann. Mann, das nenne ich "IBM-Qualität". ;)

REVERSE ist nicht wirklich abstrakt: Hiermit erhält man die umgekehrte Zeichenfolge zum übergebenen String. Wenn Sie also jemals wissen wollten, wie Ihr Vorname rückwärts geschrieben aussieht, versuchen Sie's einfach mal:

SAY REVERSE("thomas")

Natürlich müssen Sie 'thomas' durch Ihren Vornamen ersetzen, damit das Programm korrekt arbeitet. ;) Außer natürlich, Sie heißen tatsächlich auch Thomas.

SPACE
ist eine geniale Funktion für das Arbeiten mit "Wörtern". Hiermit können Sie erreichen, daß alle Wörter eines Strings automatisch mit einer identischen Anzahl Zeichen voneinander getrennt werden. Haben Sie schon einmal versucht, "manuell" die Wörter eines Strings zu ermitteln, um Sie danach mit jeweils einem Leerzeichen voneinander zu trennen? Das schaffen Sie in REXX mit einer einzigen Anweisung:

SAY SPACE(eingabe)

und schon erscheint: Mary hat 5 kleine Lämmer.
Aber natürlich verschafft uns das Verwenden der Vorgabewerte hier einen Vorteil, denn eigentlich müßte es ausformuliert heißen

SAY SPACE(eingabe, 1, " ")

damit klar ist, daß wir die Wörter durch jeweils ein (1) Leerzeichen (" ") trennen wollen. Aber warum nicht 'mal durch jeweils 2 Unterstriche voneinander trennen?

SAY SPACE(eingabe, 2, "_")

würde anzeigen: Mary__hat__5__kleine__Lämmer.
Bestimmt haben Sie schon gemerkt, daß also auch SPACE intern den String in einzelne Wörter zerlegt, um zum Ziel zu kommen. Diese Funktion eignet sich hervorragend zum "Normalisieren" von Benutzereingaben oder den Ausgaben anderer Programme, wenn Sie diese in einer bestimmten Form vorverarbeiten müssen. Beachten Sie auch, daß die Angabe von 0 (null) als Abstand dazu führt, daß alle angegebenen Trennzeichen entfernt werden:

SAY SPACE(eingabe, 0)

würde also anzeigen: Maryhat5kleineLämmer.

Ich muß zugeben, daß ich mich bis heute nie mit OVERLAY beschäftigt habe. Nachdem ich mir die Funktion aber jetzt im Rahmen des Artikels näher angeschaut habe (und scheinbar endlich verstehe, wofür sie gut ist) werde ich sie wahrscheinlich in Zukunft öfter verwenden.
Was OVERLAY eigentlich macht, ist nichts anderes als wie INSERT einen Text in einen anderen einzufügen (es werden sogar die identischen Parameter und Syntaxschemata verwendet) mit der Ausnahme, daß - um es so zu sagen - nicht der "Einfügemodus" sondern der "Überschreibmodus" beim Einfügen verwendet wird, falls Sie verstehen was ich meine. (Hätte man diese Funktion "OVERWRITE" genannt, hätte ich's auch sofort kapiert.):

SAY OVERLAY("=XYZ=", "01234567890", 4)

ergibt nämlich 012=XYZ=890
Wenn Sie die optionalen Parameter verwenden möchten, schauen wir uns erst noch 'mal das Syntaxdiagramm an:

ergebnis = OVERLAY ( <eingabe> , <text> [, START ] [, LÄNGE ] [, FÜLLZEICHEN ] )

Wenn Sie einen Wert für LÄNGE angeben, wird <eingabe> mit dem angegebenen <füllzeichen> auf die angegebene Länge aufgefüllt. Das vorgegebene Füllzeichen ist natürlich mal wieder das Leerzeichen, daher wird mit dem Befehl

SAY OVERLAY("=XYZ=", "01234567890", 4, 6)

folgendes angeziegt: 012=XYZ= 90
Der Vorgabewert für START ist 1, was also bedeutet, daß <text> ab dem ersten Zeichen durch <eingabe> überschrieben wird.

Zu guter Letzt hätten wir noch TRANSLATE, die auch eine "coole" Funktion für das Arbeiten mit Strings darstellt. Hiermit können Sie zwei Zeichentabellen definieren, die zum Austauschen der Zeichen eines Strings verwendet werden. Jedes Zeichen im String (das in der "Eingabetabelle" enthalten ist) wird durch das passende Gegenstück (in der "Ausgabetabelle") ersetzt. Beide Tabellen werden jeweils einfach in Form eines Strings angegeben. Die passenden "Gegenstücke" werden dabei durch die Position des Zeichens innerhalb der Tabellen ermittelt (Zeichen 1 in "Eingabetabelle" entspricht Zeichen 1 der "Ausgabetabelle" usw.).
Das Syntaxdiagram sieht wie folgt aus:

ausgabe = TRANSLATE( <eingabe> [, <ausgabe-tabelle>] [, <eingabe-tabelle>] [, FÜLLZEICHEN] )

Das FÜLLZEICHEN wird dazu verwendet, die <ausgabe-tabelle> auf die gleiche Länge wie die <eingabe-tabelle> zu bringen, falls diese größer sein sollte. Damit wird erreicht, daß für jede Position der <eingabe-tabelle> ein entsprechendes "Gegenstück" in der <ausgabe-tabelle> vorhanden ist. So weit so gut. Da es sich bei allen Parametern "hinter" <eingabe> um optionale Bestandteile handelt, werden Sie sich vielleicht fragen, was denn aus <eingabe> wird, wenn nichts weiter angegeben wird... ganz einfach: <eingabe> wird in Großbuchstaben umgewandelt und das war's:

SAY TRANSLATE("hallo")

ergibt folglich: HALLO
Die in <eingabe> enthaltenen Zeichen, für die es keine Übereinstimmung in der <eingabe-tabelle> gibt, werden unverändert nach <ausgabe> übernommen. Damit lassen sich prima unerwünschte Zeichen entfernen: Ersetzen Sie alle unerwünschten Zeichen durch Leerzeichen und verwenden Sie danach die Funktion SPACE. Beispielsweise könnten Sie so alle Vokale aus einem String entfernen::

ausgabe = TRANSLATE("hallo", copies(" ", 5), "aeiou")

Damit wird eine <eingabe-tabelle> erstellt, die alle Vokale enthält sowie eine <ausgabe-tabelle> von passender Länge, die also 5 Leerzeichen enthält. Somit wird also jeder Vokal durch ein Leerzeichen ersetzt. Dies ergäbe als Ausgabe "h ll ". Dann verwenden Sie noch die SPACE-Funktion mit einem Abstandswert von 0:

SAY SPACE(ausgabe, 0)

und schon erscheint hll Oder hier das Ganze als Einzeiler:

SAY SPACE(TRANSLATE("hallo", copies(" ", 5), "aeiou"), 0)

Okay, das ist vielleicht kein perfektes Beispiel. Aber stellen Sie sich vor, daß Sie ein mehrzeiliges Texteingabefeld haben und nun ermitteln möchten, wie viele Zeilen darin enthalten sind. Jetzt brauchen Sie nur alles außer "0D"x (was soviel bedeutet wie "Zeilenvorschub" bzw. "line feed" / "LF") durch Leerzeichen zu ersetzen, diese mit SPACE() loszuwerden und dann die Länge des verbleibenden Texts zu prüfen. Fertig. Diese Methode funktioniert natürlich auch für Textdateien, nachdem deren Inhalt in eine Variable gelesen wurde. Als ich das zum ersten mal selber getestet habe, konnte ich nicht glauben, wie viel schneller diese Methode ist, als das, was ich bis dahin selbst zusammenprogrammiert hatte. Mal probieren? Passen Sie aber auf, daß Ihre Textdateien nicht zu groß sind (ich habe es nur mit Dateien bis zu 52KB getestet).

/* zeilenzähler beispiel */
fname="c:\temp\testfile.txt"  /* tragen sie hier ihre datei ein */

tablein = xrange("00"x, "FF"x) /* komplette ascii-tabelle, 256 bytes */
tableout = copies(" ", 13) || "0D"x || copies(" ", 242) /* 13 leerz. + LF + 242 leerz. = 256 bytes) */

fchars=charin(fname, 1, chars(fname)) /* kompletten dateiinhalt in variable lesen */
result = TRANSLATE(fchars, tableout, tablein) /* alles löschen außer LF-Zeichen */
say length(space(result, 0)) + 1

Ein anderes einfaches Beispiel für die Verwendung von TRANSLATE und REVERSE ist diese simple "Verschlüsselung":

/* verschlüsselungs beispiel */
/* achtung: dies ist eine NICHT sichere (weil ziemlich dumme) verschlüsselungsmethode! */
intab = xrange("a", "z")
outab = reverse(intab)
enc = translate("thomas", outab, intab)
say enc
dec = translate(enc, intab, outab)
say dec

Was hier passiert, ist das zunächst eine <eingabe-tabelle> erstellt wird, die das alphabet in Kleinbuchstaben enthält. Dann wird die <ausgabe-tabelle> erstellt, indem die umgekehrte Zeichenfolge (reverse) der <eingabe-tabelle> verwendet wird. Das Verschlüsseln geschieht mittels 'translate' und den beiden Tabellen.
Das "Entschlüsseln" geschieht mittels eines erneuten translate, wobei <eingabe-tabelle> und <ausgabe-tabelle> aus der Verschlüsselung gegeneinander getauscht werden.
Viel Spaß damit.

Im nächsten Teil werden wir uns kurz mit den gängigsten, interessantesten bzw. nützlichsten "Hilfs-" Funktionen von REXX beschäftigen. Vielen Dank für Ihre Geduld und bleiben Sie dran! ;)


Artikelverzeichnis
editor@os2voice.org
< Vorherige Seite | Inhaltsverzeichnis | Nächste Seite >
VOICE-Homepage: http://de.os2voice.org