Archive for the 'Lisp' Category

Using SBCL for Common Lisp shell scripting

Sunday, October 5th, 2008

I have previously developed some Commom Lisp shell scripts with Emacs/Slime/SBCL and used Clisp for running the scripts. But after running into a compatibility problem between SBCL and Clisp while developing a script for maintaing an automatic mirror of my music collection where flac files are converted to much smaller ogg files, I decided I might as well do what has to be done for using SBCL directly for running the script.

Prepping SBCL

As was mentioned in a comment in my previous post about using Common Lisp for shell scripting, the SBCL manual outlines a piece of code that must be added to an initialization file (I have added it to my $HOME/.sbclrc file).

After adding that to .sbclrc, the next step is to add a shebang line to my script

#!/usr/bin/sbcl --noinform

Adapting for development

The problem now is that the code that is needed for running the script will also be executed while compiling the file inside Slime. I found that this can be fixed by inspecting the *posix-argv* variable. This is a list, that inside Slime has one entry (“/usr/bin/sbcl”), and when the script is executed from the command line has two entries (path to the SBCL interpreter and path to your script in addition to possible command line arguments to the scripts). So if *posix-argv* is longer than 1, we can execute the script. One caveat here, if you are inside a package, *posix-argv* is not directly available. We must use sb-ext:*posix-argv* instead. This leads us to the following file structure:

#!/usr/bin/sbcl --noinform

;; Lisp code (defuns, defclasses, whatever)

;; If run from command line, the following if-test will succeed
(if (> (length sb-ext:*posix-argv*) 1)
;; Code that is executed from the command line only goes here

Now, the script can be developed as usual in Slime and be executed from the command line without changing anything. This also means that the command line arguments to the script is found from the third position and onwards in ext:*posix-argv*.

Script details

The complete script.

This loops through my music directory and creates a parallell directory structure where flac files are converted to ogg files and other file types are just hard linked to the original file. Soft links does not work, because when you try to copy the files to your portable music player, you get a soft link/permission error instead of getting your actual music file.

The flac to ogg conversion is done with sox, so you need to install sox in addition to sbcl and cl-asdf to run this script.

Script usage

./converter.lisp basedir targetdir quality
./converter.lisp /media/sda4/musikk/ /media/sda4/ogg-musikk/ 5
Note: You need the ending / in the directory paths, it does not work otherwise.

Portability hints for Clisp

The main problem I ran into with Clisp is different behaviour in the directory function. This can probably be fixed by using the complete pathname library from PCL chapter 15. The shebang line must be changed to f.ex.
#!/usr/bin/env clisp
and command line arguments are available in ext:*args*

More hints for porting shell scripts to other CL implementations are available in the Common Lisp Cookbook.

Learning Common Lisp by using it for shell scripting

Sunday, May 13th, 2007

For quite a while now, Lisp, or more specifically, Common Lisp has been on my list of languages to learn.
Lack of time and suitable projects for learning has put it off for a quite a while. And, while Lisp can be a neat
language for almost everything, a significant effort is required for getting up to sufficient speed for programming a typical web application.

Getting the idea

A post I found on comp.lang.lisp
raised the subject of
using Common Lisp for shell scripting
. So, I thought that shell scripting would be perfect for learning Common Lisp. Small programs that are done quickly and are usable are a lot more motivating
than dabbling with a small part of a normal application and not getting anywhere near a finished project.

So far, it’s been pretty rewarding. I have written a small backup utility (script source) that copies a file and adds a timestamp to the filename and a script for reminding me every time one of my domain names
closes in on its expiration date (script source). Variable binding with let and multiple-value-bind, the
format directive and date handling are the most important lessons I have learned from these scripts.

Shell scripting quirks

Compared to a normal Common Lisp program, there are two things to notice. First, the shebang line
that is required for use as a shell script (#!/usr/bin/env clisp). It causes a syntax error in Lisp so I
comment it out for development. Second, the whole script is fired with a main function that takes no
arguments. The main call is also commented out for development. I have basically followed the model
from Lars Rune Nøstdal’s example script from the mentioned CLL thread
and I think the idea behind this approach is that all functions can be developed and tested
independently. When going from development to executable shell script, all that is needed is to uncoment the shebang line and the call to the main function.

You can probably use any Common Lisp implementation for this. Clisp is quite small so it works well for shell scripting and is what I have used for my scripts.

Script usage

Usage of the backup script (after making the script executable with chmod +x clbackup.lisp):

harald@semmentjern:~/prog/lisp$ mkdir test
harald@semmentjern:~/prog/lisp$ touch test/somefile.txt
harald@semmentjern:~/prog/lisp$ ls test
harald@semmentjern:~/prog/lisp$ ./clbackup.lisp test/somefile.txt 
Copying file test/somefile.txt to test/somefile.txt.20070513154857
harald@semmentjern:~/prog/lisp$ ls test
somefile.txt  somefile.txt.20070513154857

Usage of the domain alert script (with a 150 days limit):

harald@semmentjern:~/prog/lisp$ ./domainalert.lisp 150 expires in 144 days expires in 150 days