Programmeren in REXX/Host Commando's

   Programmeren    in REXX

In dit hoofdstuk leggen we uit hoe we host-commando's kunnen uitvoeren en waarop we daarbij moeten letten.

Oproepen van een host-commando bewerken

Met de term host bedoelen we het programma dat ons REXX programma heeft opgeroepen. We moeten dus aannemen dat die host een omgeving heeft waar we commando's kunnen laten uitvoeren.

Het besturingssysteem voorziet in zo'n omgeving. In Windows systemen is dat de CMD-shell (de opdrachtprompt), op Linux systemen de "bash"-shell.

Maar andere programma's kunnen commando's aanvaarden vanuit een REXX programma. Zo zou een REXX programma kunnen "praten" met een editor, en daarbij documentinhoud opvragen of veranderen. Met het REXX address bevel kunnen we aangeven aan wie REXX de host commando's moet doorgeven; met de address() functie kunnen we opvragen naar wie de commando's op dit moment worden. In Windows is de standaard adressering een address "CMD".

In de rest van dit hoofdstuk zullen we voorbeelden gebruiken van een Windows systeem.

DOS commando's bewerken

We spreken wel van Windows commando's, maar eigenlijk bekijken we hier de oeroude MS-DOS commando's. Normaal voeren we die uit door een CMD opdrachtprompt te openen, of door ze in een BAT procedure te steken. Maar het kan dus evengoed met REXX, met dan alle toeters en bellen die daarmee gepaard gaan.

Een commando roepen we op als een op zichzelf staand statement. Als REXX, na interpretatie, tot het besluit is gekomen dat het niet een werkje voor hem is (een assignatie of een REXX bevel) dan geeft hij het resultaat van de interpretatie door aan wie hem heeft opgeroepen. Hopelijk begrijpt de oproeper het commando en kan die er iets zinnigs mee doen. Als er problemen optreden, of het is een onbekend commando, dan hopen we een returncode terug te krijgen. Deze returncode zet REXX dan in de gereserveerde variabele RC. Windows is spijtig genoeg nogal spaarzaam wat returncodes betreft. Daarom bestaan er functies die voor een wat betere samenwerking zorgen. We zullen die bestuderen in het hoofdstuk RexxUtil functies.

In dit eerste voorbeeld roepen we het "COPY" commando op:

/* We voeren een host-commando uit */
MijnBestand='D:\Tekst.txt'
'COPY' Mijnbestand 'E:\Map\'
if rc<>0 then say 'Er waren problemen met het COPY commando.'
exit

In het geval de bestandsnaam of pad een spatie bevat moet die voor Windows tussen aanhalingstekens worden geplaatst. Dan moet ons voorbeeld als volgt worden aangepast:

/* We voeren een host-commando uit */
MijnBestand='"D:\Mijn Werkmap\Tekst.txt"'
'COPY' Mijnbestand 'E:\Map\'
if rc<>0 then say 'Er waren problemen met het COPY commando.'
exit

Voor REXX is de string die aan de variabele MijnBestand moet worden toegekend alles wat tussen de enkelvoudige aanhalingstekens staat, dus inclusief de dubbele aanhalingstekens. Dan zal het COPY commando geen problemen opleveren.

Alvorens we nu verder kunnen gaan moeten we een uiterst belangrijk punt aansnijden...

Van levensbelang bewerken

Wanneer we een host-commando willen uitvoeren, dan houden we best volgende regel aan:

 Schrijf alles wat niet variabel is, als een constante !


Wat kan er namelijk gebeuren als we dit niet doen ? Laten we kijken naar dit stukje programma:

/* Dit gaat ons in de problemen brengen */
copy d:\Mijnbestand e:\

Hier maken we het wel erg bont door niets tussen aanhalingstekens te schrijven. Niet te verwonderen dat we het volgende op het scherm zien verschijnen:

     2 *-* copy d:\MijnFile e:\
Error 35 running d:\test.rex line 2:  Invalid expression
Error 35.1:  Incorrect expression detected at ":"

REXX kan helemaal niets doen met de ":", want dit is geen geldig teken voor een token, en als label kan het ook al niet dienen, want het is niet het eerste woord van een zin.

OK, laten we dat proberen te verbeteren:

/* Dit is ook niet helemaal koosjer */
copy "d:\Mijnbestand" "e:\"

We krijgen nu wel

1 bestand(en) gekopieerd.

