Version: 4.2.1

18.2 Unix Scripts

Under Unix and Mac OS X, a Scheme file can be turned into an executable script using the shell’s #! convention. The first two characters of the file must be #!; the next character must be either a space or /, and the remainder of the first line must be a command to execute the script. For some platforms, the total length of the first line is restricted to 32 characters, and sometimes the space is required.

The simplest script format uses an absolute path to a mzscheme executable followed by a module declaration. For example, if mzscheme is installed in "/usr/local/bin", then a file containing the following text acts as a “hello world” script:

  #! /usr/local/bin/mzscheme
  #lang scheme/base
  "Hello, world!"

In particular, if the above is put into a file "hello" and the file is made executable (e.g., with chmod a+x hello), then typing ./hello at the shell prompt produces the output "Hello, world!".

The above script works because the operating system automatically puts the path to the script as the argument to the program started by the #! line, and because mzscheme treats a single non-flag argument as a file containing a module to run.

Instead of specifying a complete path to the mzscheme executable, a popular alternative is to require that mzscheme is in the user’s command path, and then “trampoline” using /usr/bin/env:

  #! /usr/bin/env mzscheme
  #lang scheme/base
  "Hello, world!"

In either case, command-line arguments to a script are available via current-command-line-arguments:

  #! /usr/bin/env mzscheme
  #lang scheme/base
  (printf "Given arguments: ~s\n"
          (current-command-line-arguments))

If the name of the script is needed, it is available via (find-system-path 'run-file), instead of via (current-command-line-arguments).

Usually, then best way to handle command-line arguments is to parse them using the command-line form provided by scheme. The command-line form extracts command-line arguments from (current-command-line-arguments) by default:

  #! /usr/bin/env mzscheme
  #lang scheme
  
  (define verbose? (make-parameter #f))
  
  (define greeting
    (command-line
     #:once-each
     [("-v") "Verbose mode" (verbose? #t)]
     #:args
     (str) str))
  
  (printf "~a~a\n"
          greeting
          (if (verbose?) " to you, too!" ""))

Try running the above script with the --help flag to see what command-line arguments are allowed by the script.

An even more general trampoline uses /bin/sh plus some lines that are comments in one language and expressions in the other. This trampoline is more complicated, but it provides more control over command-line arguments to mzscheme:

  #! /bin/sh
  #|
  exec mzscheme -cu "$0" ${1+"$@"}
  |#
  #lang scheme/base
  (printf "This script started slowly, because the use of\n")
  (printf "bytecode files has been disabled via -c.\n")
  (printf "Given arguments: ~s\n"
          (current-command-line-arguments))

Note that #! starts a line comment in Scheme, and #|...|# forms a block comment. Meanwhile, # also starts a shell-script comment, while exec mzscheme aborts the shell script to start mzscheme. That way, the script file turns out to be valid input to both /bin/sh and mzscheme.