racket/collects/scribblings/guide/scripts.scrbl
2010-04-30 08:02:57 -06:00

145 lines
4.4 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scheme/cmdline
"guide-utils.ss")
@title[#:tag "scripts"]{Scripts}
Racket files can be turned into executable scripts under Unix and Mac
OS X. Under Windows, a compatibility layer like Cygwin support the
same kind of scripts, or scripts can be implemented as batch files.
@section{Unix Scripts}
In a Unix environment (including Linux and Mac OS X), a Racket file can
be turned into an executable script using the shell's @as-index{@tt{#!}}
convention. The first two characters of the file must be @litchar{#!};
the next character must be either a space or @litchar{/}, 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 @exec{racket}
executable followed by a module declaration. For example, if
@exec{racket} is installed in @filepath{/usr/local/bin}, then a file
containing the following text acts as a ``hello world'' script:
@verbatim[#:indent 2]{
#! /usr/local/bin/racket
#lang racket/base
"Hello, world!"
}
In particular, if the above is put into a file @filepath{hello} and
the file is made executable (e.g., with @exec{chmod a+x hello}), then
typing @exec{./hello} at the shell prompt produces the output
@tt{"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
@tt{#!} line, and because @exec{racket} treats a single non-flag
argument as a file containing a module to run.
Instead of specifying a complete path to the @exec{racket}
executable, a popular alternative is to require that @exec{racket}
is in the user's command path, and then ``trampoline'' using
@exec{/usr/bin/env}:
@verbatim[#:indent 2]{
#! /usr/bin/env racket
#lang racket/base
"Hello, world!"
}
In either case, command-line arguments to a script are available via
@racket[current-command-line-arguments]:
@verbatim[#:indent 2]{
#! /usr/bin/env racket
#lang racket/base
(printf "Given arguments: ~s\n"
(current-command-line-arguments))
}
If the name of the script is needed, it is available via
@racket[(find-system-path 'run-file)], instead of via
@racket[(current-command-line-arguments)].
Usually, then best way to handle command-line arguments is to parse
them using the @racket[command-line] form provided by
@racketmodname[racket]. The @racket[command-line] form extracts
command-line arguments from @racket[(current-command-line-arguments)]
by default:
@verbatim[#:indent 2]{
#! /usr/bin/env racket
#lang racket
(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 @DFlag{help} flag to see what
command-line arguments are allowed by the script.
An even more general trampoline uses @exec{/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 @exec{racket}:
@verbatim[#:indent 2]|{
#! /bin/sh
#|
exec racket -cu "$0" ${1+"$@"}
|#
#lang racket/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 @litchar{#!} starts a line comment in Racket, and
@litchar{#|}...@litchar{|#} forms a block comment. Meanwhile,
@litchar{#} also starts a shell-script comment, while @exec{exec
racket} aborts the shell script to start @exec{racket}. That way,
the script file turns out to be valid input to both @exec{/bin/sh} and
@exec{racket}.
@section{Windows Batch Files}
A similar trick can be used to write Racket code in Windows
@as-index{@tt{.bat}} batch files:
@verbatim[#:indent 2]|{
; @echo off
; Racket.exe "%~f0" %*
; exit /b
#lang racket/base
"Hello, world!"
}|
@;{
Original trick from Ben Goetter, who used:
; @echo off && REM -*- racket -*-
; "%RACKET%" "%~f0" %*
; exit /b
#lang racket
...
it might be worth documenting the Emacs "-*-" convention and a way to
set environment variables -- but that would be needed in the unix part
too.
;}