Writing Scripts with Common Lisp

lisp alienScripting languages such as Python and Ruby naturally lend themselves to the task of writing command line utilities. Lisp is much older than these languages, so it’s not surprising that some Lisp implementations are more difficult than others for scripting. Some lisps have a library too small to be useful. Some lisps cannot access files. But most of all, many lisps have trouble with shebangs and scripted main.

Shebangs

Add the following to ~/.cmucl-init.lisp (or your initialization file):

;;; Play nice with shebangs
(set-dispatch-macro-character #\# #\!
 (lambda (stream character n)
  (declare (ignore character n))
  (read-line stream nil nil t)
  nil))

This instructs CL to ignore all lines beginning with #!, even in the REPL.

Scripted Main

Java, Python, Perl, Ruby, Lua, Haskell, and newLISP all have scripted main.

One of the most useful tools in a scripting language is a scripted main. I invented this term out of necessity: it’s hard to Google something without a name. Scripted main refers to a special function, typically called main, which is executed when a script is run from the command line (e.g. python myscript.py or ./myscript.py). If the script is imported by another script, main() is not called. Scripted main is extremely useful for writing programs that do this:

$ ./welcome.py
Usage: ./welcome.py <name>
$ ./welcome.py Brandon
Welcome Brandon!

If another script greeter.py imports code from welcome.py, we don’t want greeter to call welcome‘s main function. The code in welcome that prints out Usage: ./welcome.py <name> probably calls sys.exit(0) soon after, which would kill greeter immediately after importing welcome.

How to do it

The next few examples are specific to CLISP. If you use SBCL, Clojure, or another implementation, you’ll need to modify the shebang lines accordingly.

Save as hello.cl:

#!/bin/bash
#|
exec clisp -q -q $0 $0 ${1+"$@"}
exit
|#
(defun hello-main ()
  (format t "Hello Main!~%")
  (quit))

(hello-main)

Run:

$ chmod +x hello.cl
$ ./hello.cl
Hello Main!

The chmod command marks hello.cl as a self-running executable so that ./hello.cl is allowed.

What if another script greeter.cl loads the code from hello? Let’s write a greeter that lets us choose which greeting to print.

Save as greeter.cl:

#!/bin/bash
#|
exec clisp -q -q $0 $0 ${1+"$@"}
exit
|#

(load "hello")

(defun greeter-main ()
  (format t "Ready to greet.~%")
  (quit))

(greeter-main)

Run:

$ chmod +x greeter.cl
$ ./greeter.cl
Hello Main!
Ready to greet.

The problem with this logic is that hello.cl‘s main function will be called as soon as the file is loaded. We don’t want this to happen, so we will use some special CMUCL code:

Resave as hello.cl:

#!/bin/bash
#|
exec clisp -q -q $0 $0 ${1+"$@"}
exit
|#

(defun hello-main (args)
  (format t "Hello from main!~%"))

;;; With help from Francois-Rene Rideau
;;; http://tinyurl.com/cli-args
(let ((args
       #+clisp ext:*args*
       #+sbcl sb-ext:*posix-argv*
       #+clozure (ccl::command-line-arguments)
       #+gcl si:*command-args*
       #+ecl (loop for i from 0 below (si:argc) collect (si:argv i))
       #+cmu extensions:*command-line-strings*
       #+allegro (sys:command-line-arguments)
       #+lispworks sys:*line-arguments-list*
     ))

  (if (member (pathname-name *load-truename*)
              args
              :test #'(lambda (x y) (search x y :test #'equalp)))
    (hello-main args)))

Resave as greeter.cl:

#!/bin/bash
#|
exec clisp -q -q $0 $0 ${1+"$@"}
exit
|#

(load "hello")

(defun greeter-main (args)
  (format t "Ready to greet.~%")
  (quit))

;;; With help from Francois-Rene Rideau
;;; http://tinyurl.com/cli-args
(let ((args
       #+clisp ext:*args*
       #+sbcl sb-ext:*posix-argv*
       #+clozure (ccl::command-line-arguments)
       #+gcl si:*command-args*
       #+ecl (loop for i from 0 below (si:argc) collect (si:argv i))
       #+cmu extensions:*command-line-strings*
       #+allegro (sys:command-line-arguments)
       #+lispworks sys:*line-arguments-list*
     ))

  (if (member (pathname-name *load-truename*)
              args
              :test #'(lambda (x y) (search x y :test #'equalp)))
    (greeter-main args)))

Now the mains function are only called when hello.cl orgreeter.cl are run directly, not when it is loaded.

Run:

$ ./hello.cl
Hello Main!
$ ./greeter.cl
Ready to greet.

This is the desired behavior. For more examples, check out problem.cl and sigil-clean.cl.

About these ads

3 responses to “Writing Scripts with Common Lisp

  1. Pingback: Links 28/11/2010: Pwnage Radio Launched, Fedora 3-14 Benchmarks | Techrights

  2. This is pretty cool, but also kind of depressing at the same time. I use a similar hack for my favorite language, Dylan.

    I just started my research into the state of doing scripting in Lisp. Do you have any suggestions for the best tools to look at? Scheme would be fine too.

    -Carl

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s