Subject: CMUCL "-script" hack now available
From: (Rob Warnock)
Date: Sat, 24 Nov 2007 06:13:25 -0600
Newsgroups: comp.lang.lisp
Message-ID: <>
A few days ago, in <>,
I mentioned my CMUCL "-script" hack once again [which I've done far too
many times over the past few years], that lets you do stuff like this:

    $ cat ./hello.cmucl 
    #!/usr/local/bin/cmucl -script
    (format t "Hello, world!~%")
    (loop for arg in (cons *script-name* *script-args*)
	  and i from 0
      do (format t "argv[~a] = ~s~%" i arg))

    $ ./hello.cmucl -foo=37 -bar baz gorp
    Hello, world!
    argv[0] = "./hello.cmucl"
    argv[1] = "-foo=37"
    argv[2] = "-bar"
    argv[3] = "baz"
    argv[4] = "gorp"

and promised [once again!] to share it. Well, it's here:

I have *not* cleaned it up at all, so if you actually want to
understand what it does [or even worse, *why* it does it that way]
you're probably in for some rough slogging. But if you just want
to *use* it to write scripts like the above, here's all you have
to do [instructions are also embedded in the file itself]:

1. Download the above URL & save it in "library:site-switch-script.lisp"
   ["library:" is typically "/usr/local/lib/cmucl/lib/", but YMMV].

2. Append the following expression to your "library:site-init.lisp" file:

     (let ((option (second *command-line-strings*)))
       (when (equal option "-script")
	 (load (merge-pathnames "site-switch-script" *load-pathname*))))

3. The startup time for scripts will be measurably faster if you
   COMPILE-FILE both "site-switch-script.lisp" & "site-init.lisp".
   [But *don't* do this if you tend to change versions of CMUCL often,
   since if "site-init.lisp" is compiled with the wrong version,
   CMUCL won't start up correctly.]

4. Prepend source or FASL "scripts" with "#!/usr/local/bin/cmucl -script"
   [or wherever you have an executable of CMUCL installed] and then
   "chmod a+x" them. Such files will now run much the same as if you said:

     $ cmucl -noinit -quiet -batch -load {filename} -eval '(quit)'

   Some of that behavior may be modified by pushing items onto the
   *SCRIPT-EXIT-HOOKS* variable [see inside the file for details].

Again, this is a hack, albeit a *very* useful one in my experience
with it over the past several years. If enough other people also
find it useful, I'll try to get the CMUCL maintainers to include
a version of the "-script" option in the standard distribution at
some point.

[I would have pushed for such before now, but the CMUCL command-line
option decoding is... well, "baroque" would be too kind. It needs a
*lot* of cleaning up, and I confess, I was lazy. I just hacked around
it altogether. (*blush*)]


p.s. Yes, #3 above said "source *or* FASL" -- it works for either
[though not both at the same time!]. And you can concatenate multiple
FASLs into a single "script", e.g.:

    $ cat script_header.cmucl
    #!/usr/local/bin/cmucl -script
    $ head foo{1,2,3}.lisp
    ==> foo1.lisp <==
    (defun foo1 () (format t "This is foo1.~%"))

    ==> foo2.lisp <==
    (defun foo2 () (format t "This is foo2.~%"))

    ==> foo3.lisp <==
    (declaim (ftype function foo1 foo2))
    $ cmu-compile foo{1,2,3}.lisp
    ...[compile chatter]...
    $ cat script_header.cmucl foo1.x86f foo2.x86f foo3.x86f > foo
    $ chmod a+x foo
    $ ./foo
    This is foo1.
    This is foo2.

p.p.s. If you want a script which just sets up an environment
and then fall into a standard top-level REPL, here's an example:

    $ cat ./test-repl
    #!/usr/local/bin/cmucl -script
    (format t "~%Welcome to the customized test REPL~%~%")
    (setf *batch-mode* t)           ; So EOF will exit.
    (setf *prompt* "test> ")        ; Custom prompt.
    ;;; ...[any other initializations/customizations you might want]...
    (push :repl *script-exit-hooks*)

    $ ./test-repl

    Welcome to the customized test REPL

    test> (expt 2 100)

    test> ^D^D

[Yes, it is an artifact of CMUCL's READ processing that even with
*BATCH-MODE* set, it still takes *two* EOFs in a row to get it to exit.]

p.p.p.s. If you want interrupts (SIGINT, ^C) to just exit instead of
dropping into the debugger, include this in your script:

    (system:enable-interrupt unix:sigint (lambda (signal code scp)
					   (declare (ignore signal code scp))
					   (unix:unix-exit 1)
					   (error "Unix-exit didn't exit!")))

Rob Warnock			<>
627 26th Avenue			<URL:>
San Mateo, CA 94403		(650)572-2607