[ Impressum ]

Kommandozeilen-Skripte mit LiveCode Server

www.Rozek.de > LiveCode Server > CmdLine Scripts
Wie in einer anderen Notiz bereits erläutert wurde, lässt sich der "LiveCode Server" [1] nicht nur als CGI-Prozessor, sondern sehr gut auch für Kommandozeilen-Anwendungen einsetzen.

Auf dieser Seite wird beschrieben, was Sie dabei alles beachten müssen.

Überblick

Von der Kommandozeile aus aufrufbare Skripte müssen eine Reihe grundlegender Funktionalitäten aufweisen, um in der Praxis von Nutzen zu sein - diese sollen hier der Reihe nach vorgestellt werden:
  • Grundstruktur eines Skriptes mit Shebang-Zeile
  • Ausgabe auf stdout und stderr
  • Eingabe von stdin
  • Verhalten im Falle von Fehlern
  • Erfassen von Kommandozeilen-Argumenten
  • Aufruf externer Betriebssystem-Kommandos
  • Zugriff auf Umgebungsvariablen
Sofern Sie "LiveCode Server" in passender Weise installiert haben, können Sie die hier vorgestellten Skripte auch bei sich auf Ihrem eigenen Rechner ausprobieren. Zwar wurden die Beispiele für Mac OS X konzipiert und nur auf einem Mac-Rechner (unter Mac OS X 10.9 "Mavericks") getestet, die meisten Skripte sollten jedoch unverändert auch unter einem aktuellen Linux und - mit leichten Anpassungen - auch unter Windows laufen.

Grundstruktur eines Skriptes mit Shebang-Zeile

Zwar wurde die grundlegende Struktur eines Kommandozeilen-Skriptes im Rahmen der Installation von "LiveCode Server" bereits ausführlich beschrieben, trotzdem soll sie an dieser Stelle noch einmal vorgestellt werden: Kommandozeilen-Skripte haben üblicherweise folgenden Aufbau

#!/usr/bin/env lc-server
<?lc
...
?>

die erste (die sogenannte "Shebang"-Zeile) zeigt dem Betriebssystem, wo der für die Ausführung des Skriptes zuständige Interpreter zu finden ist. Die Zeilen mit den HTML-Tags <?lc und ?> sind der Tatsache geschuldet, dass "LiveCode Server" eigentlich zur Erstellung dynamischer Web-Seiten gedacht ist und an dieser Stelle etwas "zweckentfremdet" wird.

Bitte vergessen Sie auch nicht, jedes derart gestaltete Skript als ausführbar zu markieren:

chmod +x <name of the script file>

Das eigentliche Skript wird anstelle der Ellipse (...) eingetragen. Da dieser Rahmen grundsätzlich verwendet werden sollte, wird er in den folgenden Beispielen nicht mehr explizit erwähnt.

Ausgabe auf stdout und stderr

Die Ausgabe von Text oder Binär-Daten gehört zu den grundlegenden Aufgaben eines Kommandozeilen-Skriptes. Der Dokumentation zu "LiveCode Server" zufolge eignet sich folgende Anweisung für die Ausgaben von Texten:

put "Hello, World!"

Lässt man ein Skript mit dieser Zeile ausführen, wird der Text zwar ausgegeben - mangels nachfolgendem Zeilenvorschub erscheint die Eingabeaufforderung für das nächste Kommando aber noch in derselben Zeile wie der Ausgabetext.

Ändert man die Anweisung nun ein wenig

put "Hello, World!" & CR

erscheint aber plötzlich gar kein Text mehr in der Anzeige!

Abhilfe liefert die Schreibweise in der Form

put binary "Hello, World!" & CR

die sich (trotz des "binary" darin) wunderbar auch für Textausgaben eignet. Als angenehmen Nebeneffekt befreit einen diese Form zugleich von den unangenehmen Folgen unerwünschter Zeichensatz-Konvertierungen zwischen LiveCode und dem Betriebssystem (vor allem unter Mac OS X!)

Eine Alternative zu put bietet das Kommando write:

write "Hello, World!" & CR to stdout

Wie put schreibt auch diese Anweisung in den Standard-Ausgabekanal. Möchte man nach stderr ausgeben, so schreibt man stattdessen

write "I am an error message!" & CR to stderr

Da alle diese Formen etwas "sperrig" sind, kann man sich mit ein paar kleinen Prozeduren behelfen (Anwendungsbeispiele finden Sie in den weiter unten folgenden Abschnitten):

--------------------------------------------------------------------------------
-- say[ln] writes the given arguments to stdout --
--------------------------------------------------------------------------------

command say
if (ParamCount() is 0) then; exit say; end if

repeat with i = 1 to ParamCount()
write Param(i) to stdout
end repeat
end say


