Chapter 18

Writing and Running Scripts

Under Unix, a Scheme file can be turned into an executable script using the shell's #! convention. The first two characters of the file must be #!, 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.

The simplest script format uses an absolute path to a mzscheme executable, followed by -qr. For example, if mzscheme is installed in /usr/plt/bin, then a file containing the following text acts as a ``hello world'' script:

  #! /usr/plt/bin/mzscheme -qr
  (display "Hello, world!")
  (newline)

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 will produce the output ``Hello, world!''.

Instead of specifying a complete path to the mzscheme executable, an alternative is to require that mzscheme is in the user's command path, and then ``trampoline'' with /bin/sh:

  #! /bin/sh
  #|
  exec mzscheme -qr "$0" ${1+"$@"}
  |#
  (display "Hello, world!")
  (newline)

The effect is the same, because # starts a one-line comment to /bin/sh, but #| starts a block comment to MzScheme. Finally, calling mzscheme with exec causes the MzScheme process to replace the /bin/sh process.

To implement a script inside module, use -qu instead of -qr:

  #! /usr/plt/bin/mzscheme -qu
  (module hello mzscheme
    (display "Hello, world!")
    (newline))

The -qr command-line flag to MzScheme is an abbreviation for the -q flag followed by the -r flag. As detailed in Chapter 17, -q skips the loading of ~/.mzschemerc, while -r suppresses MzScheme's startup banner, suppresses the read-eval-print loop, and loads the specified file. In the first example above, the file for -r is supplied by the shell's #! handling: it automatically puts the name of the executed script at the end of the #! line. In the second example, the script file name is supplied explicitly with "$0". The -qu flag is similarly an abbreviation for -q followed by -u, which acts like -r except that it requires the script file instead of loading it.

If command-line arguments are supplied to a shell script, the shell attaches them as extra arguments to the script command. Among its other jobs, the -r or -u flag ensures that the extra arguments are not interpreted by MzScheme, but instead put into the current-command-line-arguments parameter as a vector of strings. For example, the following mock script prints each command-line argument back on its own line:

  #! /usr/plt/bin/mzscheme -qu
  (module mock mzscheme
    (for-each (lambda (arg)
                (display arg)
                (newline))
              (vector->list (current-command-line-arguments))))

Thus, mock a b c would print ``a'', ``b'', and ``c'', each on its own line. The /bin/sh version is similar:

  #! /bin/sh
  #|
  exec mzscheme -qu "$0" ${1+"$@"}
  |#
  (module mock mzscheme
    (for-each (lambda (arg)
                (display arg)
                (newline))
              (vector->list (current-command-line-arguments))))

The ${1+"$@"} part of the mzscheme command line copies all shell script arguments to MzScheme for current-command-line-arguments.

For high-quality scripts, use the cmdline MzLib library to parse command-line arguments (see Chapter 7 in PLT MzLib: Libraries Manual). The following hello2 script accepts a --chinese flag to produce Chinese pinyin output. Due to the built-in functionality of the command-line form, the script also accepts a --help or -h flag that produces detailed help on the available command-line options:

  #! /bin/sh
  #|
  exec mzscheme -qu "$0" ${1+"$@"}
  |#
  (module hello2 mzscheme
    (require (lib "cmdline.ss"))
    
    (define chinese? #f)
    
    (command-line
     "hello2"
     (current-command-line-arguments)
     (once-each
      [("--chinese") "Chinese output"
       (set! chinese? #t)]))
    
    (display (if chinese?
                 "Nihao, shijie!"
                 "Hello, world!"))
    (newline))