doch er blijft een groot gevaar dreigen. Het woord "copy" is voor REXX geldig als naam voor een variabele. Het programma zal werken zolang we geen waarde aan deze variabele toekennen... maar...

/* Oei, systeem kapot */
copy="ERASE"
copy "C:\IO.SYS"

Oef, het bestand is als systeembestand en enkel-lezen gedefinieerd, er is dus gelukkig niets gebeurd, maar het kon catastrofaal zijn, niet ? In bovenstaand voorbeeld ziet men natuurlijk onmiddellijk de fout. Maar een REXX programma kan honderden statements bevatten (en dat is echt niet overdreven) waarbij het statement copy="ERASE" ergens ver van het statement copy "C:\IO.SYS" kan verwijderd zijn.

Dus, nogmaals, en we kunnen het niet genoeg herhalen:

 Schrijf alles wat niet variabel is, als constante !


Een andere REXX oproepen bewerken

Laten we veronderstellen dat we volgend eenvoudig REXXje willen oproepen:

/* RexxSubProgramma, wordt opgeroepen door een andere REXX */
parse source . oproeptype .
say 'RexxSubProgramma: We werden opgeroepen als' oproeptype
say 'RexxSubProgramma: We kregen' arg() 'parameter(s) door, nl.'
do i=1 to arg()
   say 'RexxSubProgramma: "'arg(i)'"'
end
exit arg()

We kunnen dat REXXje op verschillende manieren oproepen:

  • Door het op te roepen als een DOS-commando;
  • Door het op te roepen als een externe routine mits gebruik van een REXX call instructie
  • Door het op te roepen als een externe functie.

Merk op dat een externe functie of routine steeds minder performant zal zijn dan wanneer we ze intern hadden geschreven. Om performant te werken moeten we dus dezelfde routine of functie steeds weer gaan kopiëren in elk programma dat er gebruik van wil maken.

In het hoofdstuk "Toch een beetje OORexx" zullen we echter leren dat OORexx mogelijkheden biedt om de externe functies toch even performant te laten werken als interne. We kunnen op die manier onze eigen functiepakketten samenstellen. Maar dit is dus niet in alle implementaties van REXX voorzien.

Oproepen als een DOS commando bewerken

/* TestRexx, roept een andere REXX op */
'REXX D:\RexxSubProgramma.rex p1 p2 , p3'
if rc<>0 then say 'TestRexx RexxSubProgramma gaf foutcode='rc
exit

Met dit resultaat aan het scherm:

RexxSubProgramma: We werden opgeroepen als COMMAND
RexxSubProgramma: We kregen 1 parameter(s) door, nl.
RexxSubProgramma: "p1 p2 , p3"
TestRexx: RexxSubProgramma gaf foutcode=1

Ons tweede RexxSubProgramma wordt opgeroepen alsof het rechtstreeks vanaf de CMD-opdrachtprompt zou worden opgeroepen. Dus, parse source geeft dan ook "COMMAND" als tweede antwoord.

Het subprogramma kreeg ook maar één parameter door. Zelfs al plaatsen we een komma tussen de parameters, toch wordt alles als één parameter beschouwd. De komma staat immers in een constante, waarin REXX helemaal niet kijkt.

Het RexxSubProgramma draait in een proces, onderliggend aan het hoofdprogramma (child process). Het hoofdprogramma zal dus niet verder kunnen werken zolang het subprogramma niet is gestopt. Om dit te vermijden kan men gebruik maken van het Windows start commando om een ander proces te starten. We schrijven dan "START REXX D:\RexxSubProgramma.rex p1 p2 , p3". Willen beide programma's dan nog met elkaar kunnen praten, dan moeten speciale technieken van multi-programmatie worden gebruikt, en dat onderwerp laten we in dit boek links liggen.

Oproepen met een CALL instructie bewerken

/* TestRexx, roept een andere REXX op */
call 'D:\RexxSubProgramma.rex p1 p2 , p3'
if rc<>'RC' then say 'TestRexx: RexxSubProgramma gaf returncode='rc
            else say 'TestRexx: RexxSubProgramma gaf als resultaat:' result
exit
 Verwar dit REXX call bevel niet met het Windows call commando !


Nu zien we het volgende op het scherm verschijnen:

     2 *-* call 'D:\RexxSubProgramma.rex p1 p2 , p3'
Error 43 running d:\testrexx.rex line 2:  Routine not found
Error 43.1:  Could not find routine "D:\RexxSubProgramma.rex p1 p2 , p3"