command sayln # not DRY, but passing all parameters to another handler is tough
if (ParamCount() is 0) then; write LF to stdout; exit sayln; end if

repeat with i = 1 to ParamCount()
write Param(i) to stdout
end repeat

write LF to stdout
end sayln

--------------------------------------------------------------------------------
-- cry[ln] writes the given arguments to stderr --
--------------------------------------------------------------------------------

command cry
if (ParamCount() is 0) then; exit cry; end if

repeat with i = 1 to ParamCount()
write Param(i) to stderr
end repeat
end cry


command cryln # not DRY, but passing all parameters to another handler is tough
if (ParamCount() is 0) then; write LF to stderr; exit cryln; end if

repeat with i = 1 to ParamCount()
write Param(i) to stderr
end repeat

write LF to stderr
end cryln

(Die Namen "say" und "cry" sind eine Reminiszenz des Autors an die gute alte Skriptsprache REXX [2], welches schon von Jahrzehnten eine unglaubliche Produktivität erlaubte)

Da ständig irgendwelche Meldungen ausgegeben werden müssen, wird in den folgenden Beispielen davon ausgegangen, dass die oben gezeigten Zeilen am Anfang jedes Kommandozeilen-Skriptes stehen (bzw. mittels require darin eingebettet werden).

Eingabe von stdin

Kommandozeilen-Skripte müssen ihre Eingabedaten häufig von stdin einlesen. Die zugehörige LiveCode-Anweisung lautet:

read from stdin

Die eingelesenen Zeichen liegen anschließend in it und können von dort aus weiter verarbeitet werden. Eine häufig eingesetzte Variante ist

read from stdin until EOF

und liest alle vorhanden Eingabedaten auf einen Schlag - so lange pausiert das Skript allerdings auch.

Verhalten im Falle von Fehlern

Fehler können überall auftreten - doch wie reagiert ein Kommandozeilen-Skript darauf? Zu Testzwecken können Sie eine explizite Ausnahme werfen:

throw "Error in CmdLine Script"

Ohne weitere Maßnahmen führt diese Anweisung zu folgender Ausgabe über stdout:

ERROR:
Error in CmdLine Script
FILES:
/Users/andreas/Rozek/LiveCode Server/(Tests)/./Test.lc

Dieses Verhalten lässt sich mithilfe von

set the ErrorMode to "..."

steuern.

set the ErrorMode to "inline"

führt zu der gezeigten Ausgabe über stdout,

set the ErrorMode to "stderr"

schickt diese Ausgabe stattdessen über stderr. Mit

set the ErrorMode to "quiet"

lassen sich Fehlerausgaben komplett unterdrücken.

Alternativ können vom Skript selbst nicht abgefangene Ausnahmen und Fehler auch mithilfe eines Event Handler bearbeiten:

--------------------------------------------------------------------------------
-- ScriptExecutionError catches and reports an error in the script --
--------------------------------------------------------------------------------

on ScriptExecutionError ErrorStack, FileList
cryln "Error in Command-Line Script:"
cryln "ErrorStack = ", ErrorStack
cryln "FileList = ", FileList
cryln "Context = ", the ExecutionContexts

exit to top
end ScriptExecutionError

schreibt Fehler-bezogene Informationen über stderr und reichert sie mit Daten zu der Stelle, an der der Fehler aufgetreten ist, an.

Auch diese paar Zeilen sollten zur Grundausstattung jedes Kommandozeilen-Skriptes gehören und hinter say[ln]/cry[ln] eingefügt (bzw. mit require angefordert) werden.

Erfassen von Kommandozeilen-Argumenten

LiveCode stellt von Haus aus die Variablen $# sowie $0, $1, usw. für den Zugriff auf Kommandozeilen-Argumente bereit. Allerdings ist die Dokumentation dazu fehlerhaft: $0 enthält nicht den Namen des aufgerufenen Skriptes, sondern das erste übergebene Argument. Dementsprechend liefert $# auch genau die Anzahl der beim Aufruf mitgegebenen Argumente.

Als Test möge folgendes (ausnahmsweise einmal vollständig wiedergegebene) Skript dienen

#!/usr/bin/env lc-server
<?lc
require "say_or_cry.lc"
require "ScriptExecutionError.lc"

sayln "inspecting command-line arguments:"
sayln " $#: ", $#

