Previous: Internal State, Up: Operational Features [Contents][Index]
Next: MS-DOS Compatible Scripts, Previous: Scripting, Up: Scripting [Contents][Index]
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
).
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.
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.
Next: Unix Shell Scripts, Previous: Unix Scheme Scripts, Up: Scripting [Contents][Index]
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.
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.
Previous: MS-DOS Compatible Scripts, Up: Scripting [Contents][Index]
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: Internal State, Up: Operational Features [Contents][Index]