Wade Humeniuk <whumeniu-delete-this-antispam-device@telus.net> wrote:
+---------------
| Joe Marshall wrote:
| > At regular intervals on comp.lang.lisp someone asks how to create
| > 'standalone executables' Common Lisp. Rather than enter into a
| > technical discussion of what is meant by a 'standalone executables'...
|
| Yes, entering into technical discussions has little affect.
| IMO, a direct reference to a tutorial example should be given.
...
| for example:
| CMUCL startup with a #! executable "hello World" example.
+---------------
Yes, some tutorial material on that should be helpful, since as distributed
CMUCL does not come with a built-in reader macro for "#!" [even though most
of us using CMUCL for any time quickly build a core image containing one!].
Fortunately, most of the Unix/Linux operating systems assume a /bin/sh
script if the script does not start with "#!" (or "#", for "csh" users),
so with the minimal cost of a fork/exec of /bin/sh, the following idiom[1]
suffices for most scripting needs:
% cat foo
":" ; exec cmucl -quiet -noinit -load "`which $0`" ${1+"$@"}
(format t "Hello, world!~%")
(quit)
% time foo
Hello, world!
0.009u 0.012s 0:00.03 33.3% 592+3636k 0+0io 0pf+0w
%
19 milliseconds -- not too shabby! [Well, on a 1.8 GHz Athlon... ;-} ]
Add a few "convenience features", and you get this:
% cat foo2
":" ; exec cmucl -quiet -noinit -load "`which $0`" ${1+"$@"}
;;; Provide script name and command-line args [if any] to script:
(defvar *script-name* (nth 4 *command-line-strings*)) ; See [2], below.
(defvar *script-args* (nthcdr 5 *command-line-strings*))
(defvar *script-interactive-repl* nil)
(format t "Hello, world!~%")
(format t "My name = '~a'~%" *script-name*)
(loop for i from 1
and a in *script-args*
do (format t "arg[~a] = '~a'~%" i a))
(unless *script-interactive-repl* ; set when debugging.
(quit))
% foo2 bar baz 'many words here'
Hello, world!
My name = 'foo2'
arg[1] = 'bar'
arg[2] = 'baz'
arg[3] = 'many words here'
%
And as long as I'm on the topic, it seems I made a mistake in my posting
the other day when I showed a script "header" for FASL files. Turns out it
was written for an older version of CMUCL in which READ-LINE didn't complain
if given a binary stream. But CMUCL-18e's READ-LINE *does* bitch and moan
(arguably rightfully), so here is an updated version (tested) that works
nicely with CMUCL-18e:
% cat bin/cmucl-fasl-script-header
":" ; exec cmucl -quiet -noinit -load "`which $0`" ${1+"$@"}
;;;; Hack to allow CMUCL FASL files to run as shell scripts.
;;;;
;;;; USAGE: cat {this_file} foo1.x86f ... fooN.x86f > foo ; chmod +x foo
;;;; The last "fooN.x86f" must end with a top-level form which starts the
;;;; application [and then should fall-through, *not* exit! -- see below].
;;; Provide script name and command-line args [if any] to FASL file(s):
(defvar *script-name* (nth 4 *command-line-strings*))
(defvar *script-args* (nthcdr 5 *command-line-strings*))
(defvar *script-interactive-repl* nil)
(setf ext::*complain-about-illegal-switches* nil)
(with-open-file (s *script-name* :element-type '(unsigned-byte 8))
(loop for c = (read-byte s) ; Eat header text from stream through
until (= c 90) ; final "magic character" [uppercase "z",
finally (read-byte s)) ; see below] and the newline which
(load s :contents :binary) ; follows, then pass the rest to FASLOAD.
(if *script-interactive-repl* ; If set later, start up a Lisp top-level.
(continue) ; Keep initial LOAD of script from falling into FASL files.
(quit))) ; So non-REPL case exits cleanly.
;;;; NOTA BENE: The end of this line is magic: Z
%
which is used this way:
% cat foo3.lisp
(format t "Hello, world!~%")
(format t "My name = '~a'~%" *script-name*)
(loop for i from 1
and a in *script-args*
do (format t "arg[~a] = '~a'~%" i a))
% cmucl-compile foo3.lisp
...compiler chatter...
; foo3.x86f written.
; Compilation finished in 0:00:00.
% cat cmucl-fasl-script-header foo3.x86f >foo3
% chmod +x foo3
% ls -l foo3
-rwxr-xr-x 1 rpw3 rpw3 2714 Feb 7 05:00 foo3
% foo3 what a "really fun" hack.
Hello, world!
My name = 'foo3'
arg[1] = 'what'
arg[2] = 'a'
arg[3] = 'really fun'
arg[4] = 'hack.'
%
-Rob
[1] Which I first learned from the PLT scheme, crowd. Thanks guys!
[2] CMUCL experts may wonder why I didn't use the "command-line-switch"
routines here, e.g., (car (get-command-line-switch "load")) instead
of (nth 4 *command-line-strings*)) for *script-name*, etc., and the
(setf ext::*complain-about-illegal-switches* nil). The reason is that
the above can easily be emulated in CLISP and other CLs, and is more
similar to what the user expects coming from a C or Perl environment.
And the SETF makes it easy to use application-specific options, e.g.:
% foo3 -v -o bar bar.in
Hello, world!
My name = 'foo3'
arg[1] = '-v'
arg[2] = '-o'
arg[3] = 'bar'
arg[4] = 'bar.in'
%
-----
Rob Warnock <rpw3@rpw3.org>
627 26th Avenue <URL:http://rpw3.org/>
San Mateo, CA 94403 (650)572-2607