if ($# > 0) then
sayln
repeat with i = 0 to $#-1
sayln " $",i,": ", value("$" & i)
end repeat
end if

sayln "done"
?>

Rufen Sie dieses Skript mit unterschiedlichen Argumenten auf und schauen Sie auf die jeweilige Ausgabe.

Aufruf externer Betriebssystem-Kommandos

Kommandozeilen-Skripte müssen immer mal wieder auch auf andere (Betriebssystem-)Kommandos zurückgreifen. LiveCode stellt hierfür die Shell-Funktion bereit:

shell("your external command with any arguments")

Das Ergebnis dieses Aufrufes enthält die kombinierten Ausgaben des ausgeführten Kommandos über stdout und stderr. Außerdem wird the result (angeblich) auf den ExitCode des Kommandos gesetzt. Ein vorheriges

set the hideConsoleWindows to true

ist nicht erforderlich. Allerdings können Sie das Startverzeichnis für das externe Kommando mittels

set the DefaultFolder to "..."

passend voreinstellen.

Testen Sie die shell-Funktion doch einmal mit folgenden Unix/Linux-Kommandos:
  • pwd - sollte das voreingestellte Verzeichnis liefern
  • printenv - sollte Ihnen eine Aufstellung aller Umgebungsvariablen anzeigen
  • no-such-command - sollte eigentlich fehlschlagen (aber sehen Sie selbst)
  • exit 1 - sollte eigentlich einen ExitCode <> 0 liefern (aber sehen Sie selbst)
Wie Sie sehen, ist die shell-Funktion sicherlich praktisch - aber nur, solange das aufgerufene Kommando korrekt funktioniert. Etwaige Fehler müssen Sie leider selbst anhand der Kommando-Ausgabe erkennen.

Zugriff auf Umgebungsvariablen

Mithilfe des $-Präfixes ermöglicht LiveCode auch den Zugriff auf (viele, leider nicht alle) Umgebungsvariablen. Dummerweise gibt es aber keine direkte Möglichkeit, sich von LiveCode eine Liste aller Umgebungsvariablen anzeigen zu lassen: Sie müssen die Namen der benötigten Variablen im Voraus kennen.

Die shell-Funktion kann hier Abhilfe schaffen. Das folgende Skript

local EnvVarSet
put shell("printenv") into EnvVarSet
filter EnvVarSet with "*=*"
split EnvVarSet by LF and "=" # not fool-proof: works for values w/o LF only!
# but keeps EnvVars inaccessible to LiveCode (such as $_ and such)

sayln "List of Environment Variables:"
repeat for each key EnvVar in EnvVarSet
sayln "- '", EnvVar, "' = '", EnvVarSet[EnvVar], "'"
end repeat
sayln "finished"

zeigt Ihnen alle verfügbaren Umgebungsvariablen und deren Inhalte an - sofern darin keine Zeilenvorschübe oder ähnlich kritische Zeichen vorkommen.

Außerdem haben Sie von LiveCode nicht unbedingt Zugriff auf alle gezeigten Variablen. Die Anweisung

put binary $_

liefert beispielsweise kein Ergebnis.

Folgendes Skript ist in dieser Hinsicht etwas "ehrlicher":

local EnvVars
put shell("printenv") into EnvVars
filter EnvVars with "*=*"
split EnvVars by LF and "=" # not fool-proof: works for values w/o LF only!

---- check the EnvVars found so far - keep only those available to LiveCode ----

local EnvVarSet; put empty into EnvVarSet
repeat for each key EnvVar in EnvVars
if (EnvVar is not empty) and (EnvVar is not "#") and (EnvVar is not an integer) then
local EnvVarValue; put value("$" & EnvVar) into EnvVarValue
if (EnvVarValue is not empty) then # no fully correct!
put EnvVarValue into EnvVarSet[EnvVar]
end if
end if
end repeat

---- now show what we have found ----

local EnvVarList; put the keys of EnvVarSet into EnvVarList
sort lines of EnvVarList

sayln "List of Environment Variables:"
repeat for each line EnvVar in EnvVarList
sayln "- '", EnvVar, "' = '", EnvVarSet[EnvVar], "'"
end repeat
sayln "finished"

Welche dieser beiden Varianten die "bessere" ist, hängt vom jeweiligen Anwendungsfall ab.

Trotz der Probleme: viel Spaß mit dem "LiveCode Server"!

Literaturhinweise

[1]
(RunRev Ltd.)
LiveCode | LiveCode Server Guide
Der LiveCode Server ist ein Interpreter für LiveCode-Skripte, der von der Kommandozeile aus gestartet wird (ohne grafische Benutzeroberfläche auskommt) und vor allem als CGI-Prozessor gedacht ist (auf diese Weise können Web-Seiten mit LiveCode bearbeitet werden - man muß also nicht mehr unbedingt PHP lernen).
[2]
(Wikipedia)
REXX
Von der Web-Seite: "REXX (Abkürzung für Restructured Extended Executor) ist eine von Mike Cowlishaw bei IBM entwickelte Skriptsprache. [...]"