Oei, wat hebben we nu verkeerd gedaan ? Wel, wanneer REXX een call bevel krijgt gaat hij op zoek naar wat als eerste parameter is meegegeven. Die eerste parameter is D:\RexxSubProgramma.rex p1 p2 , p3. REXX kent geen interne of externe functie noch routine met die naam. Ook een extern REXX programma met die naam kan hij nergens vinden. We moeten ons call bevel dus als volgt verbeteren:

call 'D:\RexxSubProgramma.rex' 'p1 p2 , p3'

en nu zien we:

RexxSubProgramma: We werden opgeroepen als SUBROUTINE
RexxSubProgramma: We kregen 1 parameter(s) door, nl.
RexxSubProgramma: p1 p2 , p3
TestRexx: RexxSubProgramma gaf als resultaat: 1

Ditmaal worden we dus opgeroepen als "SUBROUTINE" en niet als "COMMAND".

Merk op dat we testen of rc<>"RC" na het oproepen van onze subroutine. Als deze statements een eind ver in een programma voorkomen kan het echter gebeuren dat de variabele rc al is opgevuld met een returncode van een andere instructie. Onze test is dus niet waterdicht, tenzij we vóór het oproepen van onze subroutine een drop rc zouden schrijven. Met een DROP bevel worden de opgesomde variabelen verwijderd uit de gekende variabelen. Ook een stem kan men met drop volledig "weggooien" (drop stem.).


In tegenstelling tot een commando, dat enkel een getal als returncode kan teruggeven (variabele RC), komt het resultaat van een subroutine in de gereserveerde variabele RESULT terecht. Dat resultaat kan echt alles zijn, een cijfer, een string, of zelfs helemaal niets.

Daarom passen we ons programmaatjes aan in functie van wat er moet teruggegeven worden:

/* RexxSubProgramma, wordt opgeroepen door een andere REXX */
parse source . oproeptype .
say 'RexxSubProgramma: We werden opgeroepen als:' oproeptype
say 'RexxSubProgramma: We kregen' arg() 'parameter(s) door, nl.'
do i=1 to arg()
   say 'RexxSubProgramma: "'arg(i)'"'
end
if oproeptype='FUNCTION' then return '==>'arg(1)'<=='
                         else return arg()

We hebben ons programmaatje bimodaal gemaakt, het kan opgeroepen worden als commando of als externe subroutine/functie. Noteer daarbij het volgende:

  • Return arg() heeft hier hetzelfde effect als een exit arg(). Met een return bevel moet REXX naar een hoger niveau terugkeren. Zit je in een REXX subroutine, ga je dus terug naar een oproepende subroutine of uiteindelijk naar het hoofdprogramma. Zit je al in het hoofdprogramma (zoals hier het geval is), dan verlaat je met een return bevel het hoofdprogramma, en komt het dus neer op een exit bevel.
  • Langs de kant van het oproepend programma komt het resultaat van de externe subroutine terecht in variabele result, terwijl variabele RC niet wordt beïnvloed. Roept men een ander REXX programma op als een host commando, dan zit het resultaat wél in variabele RC, en kan het alleen een geheel getal zijn. Variabele result wordt dan niet beïnvloed.

In dit geval draait het RexxSubProgramma in hetzelfde proces als het hoofdprogramma. Ook hier zal het hoofdprogramma niet verder kunnen zolang het subprogramma niet is beëindigd.

Als functie bewerken

Nu maken we er een functie van door de oproep te vervangen in:

say 'D:\RexxSubProgramma.rex'('p1 p2 , p3')

met als resultaat:

RexxSubProgramma: We werden opgeroepen als: FUNCTION
RexxSubProgramma: We kregen 1 parameter(s) door, nl.
RexxSubProgramma: p1 p2 , p3
==>p1 p2 , p3<==
TestRexx: RexxSubProgramma gaf als resultaat: RESULT

Let hierbij op volgende zaken:

  1. We werden wel degelijk opgeroepen als functie (FUNCTION);
  2. Variabele result is niet meer ingevuld, al schreven we return met een string. Na een call, en enkel dan zal REXX met variabele result iets doen. Komt er niets terug, dan wordt result namelijk "gedropped". Hier hebben we geen call gebruikt, dus variabele result blijft wat ze tevoren al was (in ons voorbeeld, een niet geïnitialiseerde variabele).
  3. De functie moet altijd iets teruggeven, al was het een nullstring (dus return "" is prima).
  4. We hebben nog steeds slechts één parameter binnengekregen in het RexxSubProgramma...
