Wenn bestimmte Arbeitsgänge in einem Skript mehrfach benötigt werden, fasst man die erforderlichen Anweisungsfolgen in Prozeduren oder Funktionen unter einem möglichst aussagekräftigen Namen zusammen. Das erspart zum einen ineffiziente Wiederholungen gleichartigen Codes und erlaubt zum anderen eine übersichtliche Gliederung komplizierterer Skripte.
Eine Prozedurdefinition fasst eine Anweisungsfolge unter einem Namen als Prozedurbezeichner zusammen. Der Interpreter führt die Anweisungsfolge im Prozedurrumpf immer dann aus, wenn er den Prozedurbezeichner als Anweisung im Quelltext vorfindet (Prozeduraufruf). Danach fährt er mit der nächsten Anweisung nach dem Aufruf fort.
Prozedurdefinitionen müssen am Ende des Skriptes stehen, ihre Reihenfolge spielt keine Rolle. Sie werden durch das Schlüsselwort sub eingeleitet und durch endsub abgeschlossen:
sub <identifier> # Prozedurkopf # Prozedurrumpf mit Anweisungen endsub
Eine so definierte Prozedur kann unter ihrem Namen <identifier> oder auch durch gosub( <identifier> ) (obsolet) im Quelltext aufgerufen werden. Der Interpreter führt bei jedem Aufruf alle Anweisungen im Prozedurrumpf aus, bis er endsub oder eine return-Anweisung erreicht. Im Prozedurrumpf können beliebig viele return-Anweisungen verwendet werden.
Prozeduren können nicht verschachtelt werden, eine Prozedur kann in ihrem Rumpf also keine Prozedurdefinitonen aufnehmen. Eine Prozedur kann mit ihren Anweisungen andere Prozeduren und auch sich selbst (Rekursion) aufrufen.
Der Prozedurbezeichner ist frei wählbar, er darf allerdings nicht mit dem Dollarzeichen $ beginnen (das kennzeichnet Variablenbezeichner) und kein Schlüsselwort sein. Er muss mit einem Buchstaben beginnen und darf im Übrigen aus Buchstaben, Ziffern und dem Unterstrich _ bestehen. Groß- und Kleinbuchstaben werden nicht unterschieden.
In einem Prozedurrumpf können Variablen lokal deklariert werden, außerhalb von Prozeduren auf der Skriptebene deklarierte Variablen sind global.
Lebensdauer
Lokale Variablen speichern ihre Werte, solange die Prozedur ausgeführt wird, in der sie deklariert sind. Sobald die Prozedur beendet wird, existieren sie nicht mehr und die in ihnen gespeicherten Werte sind verloren.
Globale Variablen speichern ihre Werte, solange das Skript läuft, in dem sie deklariert sind.
Sichtbarkeit
Auf lokale Variablen kann nur innerhalb der Prozedur zugegriffen werden, in der sie deklariert wurden. Im übrigen Skript oder anderen Prozeduren gelten sie als nicht deklariert, sie sind dort unsichtbar. Eine aufgerufene Prozedur kann daher auch die Variablen der sie aufrufenden Prozedur nicht sehen.
Globale Variablen sind in jeder Prozedur sichtbar. Eine globale Variable kann allerdings durch eine gleichnamige lokale Variable verdeckt werden. Auf diese globale Variable ist dann in der Prozedur kein Zugriff möglich, obwohl sie weiterhin existiert und ihren Wert behält.
Beispiel:
#!hs2
var( $x , $y ) # globale Variablen {1}
# globaler Sichtbarkeitsbereich:
# Hier sind $x und $y sichtbar.
Sub TestScope # Beginn der Prozedur TestScope
var( $a, $b, $x ) # lokale Variablen {2}
# lokaler Sichtbarkeitsbereich:
# Hier sind $a, $b, das bei {2} deklarierte $x und das
# global deklarierte $y sichtbar.
# Die bei {1} global deklarierte Variable $x ist verdeckt,
# kein Zugriff möglich!
endsub # Ende der Prozedur TestScope
Im Rumpf der Prozedur TestScope kann auf die globale, bei {1} deklarierte Variable $x nicht zugegriffen werden, nur das bei {2} deklarierte $x ist sichtbar. Außerhalb dieser Prozedur ist das globale $x sichtbar.
Funktionen sind Prozeduren, die ein Resultat zurückgeben. Sie können daher nicht nur wie eine Prozedur mit ihrem Namen als Anweisung aufgerufen werden, ihr Name kann auch als Operand in Ausdrücken verwendet werden. Die Anweisungen im Funktionsrumpf werden ausgeführt, bevor der Wert des Funktionsresultats wie jeder andere Operand im Ausdruck weiterverarbeitet wird.
Im Funktionsrumpf muss dazu im Verlaufe der Ausführung eine return()-Anweisung mit dem zurückzugebenden Wert erreicht werden. Die return()-Anweisung beendet dann die Ausführung der Funktion und gibt den Wert des Ausdrucks in ihren Klammern als Resultat zurück. Im Funktionsrumpf können beliebig viele return()-Anweisungen verwendet werden.
In dem Skript
#!hs2 var( $a, $b, $x ) $a = 1 $b = 2 $x = Summe # vor der Zuweisung wird Summe ausgeführt, # erst dann wird das Resultat $x zugewiesen. quit sub Summe # Funktion Summe return( $a + $b ) endsub
liefert die Funktion Summe bei Aufruf die Summe der beiden globalen Variablen $a und $b mit return( $a + $b ) zurück.
Eine Prozedur oder Funktion arbeitet zwar immer die gleiche in ihrem Rumpf definierte Anweisungsfolge ab, aber man kann ihr durch Parametrierung bei jedem Aufruf andere Daten zur Verarbeitung übergeben.
Im Beispiel des vorhergehenden Abschnittes hat die Funktion Summe ihre Daten den globalen Variablen $a und $b entnommen. Falls mehr als nur ein Ergebnis berechnet wird, könnte jeder errechnete Wert im Rumpf weiteren globalen Variablen zugewiesen werden. Eine solche Parametrierung durch Seiteneffekte empfiehlt sich nur in kleinen Skripten und lässt vor allem bei der Berechnung von Ausdrücken mit mehreren Funktionsaufrufen schnell die Übersicht vermissen. Sie empfiehlt sich generell nur für solche Werte, die in mehreren Funktionen (etwa eines Moduls) gleichermaßen berücksichtigt werden und die sich nicht bei jedem Aufruf, sondern nur verhältnismäßig selten ändern.
Besser ist es, die jeweils zu verarbeitenden Daten in den Aufruf selbst aufzunehmen und sie als Argumente zu übergeben. Dazu muss die Prozedur oder Funktion in ihrer Definition eine Parameterliste vorgeben, die Anzahl und Verarbeitungsrichtung ihrer Parameter angibt. Beispielsweise definiert
sub Division( $x, $y, *$r ) # Funktionskopf mit Parameterliste $r = $x % $y return( $x / $y ) endsub
eine Funktion Division, die den Parameter $x ganzzahlig durch den Parameter $y dividiert. Die Parameter gelten im Rumpf als lokal deklariert und werden dort wie andere lokal deklarierte Variablen auch verwendet. Dem Parameter $r geht ein Stern (*) voran, um die Rückgabe eines Wertes in $r bei Funktionsende anzuzeigen. Der Aufruf
$div = Division( 10, 5, $divRest )
bewirkt dann die Zuweisung des Funktionsresultats 2 an die Variable $div und die Zuweisung des Divisionsrestes 0 an die Variable $divRest. Die Zuweisung an $divRest erfolgt erst beim Verlassen der Funktion.
Anstelle der beiden Literale 10 und 5 kann jeder Ausdruck als Argument übergeben werden; der Interpreter berechnet alle Argumente und kopiert ihre Werte in die als Wertparameter deklarierten lokalen Variablen (im Beispiel $x und $y), bevor er mit der Ausführung der Prozedur oder Funktion beginnt.
Der dritte, durch den vorangehenden Stern als Rückgabeparameter deklarierte Parameter setzt allerdings beim Aufruf zwingend eine Variable voraus, da einem Ausdruck kein Wert zugewiesen werden kann. Im Übrigen verhält sich ein Rückgabeparameter nicht anders als ein Wertparameter; er erlaubt also den Datenfluss in beide Richtungen, während ein Wertparameter nur Daten eingeben kann.
Selbstdefinierten Prozeduren und Funktionen müssen immer so viele Werte bzw. Variablen in genau der Reihenfolge übergeben werden, die die Parameterliste der Definition vorgibt. Lediglich einige eingebaute Prozeduren und Funktionen erlauben das Auslassen von Parametern, für die sie dann Standardwerte annehmen. Näheres ist im Einzelfall deren Dokumentation zu entnehmen.