Previous: , Up: Operational Features   [Contents][Index]

3.13 Scripting


3.13.1 Unix Scheme Scripts

In reading this section, keep in mind that the first line of a script file has (different) meanings to SCM and the operating system (execve).

file: #! interpreter \ …

On unix systems, a Shell-Script is a file (with execute permissions) whose first two characters are ‘#!’. The interpreter argument must be the pathname of the program to process the rest of the file. The directories named by environment variable PATH are not searched to find interpreter.

When executing a shell-script, the operating system invokes interpreter with a single argument encapsulating the rest of the first line’s contents (if not just whitespace), the pathname of the Scheme Script file, and then any arguments which the shell-script was invoked with.

Put one space character between ‘#!’ and the first character of interpreter (‘/’). The interpreter name is followed by ‘ \’; SCM substitutes the second line of file for ‘\’ (and the rest of the line), then appends any arguments given on the command line invoking this Scheme-Script.

When SCM executes the script, the Scheme variable *script* will be set to the script pathname. The last argument before ‘!#’ on the second line should be ‘-’; SCM will load the script file, preserve the unprocessed arguments, and set *argv* to a list of the script pathname and the unprocessed arguments.

Note that the interpreter, not the operating system, provides the ‘\’ substitution; this will only take place if interpreter is a SCM or SCSH interpreter.

Read syntax: #! ignored !#

When the first two characters of the file being loaded are #! and a ‘\’ is present before a newline in the file, all characters up to ‘!#’ will be ignored by SCM read.

This combination of interpretatons allows SCM source files to be used as POSIX shell-scripts if the first line is:

#! /usr/local/bin/scm \

The following Scheme-Script prints factorial of its argument:

#! /usr/local/bin/scm \ %0 %*
- !#

(define (fact.script args)
  (cond ((and (= 1 (length args))
              (string->number (car args)))
         => (lambda (n) (print (fact n)) #t))
        (else (fact.usage))))

(define (fact.usage)
  (print *argv*)
  (display "\
Usage: fact N
  Returns the factorial of N.
"
           (current-error-port))
  #f)

(define (fact n) (if (< n 2) 1 (* n (fact (+ -1 n)))))

(if *script* (exit (fact.script (list-tail *argv* *optind*))))
./fact 32
⇒
263130836933693530167218012160000000

If the wrong number of arguments is given, fact prints its argv with usage information.

./fact 3 2
-|
("./fact" "3" "2") 
Usage: fact N
  Returns the factorial of N.

3.13.2 MS-DOS Compatible Scripts

It turns out that we can create scheme-scripts which run both under unix and MS-DOS. To implement this, I have written the MS-DOS programs: #!.bat and !#.exe, which are available from: http://groups.csail.mit.edu/mac/ftpdir/scm/sharpbang.zip

With these two programs installed in a PATH directory, we have the following syntax for <program>.BAT files.

file: #! interpreter \ %0 %*

The first two characters of the Scheme-Script are ‘#!’. The interpreter can be either a unix style program path (using ‘/’ between filename components) or a DOS program name or path. The rest of the first line of the Scheme-Script should be literally ‘\ %0 %*, as shown.

If interpreter has ‘/’ in it, interpreter is converted to a DOS style filename (‘/’ ⇒ ‘\’).

In looking for an executable named interpreter, #! first checks this (converted) filename; if interpreter doesn’t exist, it then tries to find a program named like the string starting after the last ‘\’ (or ‘/’) in interpreter. When searching for executables, #! tries all directories named by environment variable PATH.

Once the interpreter executable path is found, arguments are processed in the manner of scheme-shell, with all the text after the ‘\’ taken as part of the meta-argument. More precisely, #! calls interpreter with any options on the second line of the Scheme-Script up to ‘!#’, the name of the Scheme-Script file, and then any of at most 8 arguments given on the command line invoking this Scheme-Script.

The previous example Scheme-Script works in both MS-DOS and unix systems.


3.13.3 Unix Shell Scripts

Scheme-scripts suffer from two drawbacks:

The following approach solves these problems at the expense of slower startup. Make ‘#! /bin/sh’ the first line and prepend every subsequent line to be executed by the shell with :;. The last line to be executed by the shell should contain an exec command; exec tail-calls its argument.

/bin/sh is thus invoked with the name of the script file, which it executes as a *sh script. Usually the second line starts ‘:;exec scm -f$0’, which executes scm, which in turn loads the script file. When SCM loads the script file, it ignores the first and second lines, and evaluates the rest of the file as Scheme source code.

The second line of the script file does not have the length restriction mentioned above. Also, /bin/sh searches the directories listed in the ‘PATH’ environment variable for ‘scm’, eliminating the need to use absolute locations in order to invoke a program.

The following example additionally sets *script* to the script argument, making it compatible with the scheme code of the previous example.

#! /bin/sh
:;exec scm -e"(set! *script* \"$0\")" -l$0 "$@"

(define (fact.script args)
  (cond ((and (= 1 (length args))
              (string->number (car args)))
         => (lambda (n) (print (fact n)) #t))
        (else (fact.usage))))

(define (fact.usage)
  (print *argv*)
  (display "\
Usage: fact N
  Returns the factorial of N.
"
           (current-error-port))
  #f)

(define (fact n) (if (< n 2) 1 (* n (fact (+ -1 n)))))

(if *script* (exit (fact.script (list-tail *argv* *optind*))))
./fact 6
⇒ 720 

Previous: , Up: Operational Features   [Contents][Index]