VRAAG: Waarom hebben we maar één parameter  Hoe lossen we dat op ?

Antwoord:In al onze voorbeelden hebben we alles als één constante geschreven, niet te verwonderen dat we maar één parameter binnenkrijgen in de subroutine of functie. Willen we meer parameters doorgeven, dan moeten we het zo schrijven:

say 'D:\RexxSubProgramma.rex'('p1 p2' , 'p3')

om dan dit te bekomen:

 RexxSubProgramma: We werden opgeroepen als: FUNCTION
 RexxSubProgramma: We kregen 2 parameter(s) door, nl.
 RexxSubProgramma: p1 p2
 RexxSubProgramma: p3
 ==>p1 p2<==
 TestRexx:  RexxSubProgramma gaf als resultaat: RESULT


Ons hoofdprogramma kunnen we ook naar het expertniveau tillen:

/* TestRexx, roept een andere REXX op */
drop rc result               /* we willen zeker zijn dat deze nog niet bestaan */
call "D:\RexxSubProgramma.rex" "p1 p2" , "p3"
Select
 when symbol('RC')='VAR'     then say 'TestRexx: RexxSubProgramma gaf returncode='rc
 when symbol('RESULT')='VAR' then say 'TestRexx: RexxSubProgramma gaf als resultaat:' result
 otherwise                        say 'TestRexx: Er is geen returncode noch een resultaat'
exit

De functie symbol() laat namelijk toe te testen of aan een variabele een waarde toegekend is. Indien dat wel het geval is, dan antwoordt symbol met "VAR", anders met "LIT". Is de naam echter geen geldig token, dan krijgen we "BAD" als antwoord. Merk tevens op dat we de naam van de variabele tussen aanhalingstekens plaatsen. Doen we dat niet, dan zou eerst de waarde van de variabele worden ingevuld alvorens de functie symbol wordt opgeroepen.

Bekijk deze voorbeelden:

rc=10
say symbol(woord) /* LIT, want woord heeft waarde WOORD en geldig token */
say symbol(rc)    /* BAD, want rc heeft waarde 10 en dat is een ongeldige naam */
say symbol('RC')  /* VAR, want RC heeft waarde en is geldig token */
say symbol('rc')  /* VAR, want rc heeft waarde en is geldig token */

Het zal de aandachtige lezer ook zijn opgevallen dat we in het statement call "D:\RexxSubProgramma.rex" "p1 p2" , "p3" de naam van de functie tussen aanhalingstekens hebben geschreven. Dit is hier nodig omdat we leestekens zoals \ en een punt in de naam hebben. We willen namelijk niet dat REXX dit eerst probeert te interpreteren.

Maar er is nog een andere geval waarin we een functie als constante moeten schrijven. Stel dat we een interne functie gemaakt hebben met dezelfde naam als een standaardfunctie. Hoe kunnen we dan beslissen welke functie we willen oproepen ? Wel, willen we de standaardfunctie oproepen, dan moeten we de naam als constante - met hoofdletters - schrijven. Hoofdletters zijn voor onze externe functie niet nodig, want het verwijst naar een bestand, en REXX vraagt aan Windows om dat voor hem op te zoeken. Windows kan overweg met gemengde karakters.

Hier hebben we een voorbeeld:

/* Interne functie met zelfde naam als standaardfunctie */
string="Dit is een zin"
say left(string,5)          /* «n zin» */
say 'LEFT'(string,5)        /* «Dit i» */
exit
left: return right(arg(1),arg(2))

Dit voorbeeld is een beetje bij z'n haar getrokken, maar een interne functie zou wel extra functionaliteit aan een standaardfunctie kunnen toevoegen.

Net als in het geval van de call draait de functie in hetzelfde proces als het hoofdprogramma en zal het hoofdprogramma niet verder kunnen zolang het subprogramma niet is beëindigd.

Kortom, als het na dit hoofdstuk duidelijk is dat

 alles wat niet variabel is, als constante moet geschreven worden !


dan hebben we het belangrijkste kunnen aanleren. We kunnen het echt niet genoeg herhalen !

← Voorbeeld 2 Programmeren in REXX Debugging →
Informatie afkomstig van https://nl.wikibooks.org Wikibooks NL.
Wikibooks NL is onderdeel van de wikimediafoundation.