Running Command Scripts

If you write a Kawa application, it is convenient to be able to execute it directly (from the command line or clicking an icon, say), without have to explicitly run kawa or java. On Unix-like systems the easiest way to do this is to write a small shell script that runs your Kawa application.

For modest-sized applications it is convenient if the shell script and the Kawa code can be in the same file. Unix-like systems support a mechanism where a script can specify a program that should execute it. The convention is that the first line of the file should start with the two characters ‘#!’ followed by the absolute path of the program that should process (interpret) the script.

(Windows has batch files, which are similar.)

This convention works well for script languages that use ‘#’ to indicate the start of a comment, since the interpreter will automatically ignore the line specifying the interpreter filename. Scheme, however, uses ‘#’ as a multi-purpose prefix, and Kawa specifically uses ‘#!’ as a prefix for various Special named constants such as #!optional.

Kawa does recognize the three-character sequence ‘#!/’ at the beginning of a file as special, and ignores it. Here is an example:

#!/usr/local/bin/kawa
(format #t "The command-line was:~{ ~w~}~%" (command-line))

If you copy this text to a file named /home/me/bin/scm-echo, set the execute permission, and make sure it is in your PATH, then you can execute it just by naming it on command line:

$ chmod +x /home/me/bin/scm-echo
$ PATH=/home/me/bin:$PATH
$ scm-env a b
The command-line was: "/home/me/bin/scm-echo" "a" "b"

The system kernel will automatically execute kawa, passing it the filename as an argument.

Note that the full path-name of the kawa interpreter must be hard-wired into the script. This means you may have to edit the script depending on where Kawa is installed on your system. Another possible problem is that the interpreter must be an actual program, not a shell script. Depending on how you configure and install Kawa, kawa can be a real program or a script. You can avoid both problems by the env program, available on most modern Unix-like systems:

#!/usr/bin/env kawa
(format #t "The command-line was:~{ ~w~}~%" (command-line))

This works the same way, but assumes kawa is in the command PATH.

Setting kawa options in the script

If you need to specify extra arguments to kawa, you can run arbitrary shell command inside Scheme block comments. Here is an example:

#!/bin/sh
#|
exec kawa out:base=16 out:radix=yes "$0" "$*"
|#
(format #t "The command-line is:~{ ~w~}.~%" (command-line))
(display "It has ")
(display (apply + (map string-length (command-line))))
(display " characters.")
(newline)

The trick is to hide the shell code from Kawa inside a #|...|# block-comment. The start of the block comment is a line starting with a #, so it is treated as a comment by the shell. You can then invoke kawa (or java directly) as you prefer, setting up class-path and jars as needed, and passing whatever arguments you want. (The shell replaces the "$0" by the name of the script, and replaces the "$@" by the remaining arguments passed to the script.) You need to make sure the shell finishes before it reaches the end of the block comment or the Scheme code, which would confuse it. The example uses exec, which tells the shell to replace itself by kawa; an alternative is to use the shell exit command.

If you copy the above file to /tmp/sch-echo and make that file executable, you can run it directly:

$ /tmp/scm-echo "a b" "c d"
The command-line is: "/tmp/scm-echo" "a b c d".
It has #x14 characters.

When the Kawa reader sees the initial #/ it sets the command name to the file name, so it can be used by a future call to (command-name). If you want to override this you can use the -Dkawa.command.name=name option.

Using comments this way has the advantage that you have the option of running the script “manually” if you prefer:

$ kawa /tmp/scm-echo out:base=8 "x y"
The command-line is: "/tmp/scm-echo" "out:base=8" "x y".
It has 26 characters.

Other ways to pass options using meta-arg or –script

An argument consisting of just a \ (backslash) causes Kawa to read the second line looking for options. (Quotes and backslashes work like in the shell.) These replace the backslash in the command line.

This is a less verbose mechanism, but it requires an absolute path to kawa, due to shell limitations.

#!/usr/bin/kawa \
  --scheme --full-tailcalls
(format #t "The command-line is:~{ ~w~}.~%" (command-line))

In this case the effective command line received by Kawa will be --scheme, --full-tailcalls, followed by the script filename, followed by other arguments specified when running the script.

The backslash used this way originated in scsh where it is called the meta-arg. (Unlike scsh, Kawa’s #! is not a block comment, but a rest-of-line, though the backslash causes the following line to also be skipped.)

An alternative method is to use the --script2 option, which tells Kawa to execute the script after ignoring the initial two lines. For example:

#!/bin/sh
exec kawa --commonlisp out:base=16 --script2 "$0" "$@"
(setq xx 20) (display xx) (newline)

This is slightly more compact than using block-comments as shown earlier, but it has the disadvantage that you can’t explicitly use kawa or java to run the script unless you make sure to pass it the --script2 option.

Scripts for compiled code

If you compile your Kawa application to class files (or better: a jar file), you probably still want to write a small shell script to set things up. Here is one method:

#!/bin/sh
export CLASSPATH=/my/path
exec kawa -Dkawa.command.name="$0" foo "$@"

Using the kawa front-end is a convenience, since it automatically sets up the paths for the Kawa classes, and (if enabled) it provides readline support for the default input port.

Setting the kawa.command.name property to "$0" (the filename used to invoke the script) enables (command-line) to use the script name as the command name.

You can invoke java directly, which is necessary when running a jar file:

#!/bin/sh
exec java -cp /path/to/kawa -Dkawa.command.name="$0" foo.jar "$@"