#
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 `require`s the script file instead of ``load``ing 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 8 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))