[ Impressum ]

Command Line Scripts with LiveCode Server

www.Rozek.de > LiveCode Server > CmdLine Scripts
As explained in another note "LiveCode Server"[1] can not only be used as a CGI processor, but also serves very well for command-line applications.

This page describes everything you will have to consider.

Overview

Scripts executed from the command line needs to offer a basic set of functionalities in order to be useful in practice - these will be presented here in order:
  • Basic Structure of a Script with a "shebang" Line
  • Output to stdout and stderr
  • Input from stdin
  • Behaviour in Case of Errors
  • Accessing Command-Line Arguments
  • Calling external Operating System Commands
  • Accessing Environment Variablen
If you have installed "LiveCode Server" appropriately, you can try the scripts presented here on your own computer. Although the examples were designed for Mac OS X and have been tested on a Mac computer only ( under Mac OS X 10.9 "Mavericks"), most scripts should, however, run unchanged on a recent Linux computer and - with slight modifications - under Windows.

Basic Structure of a Script with a "shebang" Line

Although the basic structure of a command-line script has already been described in detail as part of the installation of "LiveCode Server", it is to be presented here again: command-line scripts usually have the following structure:

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

the first line (the so-called "shebang" line) instructs the operating system where to find the interpreter responsible for the execution of the script. The lines with the HTML tags <?lc and ?> are required due to the fact that "LiveCode Server" is actually meant to create dynamic web pages and therefore somewhat diverted from its intended use here.

Please do not forget to also mark each script as executable:

chmod +x <name of the script file>

The actual script is entered in place of the ellipsis (...). Since this framework should always be used, it is not explicitly mentioned in the following examples.

Output to stdout and stderr

The output of text or binary data is one of the basic tasks of a command-line script. According to the documentation for "LiveCode Server" the following statement seems suitable:

put "Hello, World!"

If you run a script with this line, the text is being shown as foreseen - but for lack of a subsequent line feed the prompt for the next command appears in the same line as the output text.

If you change the statement a little

put "Hello, World!" & CR

suddenly, no text appears in the display any more!

Remedy provides a notation in the form

put binary "Hello, World!" & CR

which also servers wonderfully for text output (despite the "binary"). As a pleasant side effect this form additionally avoids any unpleasant consequences of unwanted character set conversions between LiveCode and the operating system (especially on Mac OS X!)

An alternative for put would be the write command:

write "Hello, World!" & CR to stdout

Like put, this statement also writes into the standard output channel. If you want to write to stderr, use

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

instead. Since all these forms are somewhat "bulky", you can help yourself with a few small procedures (usage examples will follow in the next sections):

--------------------------------------------------------------------------------
-- 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

(The names "say" and "cry" are the author's reminiscence of the good old scripting language REXX [2], which already allowed an incredible productivity decades ago)

Since messages must be issued again and again, it is assumed in the following examples, that the lines shown above are at the beginning of each command-line script (or embedded using require).

Input from stdin

Command line scripts often need to read their input from stdin. The associated LiveCode statement is:

read from stdin

The characters read are held by the variable it and can be processed from there. A variant often used is

read from stdin until EOF

which reads all available input data at one go - the script is blocked during that time, however.

Behaviour in Case of Errors

Errors can occur anywhere - but how does a command-line script react in that case? For testing purposes, you may explicitly throw an exception:

throw "Error in CmdLine Script"

Without further measures, this statement results in the following output on stdout:

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

This behavior can be controlled using

set the ErrorMode to "..."

The following statement

set the ErrorMode to "inline"

sends the shown output to stdout, while

set the ErrorMode to "stderr"

sends it to stderr instead. Error reporting may be completely suppressed using

set the ErrorMode to "quiet"

Alternatively, exceptions and errors which are not caught be the script itself may also be processed using an event handler:

--------------------------------------------------------------------------------
-- 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

writes error-related information to stderr and enriches it with details about the location at which the error occurred.

These few lines should also belong to the basic equipment of each command-line script and be inserted after say[ln]/cry[ln] (or requested using require, resp.).

Accessing Command-Line Arguments

LiveCode provides the variables $# and $0, $1, etc. to access command-line arguments. However, the related documentation is wrong, as $0 does not contain the name of the called script, but already the first passed argument. Accordingly, $# returns the exact number of arguments sent to the script.

The following (fully reproduced) script may serve as a test:

#!/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"
?>

Just run this script with different arguments and look at the respective output.

Calling external Operating System Commands

Command line scripts must also rely on other (operating system) commands from time to time. LiveCode offers the shell function for this purpose:

shell("your external command with any arguments")

The result of this call contains the combined stdout and stderr outputs of the executed command. In addition, the result is (allegedly) set to the exit code of the command. A previous

set the hideConsoleWindows to true

is not necessary. You may, however, pre-set the starting directory for the external command using

set the DefaultFolder to "..."

Try and test the shell function with the following Unix/Linux commands:
  • pwd - should provide the pre-set working directory
  • printenv - should display a list of all environment variables
  • no-such-command - should actually fail (but see for yourself)
  • exit 1 - should actually return an exit code <> 0 (but see for yourself)
As you can see, the shell function is certainly useful - but only as long as the external commands work correctly. Unfortunately, you will have to detect any errors yourself from the command output.

Accessing Environment Variables

Using the $ prefix, LiveCode provides access to many (but not all) environment variables. Unfortunately, there is no direct way to display a list of all environment variables in LiveCode: you always need to know the names of any required variables in advance.

The shell function can help here. The following script

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"

displays all available environment variables and their contents - provided that they do not contain any line feeds or similar critical characters.

Unfortunately, you do not have access to all shown variables from within LiveCode. The statement

put binary $_

for example, will not provide any result.

The following script is somewhat more "honest" in this respect:

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"

Which of these two variants turns out to be the "better" one depends on the particular application.

Despite all these problems: have fun with "LiveCode Server"!

Bibliography

[1]
(RunRev Ltd.)
LiveCode | LiveCode Server Guide
The LiveCode Server is an interpreter for LiveCode scripts that is started from the command line (does not offer any graphical user interface) and is intended primarily as a CGI processor (this way, web pages can be processed with LiveCode - thus, you do not necessarily have to learn PHP any longer).
[2]
(Wikipedia)
REXX
From the web site: "Rexx (Restructured Extended Executor) is an interpreted programming language developed at IBM by Mike Cowlishaw. It is a structured, high-level programming language designed for ease of learning and reading. [...]"