For Development HEAD DRAFTSearch (procedure/syntax/module):

3.3 Writing Scheme scripts

When a Scheme program file is given to gosh, it makes the user module as the current module, binds a global variable *argv* to the list of the remaining command-line arguments, and then loads the Scheme program. If the first line of scheme-file begins with two character sequence “#!”, the entire line is ignored by gosh. This is useful to write a Scheme program that works as an executable script in unix-like systems.

Typical Gauche script has the first line like these

#!/usr/local/bin/gosh
  or,
#!/usr/bin/env gosh
  or,
#!/bin/sh
#|
exec gosh -- $0 "$@"
|#

The second and third form uses a “shell trampoline” technique so that the script works as far as gosh is in the PATH. The third form is useful when you want to pass extra arguments to gosh, for typically #!-magic of executable scripts has limitations for the number of arguments to pass the interpreter.

After the file is successfully loaded, gosh calls a procedure named ‘main’ if it is defined in the user module. Main receives a single argument, a list of command line arguments. Its first element is the script name itself.

When main returns, and its value is an integer, gosh uses it for exit code of the program. Otherwise, gosh exits with exit code 70 (EX_SOFTWARE). This behavior is compatible with the SRFI-22.

If the main procedure is not defined, gosh exits after loading the script file.

Although you can still write the program main body as toplevel expressions, like shell scripts or Perl scripts, it is much convenient to use this ‘main’ convention, for you can load the script file interactively to debug.

Using -m command-line option, you can make gosh call main procedure defined in a module other than the user module. It is sometimes handy to write a Scheme module that can also be executed as a script.

For example, you write a Scheme module foo and within it, you define the main procedure. You don’t need to export it. If the file is loaded as a module, the main procedure doesn’t do anything. But if you specify -m foo option and give the file as a Scheme script to gosh, then the main procedure is invoked after loading the script. You can code tests or small example application in such an alternate main procedure.

Note on R7RS Scripts: If the script is written in R7RS Scheme (which can be distinguished by the first import declaration, see Three import forms), it is read into r7rs.user module and its main isn’t called. You can give -mr7rs.main command-line argument to call the main function in R7RS script. Alternatively, as specified in SRFI-22, if the script interpreter’s basename is scheme-r7rs, we assume the script is R7RS SRFI-22 script and calls main in r7rs.user module rather than user module. We don’t install such an alias, but you can manually make symbolic link or just copy gosh binary as scheme-r7rs.

Although the argument of the main procedure is the standard way to receive the command-line arguments, there are a couple of other ways to access to the info. See Command-line arguments, for the details.

Now I show several simple examples below. First, this script works like cat(1), without any command-line option processing and error handling.

#!/usr/bin/env gosh

(define (main args)   ;entry point
  (if (null? (cdr args))
      (copy-port (current-input-port) (current-output-port))
      (for-each (lambda (file)
                  (call-with-input-file file
                    (lambda (in)
                      (copy-port in (current-output-port)))))
                (cdr args)))
  0)

The following script is a simple grep command.

#!/usr/bin/env gosh

(define (usage program-name)
  (format (current-error-port)
          "Usage: ~a regexp file ...\n" program-name)
  (exit 2))

(define (grep rx port)
  (with-input-from-port port
    (lambda ()
      (port-for-each
       (lambda (line)
         (when (rxmatch rx line)
           (format #t "~a:~a: ~a\n"
                   (port-name port)
                   (- (port-current-line port) 1)
                   line)))
       read-line))))

(define (main args)
  (if (null? (cdr args))
      (usage (car args))
      (let ((rx (string->regexp (cadr args))))
        (if (null? (cddr args))
            (grep rx (current-input-port))
            (for-each (lambda (f)
                        (call-with-input-file f
                          (lambda (p) (grep rx p))))
                      (cddr args)))))
  0)

See also gauche.parseopt - Parsing command-line options, for a convenient way to parse command-line options.



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT