chiark / gitweb /
README.org: Clarify and fix the discussion of `-p', mentioning `-d'.
[runlisp] / README.org
1 # -*-org-*-
2 #+TITLE: ~runlisp~ -- run scripts written in Common Lisp
3 #+AUTHOR: Mark Wooding
4 #+LaTeX_CLASS: strayman
5 #+LaTeX_HEADER: \usepackage{tikz, gnuplot-lua-tikz}
6 #+EXPORT_FILE_NAME: doc/README.pdf
7
8 ~runlisp~ is a small C program intended to be run from a script ~#!~
9 line.  It selects and invokes a Common Lisp implementation, so as to run
10 the script.  In this sense, ~runlisp~ is a partial replacement for
11 ~cl-launch~.
12
13 Currently, the following Lisp implementations are supported:
14
15   + Armed Bear Common Lisp (~abcl~),
16   + Clozure Common Lisp (~ccl~),
17   + GNU CLisp (~clisp~),
18   + Carnegie--Mellon Univerity Common Lisp (~cmucl~),
19   + Embeddable Common Lisp (~ecl~), and
20   + Steel Bank Common Lisp (~sbcl~).
21
22 Adding more Lisps is simply a matter of writing the necessary runes in a
23 configuration file.  Of course, there's a benefit to having a collection
24 of high-quality configuration runes curated centrally, so I'm happy to
25 accept submissions in support of any free[fn:free] Lisp implementations.
26
27 [fn:free] Here I mean free as in freedom.
28
29
30 * Writing scripts in Common Lisp
31
32 ** Basic use
33
34 The obvious way to use ~runlisp~ is in a shebang (~#!~) line at the top
35 of a script.  For example:
36
37 : #! /usr/local/bin/runlisp
38 : (format t "Hello from Lisp!~%")
39
40 Script interpreters must be named with absolute pathnames in shebang
41 lines; if your ~runlisp~ is installed somewhere other than
42 ~/usr/local/bin/~ then you'll need to write something different.
43 Alternatively, a common hack involves abusing the ~env~ program as a
44 script interpreter, because it will do a path search for the program
45 it's supposed to run:
46
47 : #! /usr/bin/env runlisp
48 : (format t "Hello from Lisp!~%")
49
50 ** Specific Lisps
51
52 Lisp implementations are not created equal -- for good reason.  If your
53 script depends on the features of some particular Lisp implementation,
54 then you can tell ~runlisp~ that it must use that implementation to run
55 your script using the ~-L~ option; for example:
56
57 : #! /usr/local/bin/runlisp -Lsbcl
58 : (format t "Hello from Steel Bank Common Lisp!~%")
59
60 If your script supports several Lisps, but not all, then list them all
61 in the ~-L~ option, separated by commas:
62
63 : #! /usr/local/bin/runlisp -Lsbcl,ccl
64 : (format t #.(concatenate 'string
65 :                          "Hello from "
66 :                          #+sbcl "Steel Bank"
67 :                          #+ccl "Clozure"
68 :                          #-(or sbcl ccl) "an unexpected"
69 :                          " Common Lisp!~%"))
70
71 ** Embedded options
72
73 If your script requires features of particular Lisp implementations
74 /and/ you don't want to hardcode an absolute path to ~runlisp~, then you
75 have a problem.  Most Unix-like operating systems will parse a shebang
76 line into the initial ~#!~, the pathname to the interpreter program,
77 and a /single/ optional argument: any further spaces don't separate
78 further arguments: they just get included in the first argument, all the
79 way up to the end of the line.  So
80
81 : #! /usr/bin/env runlisp -Lsbcl
82 : (format t "Hello from Steel Bank Common Lisp!~%")
83
84 won't work: it'll just try to run a program named ~runlisp -Lsbcl~, with
85 a space in the middle of its name, and that's quite unlikely to exist.
86
87 To help with this situation, ~runlisp~ reads /embedded options/ from
88 your script.  Specifically, if the script's second line contains the
89 token ~@RUNLISP:~ then ~runlisp~ will parse additional options from this
90 line.  So the following will work properly.
91
92 : #! /usr/bin/env runlisp
93 : ;;; @RUNLISP: -Lsbcl
94 : (format t "Hello from Steel Bank Common Lisp!~%")
95
96 Embedded options are split at spaces properly.  Spaces can be escaped or
97 quoted in (an approximation to) the usual shell manner, should that be
98 necessary.  See the manpage for the gory details.
99
100 ** Common environment
101
102 ~runlisp~ puts some effort into making sure that Lisp scripts get the
103 same view of the world regardless of which implementation is running
104 them.
105
106 For example:
107
108   + The ~asdf~ and ~uiop~ systems are loaded and ready for use.
109
110   + The script's command-line arguments are available in
111     ~uiop:*command-line-arguments*~.  Its name can be found by calling
112     ~(uiop:argv0)~ -- though it's probably also in ~*load-pathname*~.
113
114   + The prevailing Unix standard input, output, and error files are
115     available through the Lisp ~*standard-input*~, ~*standard-output*~,
116     and ~*error-ouptut*~ streams, respectively.  (This is, alas, not a
117     foregone conclusion.)
118
119   + The keyword ~:runlisp-script~ is added to the ~*features*~ list.
120     This means that your script can tell whether it's being run from the
121     command line, and should therefore do its thing and then quit; or
122     merely being loaded into a Lisp system, e.g., for debugging or
123     development, and should sit still and not do anything until it's
124     asked.
125
126 See the manual for the complete list of guarantees.
127
128
129 * Invoking Lisp implementations
130
131 ** Basic use
132
133 A secondary use of ~runlisp~ is in build scripts for Lisp programs.  If
134 the entire project is just a Lisp library, then it's possibly acceptable
135 to just provide an ASDF system definition and expect users to type
136 ~(asdf:load-system "mumble")~ to use it.  If it's a program, or there
137 are things other than Lisp which ASDF can't or shouldn't handle --
138 significant pieces in other languages, or a Lisp executable image to
139 make and install -- then it seems sensible to make the project's main
140 build system be something language-agnostic, say Unix ~make~, and
141 arrange for that to invoke ASDF at the appropriate time.
142
143 But how should that be arranged?  It's relatively easy for a project'
144 Lisp code to support multiple Lisp implementation; but each
145 implementation wants different runes for evaluating Lisp forms from the
146 command line, and some of them don't provide an ideal environment for
147 integrating into a build system.  So ~runlisp~ provides a simple common
148 command-line interface for evaluating Lisp forms.  For example:
149
150 : $ runlisp -e '(format t "~A~%" (+ 1 2))'
151 : 3
152
153 If your build script needs to get information out of Lisp, then wrapping
154 ~format~, or even ~princ~, around forms is annoying; so ~runlisp~ has a
155 ~-p~ option which prints the values of the forms it evaluates.
156
157 : $ runlisp -e '(+ 1 2)'
158 : 3
159
160 If a form produces multiple values, then ~-p~ will print all of them, as
161 if by ~princ~, separated by spaces, on a single line:
162
163 : $ runlisp -p '(floor 5 2)'
164 : 2 1
165
166 There's also a ~-d~ option, which does the same thing as ~-p~, only it
167 prints values as if by ~prin1~.  For example,
168
169 : $ runlisp -p '"Hello, world!"'
170 : Hello, world!
171 : runlisp -d '"Hello, world!"'
172 : "Hello, world!"
173
174 In addition to evaluating forms with ~-e~, and printing their values
175 with ~-d~ and ~-p~, you can also load a file of Lisp code using ~-l~.
176
177 When ~runlisp~ is acting on ~-e~, ~-p~, and/or ~-l~ options, it's said
178 to be running in /eval/ mode, rather than its usual /script/ mode.  In
179 eval mode, it /doesn't/ set ~:runlisp-script~ in ~*features*~.
180
181 You can still insist that ~runlisp~ use a particular Lisp
182 implementation, or one of a subset of implementations, using the ~-L~
183 option mentioned above.
184
185 : $ runlisp -Lsbcl -p "(lisp-implementation-type)"
186 : "SBCL"
187
188 ** Command-line processing
189
190 When scripting a Lisp -- as opposed to running a Lisp script -- it's not
191 necessarily the case that your script knows in advance exactly what it
192 needs to ask Lisp to do.  For example, it might need to tell Lisp to
193 install a program in a particular directory, determined by Autoconf.
194 While it's certainly /possible/ to quote such data and splice them into
195 Lisp forms, it's more convenient to pass them in separately.  So
196 ~runlisp~ ensures that the command-line options are available to Lisp
197 forms via ~uiop:*command-line-arguments*~, as they are to a Lisp script.
198
199 : $ runlisp -p "uiop:*command-line-arguments*" one two three
200 : ("one" "two" "three")
201
202 When running Lisp forms like this, ~(uiop:argv0)~ isn't very
203 meaningful.  (Currently, it reveals the name of the script which
204 ~runlisp~ uses to implement this feature.)
205
206
207 * Configuring =runlisp=
208
209 ** Where =runlisp= looks for configuration
210
211 You can influence which Lisp implementations are chosen by ~runlisp~ by
212 writing configuration files, and/or setting environment variables.
213
214 The ~runlisp~ program looks for configuration in a number of places.
215
216   + There's a system-global directory ~SYSCONFDIR/runlisp/runlisp.d/~.
217     All of the files in this directory named ~SOMETHING.conf~ are read,
218     in increasing lexicographical order by name.  The package comes with
219     a file ~0base.conf~ intended to be read first, so that it can be
220     overridden if necessar.  This sets up basic definitions, and defines
221     the necessary runes for those Lisp implementations which are
222     supported `out of the box'.  New Lisp packages might come with
223     additional files to drop into this directory.
224
225   + There's a system-global file ~SYSCONFDIR/runlisp/runlisp.conf~ which
226     is intended to be edited by the system administrator to account for
227     any local quirks.  This is read /after/ the directory, which is
228     intended to be used by distribution packages, so that the system
229     administrator can override them.
230
231   + Users can create files ~$HOME/.runlisp.conf~ and/or
232     ~$HOME/.config/runlisp.conf~[fn:xdg-config] in their home
233     directories to add support for privately installed Lisp systems, or
234     to override settings made by earlier configuration files.
235
236 The configuration syntax is complicated, and explained in detail in the
237 *runlisp.conf* manpage.
238
239 Configuration options can also be set on the command line, though the
240 effects are subtly different.  Again, see the manual pages for details.
241
242 [fn:xdg-config] More properly, in ~$XDG_CONFIG_HOME/runlisp.conf~, if
243 you set that.
244
245
246 ** Deciding which Lisp implementation to use
247
248 The ~prefer~ option specifies a /preference list/ of Lisp
249 implementations.  The value is a list of Lisp implementation names, as
250 you'd give to ~-L~, separated by commas and/or spaces.  If the
251 environment variable ~RUNLISP_PREFER~ is set, then this overrides any
252 value found in the configuration files.
253
254 When deciding which Lisp implementation to use, ~runlisp~ works as
255 follows.  It builds a list of /acceptable/ Lisp implementations from the
256 ~-L~ command-line option, and a list of /preferred/ Lisp implementations
257 from the ~prefer~ configuration option (or environment variable).  If
258 there aren't any ~-L~ options, then it assumes that /all/ Lisp
259 implementations are acceptable; if no ~prefer~ option is set then it
260 assumes that /no/ Lisp implementations are preferred.  It then works
261 through the preferred list in order: if it finds an implementation which
262 is installed and acceptable, then it uses that one.  If that doesn't
263 work, then it works through the acceptable implementations that it
264 hasn't tried yet, in order, and if it finds one of those that's
265 installed, then it runs that one.  Otherwise it reports an error and
266 gives up.
267
268
269 ** Supporting new Lisp implementations
270
271 ~runlisp~ tries hard to make adding support for a new Lisp as painless
272 as possible.  An awkward Lisp will of course cause trouble, but
273 ~runlisp~ itself is easy.
274
275 As a simple example, let's add support for the 32-bit version of
276 Clozure\nbsp{}CL.  The source code for Clozure\nbsp{}CL easily builds
277 both 32- and 64-bit binaries in either 32- or 64-bit userlands, and one
278 might reasonably want to use the 32-bit CCL for some reason.  The
279 following configuration stanza is sufficient
280
281 : [ccl32]
282 : @PARENTS = ccl
283 : command = ${@ENV:CCL32?ccl32}
284
285   + The first line heads a configuration section, providing the name
286     which will be used for this Lisp implementation, e.g., in ~-L~
287     options or ~prefer~ lists.
288
289   + The second line tells ~runlisp~ that configuration settings not
290     found in this section should be looked up in the ~ccl~ section
291     instead.
292
293   + The third line defines the command to be used to invoke the Lisp
294     system.  It tries to find an environment variable named ~CCL32~,
295     falling back to looking up ~ccl32~ in the path otherwise.
296
297 And, err..., that's it.  The ~@PARENTS~ setting uses the detailed
298 command-line runes for ~ccl~, so they don't need to be written out
299 again.
300
301 That was rather anticlimactic, because all of the work got done
302 somewhere else.  So let's look at a complete example: Steel Bank Common
303 Lisp.  (SBCL's command-line interface is well thought-out, so this is an
304 ideal opportunity to explain how ~runlisp~ configuration works, without
305 getting bogged down in the details of fighting less amenable Lisps.)
306
307 The provided ~0base.conf~ file defines SBCL as follows.
308
309 : [sbcl]
310
311 : command = ${@ENV:SBCL?sbcl}
312 : image-file = ${@NAME}+asdf.core
313
314 : run-script =
315 :         ${command} --noinform
316 :                 $?@IMAGE{--core "${image-path}" --eval "${image-restore}" |
317 :                          --eval "${run-script-prelude}"}
318 :                 --script "${@SCRIPT}"
319
320 : dump-image =
321 :         ${command} --noinform --no-userinit --no-sysinit --disable-debugger
322 :                 --eval "${dump-image-prelude}"
323 :                 --eval "(sb-ext:save-lisp-and-die \"${@IMAGENEW|q}\")"
324
325 Let's take this in slightly larger pieces.
326
327   + We see the ~[sbcl]~ section heading, and the ~command~ setting
328     again.  These should now be unsurprising.
329
330   + There's no ~@PARENTS~ setting, so by default the ~sbcl~ section
331     inherits settings from the ~@COMMON~ section, defined in
332     ~0base.conf~.  We shall use a number of definitions from this
333     section.
334
335   + The ~image-file~ gives the name of the custom image file to look for
336     when trying to start SBCL, but not the directory.  (The directory is
337     named by the ~image-dir~ configuration setting.)  The image file
338     will be named ~sbcl+asdf.core~, but this isn't what's written.
339     Instead, it uses ~${@NAME}~, which is replaced by the name of the
340     section being processed.  When we're running SBCL, this does the
341     same thing; but if someone wants to configure a new ~foo~ Lisp and
342     set ~@PARENTS~ to ~sbcl~, then the image file for ~foo~ will be
343     named ~foo+asdf.core~ by default.  You needn't take such care when
344     configuring Lisp implementations for your own purposes, but it's
345     important for configurations which will be widely used.
346
347   + The ~run-script~ setting explains how to get SBCL to run a script.
348     This string is broken into words at (unquoted) spaces.
349
350     The syntax ~$?VAR{CONSEQ|ALT}~ means: if a configuration setting
351     ~VAR~ is defined, then expand to ~CONSEQ~; otherwise, expand to
352     ~ALT~.  In this case, if the magic setting ~@IMAGE~ is defined, then
353     we add the tokens ~--core "${image-path}" --eval "${image-restore}"~
354     to the SBCL command line; otherwise, we add ~--eval
355     "${run-script-prelude}"~.  The ~@IMAGE~ setting is defined by
356     ~runlisp~ only if (a)\nbsp{}a custom image was found in the correct
357     place, and (b)\nbsp{}use of custom images isn't disabled on its
358     command line.
359
360     The ~${image-path}~ token expands to the full pathname to the custom
361     image file; ~image-restore~ is a predefined Lisp expression to be
362     run when starting from a dumped image (e.g., to get ASDF to refresh
363     its idea of which systems are available).
364
365     The ~run-script-prelude~ is another (somewhat involved) Lisp
366     expression which sets up a Lisp environment suitable for running
367     scripts -- e.g., by arranging to ignore ~#!~ lines, and pushing
368     ~:runlisp-script~ onto ~*features*~.
369
370     Finally, regardless of whether we're using a custom or vanilla
371     image, we add the tokens ~--script "${@SCRIPT}"~ to the command
372     line.  The ~${@SCRIPT}~ token is replaced by the actual script
373     pathname.  ~runlisp~ then appends further arguments from its own
374     command line and runs the command.  (For most Lisps, ~uiop~ needs a
375     ~--~ marker before the user arguments, but not for SBCL.)
376
377   + Finally, ~dump-image~ defines a command line for dumping a custom
378     images.  The ~dump-image-prelude~ setting is a Lisp expression for
379     setting up a Lisp so that it will be in a useful state when dumped:
380     it's very similar to ~run-script-prelude~, and is built out of many
381     of the same pieces.
382
383     The thing we haven't seen before is ~${@IMAGENEW|q}~.  The
384     ~@IMAGENEW~ setting is defined by the ~dump-runlisp-image~ program
385     to name the file in which the new image should be
386     saved.[fn:image-rename]  The ~|q~ `filter' is new: it means that the
387     filename should be escaped suitable for inclusion in a Lisp quoted
388     string, by prefixing each ~\~ or ~"~ with a ~\~.
389
390 That's more or less all there is.  SBCL is a particularly simple
391 example, but mostly because other Lisp implementations require fancier
392 stunts /at the Lisp level/.  The ~runlisp~-level configuration isn't any
393 more complicated than SBCL.
394
395 [fn:image-rename] ~dump-runlisp-image~ wants to avoid clobbering an
396 existing image with a half-finished one, so it tries to arrange for the
397 new image to be written to a different file, and then renames it once
398 it's been created successfully.)
399
400
401 * What's wrong with =cl-launch=?
402
403 The short version is that ~cl-launch~ is slow and inconvenient.
404 ~cl-launch~ is a big, complicated Common Lisp/Bourne shell polyglot
405 which tries to do everything but doesn't quite succeed.
406
407 ** It's slow.
408
409 I took a trivial Lisp script:
410
411 : (format t "Hello from ~A!~%~
412 :            Script = `~A'~%~
413 :            Arguments = (~{`~A'~^, ~})~%"
414 :         (lisp-implementation-type)
415 :         (uiop:argv0)
416 :         uiop:*command-line-arguments*)
417
418 I timed how long it took to run on all of ~runlisp~'s supported Lisp
419 implementations, and compared them to how long ~cl-launch~ took: the
420 results are shown in table [[tab:runlisp-vanilla]].  ~runlisp~ is /at least/
421 two and half times faster at running this script than ~cl-launch~ on all
422 implementations except Clozure\nbsp{}CL[fn:slow-ccl], and approaching
423 four and a half times faster on SBCL.
424
425 #+CAPTION: ~cl-launch~ vs ~runlisp~ (with vanilla images)
426 #+NAME: tab:runlisp-vanilla
427 #+ATTR_LATEX: :float t :placement [tbp]
428 |------------------+-------------------+-----------------+----------------------|
429 | *Implementation* | *~cl-launch~ (s)* | *~runlisp~ (s)* | *~runlisp~ (factor)* |
430 |------------------+-------------------+-----------------+----------------------|
431 | ABCL             |            7.3378 |          2.6474 | 2.772                |
432 | Clozure CL       |            1.2888 |          0.9742 | 1.323                |
433 | GNU CLisp        |            1.2405 |          0.2703 | 4.589                |
434 | CMU CL           |            0.9521 |          0.3097 | 3.074                |
435 | ECL              |            0.8020 |          0.3236 | 2.478                |
436 | SBCL             |            0.3205 |          0.0874 | 3.667                |
437 |------------------+-------------------+-----------------+----------------------|
438 #+TBLFM: $4=$2/$3;%.3f
439
440 But this is using the `vanilla' Lisp images installed with the
441 implementations.  ~runlisp~ by default builds custom images for most
442 Lisp implementations, which improves startup performance significantly;
443 see table [[tab:runlisp-custom]].  (I don't currently know how to build a
444 useful custom image for ABCL.  ~runlisp~ does build a custom image for
445 ECL, but it doesn't help significantly.)  These results are summarized
446 in figure [[fig:lisp-graph]].
447
448 #+CAPTION: ~cl-launch~ vs ~runlisp~ (with custom images)
449 #+NAME: tab:runlisp-custom
450 #+ATTR_LATEX: :float t :placement [tbp]
451 |------------------+-------------------+-----------------+----------------------|
452 | *Implementation* | *~cl-launch~ (s)* | *~runlisp~ (s)* | *~runlisp~ (factor)* |
453 |------------------+-------------------+-----------------+----------------------|
454 | ABCL             |            7.3378 |          2.7023 | 2.715                |
455 | Clozure CL       |            1.2888 |          0.0371 | 34.739               |
456 | GNU CLisp        |            1.2405 |          0.0191 | 64.948               |
457 | CMU CL           |            0.9521 |          0.0060 | 158.683              |
458 | ECL              |            0.8020 |          0.3275 | 2.449                |
459 | SBCL             |            0.3205 |          0.0064 | 50.078               |
460 |------------------+-------------------+-----------------+----------------------|
461 #+TBLFM: $4=$2/$3;%.3f
462
463 #+CAPTION: Comparison of ~runlisp~ and ~cl-launch~ times
464 #+NAME: fig:lisp-graph
465 #+ATTR_LATEX: :float t :placement [tbp]
466 [[file:doc/lisp-graph.tikz]]
467
468 Unlike ~cl-launch~, with some Lisp implementations at least, ~runlisp~
469 startup performance is usefully comparable to other popular scripting
470 language implementations.  I wrote similarly trivial scripts in a number
471 of other languages, and timed them; the results are tabulated in table
472 [[tab:runlisp-interp]] and graphed in figure [[fig:interp-graph]].
473
474 #+CAPTION: ~runlisp~ vs other interpreters
475 #+NAME: tab:runlisp-interp
476 #+ATTR_LATEX: :float t :placement [tbp]
477 |------------------------------+-------------|
478 | *Implementation*             | *Time (ms)* |
479 |------------------------------+-------------|
480 | Clozure CL                   |        37.1 |
481 | GNU CLisp                    |        19.1 |
482 | CMU CL                       |         6.0 |
483 | SBCL                         |         6.4 |
484 |------------------------------+-------------|
485 | Perl                         |         1.1 |
486 | Python                       |         6.8 |
487 |------------------------------+-------------|
488 | Debian Almquist shell (dash) |         1.2 |
489 | GNU Bash                     |         1.5 |
490 | Z Shell                      |         3.1 |
491 |------------------------------+-------------|
492 | Tiny C (compile & run)       |         1.6 |
493 | GCC (precompiled)            |         0.6 |
494 |------------------------------+-------------|
495
496 #+CAPTION: Comparison of ~runlisp~ and other script interpreters
497 #+NAME: fig:interp-graph
498 #+Attr_latex: :float t :placement [tbp]
499 [[file:doc/interp-graph.tikz]]
500
501 (All the timings in this section were performed on the same 2020 Dell
502 XPS13 laptop running Debian `buster'.  The tools used to make the
503 measurements are included in the source distribution, in the ~bench/~
504 subdirectory.)
505
506 [fn:slow-ccl] I don't know why Clozure\nbsp{}CL shows such a small
507 difference here.
508
509 ** It's inconvenient
510
511 ~cl-launch~ has this elaborate machinery which reads shell script
512 fragments from various places and sets variables like ~$LISPS~, but it
513 doesn't quite work.
514
515 Unlike other scripting languages such as Perl or Python, Common Lisp has
516 lots of implementations, and they all have various unique features (and
517 bugs) which a script might rely on (or need to avoid).  Also, a user
518 might have preferences about which Lisps to use.  ~cl-launch~'s approach
519 to this problem is a ~system_preferred_lisps~ shell function which can
520 be used in ~~/.cl-launchrc~ to select a Lisp system for a particular
521 `software system', though this notion doesn't appear to be well-defined,
522 but this all works by editing a single ~$LISPS~ shell variable.  By
523 contrast, ~runlisp~ has a ~-L~ option with which scripts can specify the
524 Lisp systems they support (in a preference order), and a ~prefer~
525 configuration setting with which users can express their own
526 preferences: ~runlisp~ will never choose a Lisp system which the script
527 can't deal with, but it will respect the user's relative preferences.
528
529 Also, ~cl-launch~ is a monolith.  Adding a new Lisp implementation to
530 it, or changing how a particular implementation is invoked, is rather
531 involved.  By contrast, ~runlisp~ makes this remarkably easy, as
532 described in [[Supporting new Lisp implementations]].
533
534 ** It doesn't establish a (useful) common environment
535
536 A number of Lisp systems are annoyingly deficient in their handling of
537 scripts.
538
539 For example, when GNU CLisp's ~-x~ option is used, it rebinds
540 ~*standard-input*~ to an internal string stream holding the expression
541 passed in on the command line, leaving the process's actual stdin nearly
542 impossible to access.
543
544 : $ date | cl-launch -l sbcl -i "(princ (read-line nil nil))" # expected
545 : Sun  9 Aug 14:39:10 BST 2020
546 : $ date | cl-launch -l clisp -i "(princ (read-line nil nil))" # bug!
547 : NIL
548
549 As another example, Armed Bear Common Lisp doesn't seem to believe in
550 the stderr stream: when it starts up, ~*error-ouptut*~ is bound to the
551 standard output, just like ~*standard-output*~.  Also, ~cl-launch~
552 loading ASDF causes a huge number of ~style-warning~ messages to be
553 written to stdout, making ABCL pretty much useless for writing filter
554 scripts.
555
556 : $ cl-launch -l sbcl -i '(progn
557 :                           (format *standard-output* "output~%")
558 :                           (format *error-output* "error~%"))' \
559 :   > >(sed 's/^/stdout: /') 2> >(sed 's/^/stderr: /')
560 : stdout: output
561 : stderr: error
562 : $ cl-launch -l abcl -i '(progn
563 :                           (format *standard-output* "output~%")
564 :                           (format *error-output* "error~%"))' \
565 :   > >(sed 's/^/stdout: /') 2> >(sed 's/^/stderr: /')
566 : [1813 lines of compiler warnings tagged `stdout:']
567 : stdout: output
568 : stdout: error
569
570 ~runlisp~ takes care of all of this, providing a basic but useful common
571 level of shell integration for all its supported Lisp implementations.
572 In particular:
573
574   + It ensures that the standard Unix `stdin', `stdout', and `stderr'
575     file descriptors are hooked up to the Lisp ~*standard-input*~,
576     ~*standard-output*~, and ~*error-output*~ streams.
577
578   + It ensures that starting a script doesn't write a deluge of
579     diagnostic drivel.
580
581 The complete details are given in ~runlisp~'s manpage.
582
583 ** Why might one prefer =cl-launch= anyway?
584
585 On the other hand, ~cl-launch~ is well established and full-featured.
586
587 ~cl-launch~ compiles scripts before trying to run them, so they'll run
588 faster on Lisps which use an interpreter by default.  It has a caching
589 feature so running a script a second time doesn't need to recompile it.
590 If your scripts are compute-intensive and benefit from ahead-of-time
591 compilation then maybe ~cl-launch~ is preferable.
592
593 ~cl-launch~ supports more Lisp systems.  I only have six installed on my
594 development machine at the moment, so those are the ones that ~runlisp~
595 supports.  If you want your scripts to be able to run on other Lisps,
596 then ~cl-launch~ is the way to do that.  Of course, I welcome patches to
597 help ~runlisp~ support other free Lisp implementations.  ~cl-launch~
598 also supports proprietary Lisps: I have very little interest in these,
599 so if you want to run scripts using Allegro or LispWorks then
600 ~cl-launch~ is your only choice.