Tutorial - Shell Scripting
Introduction
Candle provides some standard library functions to work with the
shell
of the native OS. And this is the most convenient, if not the only, way
to communicate with the native OS, in Candle, at the moment.
There are several advantages of using Candle for shelling scripting
than using traditional shell scripting languages, like Bash:
- Candle is more powerful. Its
advanced features like set-oriented
query and pattern matching are hard to be matched by any shell
scripting language.
- Candle is more modular.
Script importing in Candle is much
easier. And features like function overloading and OOP support are not
present in most shell scripting languages.
- Candle is more functional.
Candle enforces the separation of functional code from procedural code.
- Candle is cross-platform.
While Candle is cross-platform, the shell commands invoked are not. But
if you write a thin layer that wraps around the native shell
commands, than the entire shell script in Candle can be highly portable.
(Note: in current beta release,
Candle's data model has not been extended to
support files and directories. In Candle 1.0 formal release, you will
be able to use path expressions on files and directories. One major
advantage of shell scripting language over general-purpose scripting
languages is its convenience in file selection. With the extended data
model, Candle will be as convenient as, if not more
than, traditional shell scripting languages.)
Calling a Shell Command
Below is a simple Candle script that calls the hostname
command using candle:io:exec()
library function:
<?csp1.0?>
method main() as string {
candle:io:exec("hostname"); !! any other cmd
that works on both Windows and
Linux?
return result();
}
On Windows,
Candle starts a child process and executes the command line using
Windows cmd.exe
.
On Linux, Candle calls C function popen()
,
which in turn invokes /bin/sh
.
The output from the shell command is stored as Candle's standard method
return value, and can be retrieved through the result()
function.
And when the functions returns, the return value from the main
method is automatically printed to STDOUT.
The second prototype of the candle:io:
exec()
function, accepts an additional parameter to tell Candle engine whether
it
should wait for the shell command to terminate before it returns. This
can be useful if you just want to trigger a shell command without
waiting for
it to terminate, e.g.:
<?csp1.0?>
method main() {
candle:io:exec("gedit");
!!gedit will not terminate until the editor is closed
}
Standard Input (STDIN) and
Output (STDOUT)
To write to the standard output (STDOUT), you can call candle:io:writeln()
.
If you don't want a line break to be added to the output, you can
call candle:io:write()
.
<?csp1.0?>
method main() as string {
candle:io:writeln("writing a line of
text to STDOUT.");
candle:io:write("writing some text to
STDOUT; ");
candle:io:write("additional text on the
same line.");
}
To read from the standard input, you can call candle:io:readln()
.
The input is stored as Candle's standard method return value, and can
be retrieved through the result()
function.
<?csp1.0?>
method main() as string {
candle:io:write("Please enter a line of text:");
candle:io:readln();
return result();
}
Parsing the Command Output
If you want to process the output of the shell command in a structured
manner, it can be easily done in Candle. You just need to
write a grammar that matches the output, and use the xparse()
function to turn the output into an AST (abstract syntax tree). Here's
an example that converts the listing of ls
command into
an AST:
<?csp1.0?>
grammar ls-cmd-grammar {
root = preface-line, line+;
preface-line = "total", sp, digits, lf;
sp = (" " | "&tb;")+;
lf = "&lf;";
line = file-type, permission, sp,
link-cnt, sp, user, sp, group, sp,
size, sp, month, sp, day, sp,
time,
sp, filename, lf;
file-type = char;
permission = ("r" | "w" | "x" | "-")+;
link-cnt = digits;
user = (char - sp)+;
group = (char - sp)+;
size = digits;
month = letters;
day = digits;
time = digits, (":", digits)?;
filename = (char - lf)+;
}
method main() as element {
candle:io:exec("ls -la");
let ls-output = xparse(result(),
value::ls-cmd-grammar);
return ls-output;
}
The AST can then be easily processed by Candle's advanced query
features, e.g. transforming into a HTML table with highlighting.
As xparse()
uses a grammar to do the pattern match, its performance can be much
slower than simple string functions. So if you just want to do some
simple value extraction or want good performance,
you can use string functions to process the output or pipe the
output to other text processing shell commands like grep
or awk
. xparse()
should only be used when
you want to process the entire output in a structured manner.