CLISP-Shell
A description of how to make CLISP work as a Unix shell. The official location of this text is here. The following was posted to comp.lang.lisp by Peter Wood:


The following works for me:

*DISCLAIMER:  Use at own risk.  Exercise care. Make backups of anything
you change.*

On linux, you need to have built your clisp with the
"--with-modules=bindings/linuxlibc6" option, and you should *not* have
compiled it without readline. See the build instructions and Makefile.

STEP 1

Put /usr/bin/clisp in /etc/shells so it looks (something like) this:

# /etc/shells: valid login shells
#/bin/ash
/bin/bash
/bin/sh
/usr/bin/clisp 

STEP 2

You can set up your $PATH In /etc/login.defs by modifying the entry
for ENV_PATH to look (something) like this:

ENV_PATH        PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin

This is so you can run X.

[Once you have your clisp shell set up, you can control the
environment variables (on Linux) via the linuxlibc6 bindings (see
$clisp-src/modules/bindings/linuxlibc6/linux.lisp): setenv, putenv,
getenv, etc]

STEP 3

The "startx" command is a shell script, so we can't use it.  As a
temporary fix, I have defunned thus:

(defun startx ()
  (execute "/usr/X11R6/bin/xinit"))

Which works for me.  Although I haven't investigated all the
consequences of not going via shell's startx.  Also I *don't* use
GNOME or KDE which may complicate things for you if you do.  I use
fvwm2 and have this in my .xinitrc:

exec fvwm2

STEP 4 (some guru will probably shoot this down in flames)

For conveniently running external programs (ie, I don't want to have
to type (run-program "ls" :arguments '("-lh")) every time, I have set
up a read macro.  Put the following in somefile.lisp.

(set-macro-character #\] (get-macro-character #\)))

(set-dispatch-macro-character #\# #\[
  #'(lambda (stream char1 char2)
      (setf (readtable-case *readtable*) :preserve)
      (UNWIND-PROTECT
       (LET* ((OUTPUT-STREAM (MAKE-STRING-OUTPUT-STREAM 
                                     :ELEMENT-TYPE 'BASE-CHAR))
	      (STEP1 (FORMAT OUTPUT-STREAM "(RUN-PROGRAM "))
	      (COMMAND-LINE (READ-DELIMITED-LIST #\] STREAM T))
	      (COMMAND (FIRST COMMAND-LINE))
	      (STEP2 (FORMAT OUTPUT-STREAM "\"~A\" 
                                     :ARGUMENTS " COMMAND))
	      (PARAMETERS (REST COMMAND-LINE))
	      (STEP3 (FORMAT OUTPUT-STREAM "'(")))
	     (DOLIST (X PARAMETERS
			(PROGN (FORMAT OUTPUT-STREAM "))")
			       (LET ((CLEAN 
                                      (GET-OUTPUT-STREAM-STRING 
                                               OUTPUT-STREAM)))
				    (CLOSE OUTPUT-STREAM)
				    (VALUES 
                                     (READ-FROM-STRING CLEAN)))))
		     (FORMAT OUTPUT-STREAM "\"~A\" " X)))
      (SETF (READTABLE-CASE *READTABLE*) :UPCASE))))

You will get (harmless) warnings from the compiler about unused
variables STEP*, if you compile this file.  You must keep the upper
case stuff as is.  (It could also be all upcase).

STEP 5

It's also a PITA to have to type "#[" and "]" everytime you want to
run a command, so add the following in your $HOME/.inputrc

#for clash: prints the square brackets to run an external command
"\ec": "#[]\C-b"

When you type ESC-c (or META-c) readline will print "#[]" to the
console and put the cursor inside the brackets.  If you are running
clisp as your shell, and do this, you will then be able to run
programs (more or less) normally.  You will need to escape the dot
in any filenames with a dot in them: #[cat  \.xinitrc]

STEP 6

Now try it out-- *Don't* modify your /etc/passwd yet! --

Start up a clisp with "clisp -k full", and load somefile.lisp (the
read-macro) into a CLISP which has readline compiled in.

Hit "ESC c", type "ls -l", and see if it works.

You *don't* have command line completion for external programs inside
the read-macro.  So hitting tab will just give you a list of all
clisp's possibilties, which is not much use here.  However,
"run-program" does search your path for executables so you don't have
to do #[/bin/ls -l]

Also, I haven't (yet) written lisp versions of "cd" and the other bash
builtins. But this is easy with the libc6 bindings, (chdir etc).
Another convenience thing you can do is stuff like this for often used
commands: 

(defmacro ls-lh ()
  `#[ls -lh])

Now (ls-lh) reads as (run-program "ls" :arguments '("-lh"))

STEP 7

If you are happy, and think you can live with this setup then you must
do the following:

-Dump a memory image from a clisp with the read macro and any
 convenience stuff loaded:

(saveinitmem "lispinit.mem" :quiet t)

The :quiet t suppresses clisp's start up message, which I find
annoying.  If you have some function you want to run every time clisp
starts, you can also specify this (see impnotes.html).

-Make a backup of /usr/[local]/lib/clisp.  (wherever your's is
 installed)
-Rename the "base" directory in /usr/lib/clisp to "orig-base".
-Now rename the "full" directory to "base".  
-Replace the memory image in your *new* "base" directory 
 with the one you dumped.

STEP 8

Change your /etc/passwd to reflect your new shell.
Light 13 candles in a circle round your Linux box, sacrifice a white
rooster, invoke Saint IGNUcious and ...

Login again.

If you start pining for Bash, you can run:
#[bash]

And explicitly source all your configuration files (.profile etc)

--------------------------------------------------------------------
It works for me.  Now I just need to work out how to: 

get indentation on the command line.
do command redirection.
get name completion for arguments which are filenames.

Piece of cake!  (But don't hold your breath)

I've changed the macro a little so you don't have to type built-ins like cd a different way. I'll work on making this portable and nice next. I'm only a newbie so if this is ugly please tell me. Still don't have the other stuff to make this a nice swap for bash.

(defun command-line-command-equal (command-line command)
(if (string-equal command (car command-line)) (list command (string-downcase (string (second  command-line)))) nil))

(set-macro-character #\] (get-macro-character #\)))
(set-dispatch-macro-character #\# #\[
  (lambda (stream char1 char2)
    (declare (ignore char1 char2))
    (setf (readtable-case *readtable*) :preserve)
    (unwind-protect
         (let ((command-line (read-delimited-list #\] stream t)))
           (cond ((command-line-command-equal command-line 'cd))	   
	   	(t (list 'ext:run-program (princ-to-string (car command-line))
                 :arguments `',(mapcar #'princ-to-string (rest command-line))))))
      (setf (readtable-case *readtable*) :upcase))))

Sure you know, but to source configuration files just use: #[bash -l]