[comp.lang.scheme] Scheme shellscripts

dorai@titan.rice.edu (Dorai Sitaram) (07/16/88)

Hi! Thought this might be of interest.

If you've tried using shellscripts to build your own Un*x commands, felt the
shell language a bit too hairy, and would rather use Scheme instead, here's a
short fix. This assumes that the available Scheme interpiler can be called
at the Un*x prompt with a file-name which is automatically loaded before 
producing the interactive loop. E.g., Chez Scheme (copyright R. Kent Dybvig).

A shellscript written in Scheme is like any other Scheme file, except that its
first line has to be:
			runschemescript "$*" '
and its last line:
			'

The quotes are used to delimit the Scheme code which forms the body of the
script. The script consists of a call of the Un*x command `runschemescript' 
with two arguments, the first being the list of arguments to the Scheme 
script, and the second the text of the Scheme code in the script. Thus, a 
Scheme shellscript is a *true* schellscript, i.e., it is callable at the Un*x 
command-line, though the main part of it is a piece of text which is Scheme.

The command `runschemescript' used above is a shellscript written in the usual
shell language. It is the following relatively simple piece of cshell:

**************************************************************************
		echo > .tmpfile '(set! $* (quote (' $1 ')))'
		echo >>.tmpfile $2
		echo >>.tmpfile '(exit)'
		scheme .tmpfile
**************************************************************************

In effect, `runschemescript' creates a temp Scheme file which sets a global 
variable $* to the list of arguments supplied to the Scheme script, runs the 
script which uses $* to refer to its arguments and exits. This file is then 
run with the Scheme interpiler. 

An example Scheme shellscript is the Scheme compiler `sc' which can be invoked
at the Un*x command-line, in makefiles, etc [like `cc']. My `sc' looks like

*******************************************************************************
runschemescript "$*" '

(case (length $*)
  [1 (compile-file (symbol->string (car $*)))]
  [2 (compile-file (symbol->string (car $*)) (symbol->string (cadr $*)))]
  [3 (let ([$1 (car $*)] [$2 (cadr $*)] [$3 (caddr $*)])
       (cond [(eq? $1 `-o)
	      (compile-file (symbol->string $3) (symbol->string $2))]
	     [(eq? $2 `-o)
	      (compile-file (symbol->string $1) (symbol->string $3))]
	     [else (printf "Bad args to sc: ~a~n" $*)]))]
  [else (printf "Bad args to sc: ~a~n" $*)])

'
******************************************************************************

$* being a simple Scheme list of symbols, any processing of arguments or
options is straightforward. Some nits are: These Scheme shellscripts 
always have to be enclosed in runschemescript "$*" ' <body> '. The quote-
character ' {a common enough character in Scheme!} cannot be used in the 
script's body as it interferes with the shell's delimiter characters--instead 
one should use the backquote ` or "quote". Typical shellscripts use other
Un*x commands in their bodies: in Ch*z Scheme, there is a function "system" 
which enables us to call Un*x commands; but this may not be available
in other Schemes. Even in this case, calling a Scheme shellscript from 
within a Scheme shellscript will have multiple invocations of the Scheme
interpreter running simultaneously, which doesn't help a whole lot in
maintaining efficiency. Strangely, one doesn't have to bother about the
same file .tmpfile being used for such nesting--once the file is loaded,
it doesn't matter if it is used for something else.

On the whole, I've found that in a Un*x environment which supports Ch*z 
Scheme, the above technique beats writing shellscripts in cshell for sheer 
ease and maneuvrability. One can then, if one chooses, convert a fully 
debugged Scheme script to a cshell script (or a C program) to recover 
efficiency.

If anyone can suggest further improvement I'll be glad to hear of it.

--dorai

james@ZERMATT.LCS.MIT.EDU (James William O'Toole Jr.) (07/17/88)

    Date: 16 Jul 88 08:01:32 GMT
    From: titan!dorai@rice.edu  (Dorai Sitaram)

    A shellscript written in Scheme is like any other Scheme file, except that its
    first line has to be:
			    runschemescript "$*" '
    and its last line:
			    '

    The quotes are used to delimit the Scheme code which forms the body of the
    script. The script consists of a call of the Un*x command `runschemescript' 
    with two arguments, the first being the list of arguments to the Scheme 
    script, and the second the text of the Scheme code in the script. Thus, a 
    Scheme shellscript is a *true* schellscript, i.e., it is callable at the Un*x 
    command-line, though the main part of it is a piece of text which is Scheme.

    The command `runschemescript' used above is a shellscript written in the usual
    shell language. It is the following relatively simple piece of cshell:

    ...

    If anyone can suggest further improvement I'll be glad to hear of it.

Your method, when executed, requires starting one /bin/csh to interpret
the shellscript, another /bin/csh to interpret the ``runschemescript''
shellscript, copying the scheme code into a temporary file, and invoking
the scheme implementation on that file.

I would suggest that you instead place your scheme code in a file whose
first line is ``#!/bin/scheme arg''.  If your file is called foo, and
its first line is as given above, then when you execute foo, your Unix
will execute the command ``/bin/scheme arg foo''.  You may omit arg, or
choose arg to be something which speeds up /bin/scheme, or tells it to
load the file whose name follows, or whatever.  You will have to
convince your scheme to ignore the first line of foo, of course.  This
method avoids starting up extra shells and copying files.

  --Jim

dorai@titan.rice.edu (Dorai Sitaram) (07/21/88)

James William O'Toole Jr. <james@ZERMATT.LCS.MIT.EDU> writes:

>    Date: 16 Jul 88 08:01:32 GMT
>    From: titan!dorai@rice.edu  (Dorai Sitaram)
>
>    A shellscript written in Scheme is like any other Scheme file, except that its
>    first line has to be:
>			    runschemescript "$*" '
>    and its last line:
>			    '
>
>    The quotes are used to delimit the Scheme code which forms the body of the
>    script. The script consists of a call of the Un*x command `runschemescript' 
>    with two arguments, the first being the list of arguments to the Scheme 
>    script, and the second the text of the Scheme code in the script. Thus, a 
>    Scheme shellscript is a *true* schellscript, i.e., it is callable at the Un*x 
>    command-line, though the main part of it is a piece of text which is Scheme.
>
>    The command `runschemescript' used above is a shellscript written in the usual
>    shell language. It is the following relatively simple piece of cshell:
>
>    ...
>
>    If anyone can suggest further improvement I'll be glad to hear of it.
>
>Your method, when executed, requires starting one /bin/csh to interpret
>the shellscript, another /bin/csh to interpret the ``runschemescript''
>shellscript, copying the scheme code into a temporary file, and invoking
>the scheme implementation on that file.
>
>I would suggest that you instead place your scheme code in a file whose
>first line is ``#!/bin/scheme arg''.  If your file is called foo, and
>its first line is as given above, then when you execute foo, your Unix
>will execute the command ``/bin/scheme arg foo''.  You may omit arg, or
>choose arg to be something which speeds up /bin/scheme, or tells it to
>load the file whose name follows, or whatever.  You will have to
>convince your scheme to ignore the first line of foo, of course.  This
>method avoids starting up extra shells and copying files.
>
>  --Jim

A Refresher on #!
-----------------
If `#! A A1 ...' is the first line of a shell script file whose name is B, 
calling B with args B1 ... has the same effect as calling A with args 
A1 ... B B1 ...

In the above, A has to be a full pathname of a *standard* Un*x command (like 
cat, scheme, etc), i.e., it cannot be a user-fashioned command or shell script
(I found this out thru experimentation. Why is this?).

!# no rehserfeR A
-----------------
Jim [and J A Biep Durieux (private communication)] suggest that 
#! /usr/local/scheme be the first line of a Scheme script: this has the 
unfortunate effect that all arguments to a Scheme script will be loaded as 
Scheme files [even though they are in general, just like any shell script 
arguments, *not* Scheme files]. The script file itself is loaded into Scheme, 
which is ok, provided that its first (non-Scheme) line can somehow be removed.

However, Jim and Biep are right in stating that #! can lead to a concise
and efficient implementation of Scheme scripts. Let the first line of a 
Scheme script be the line

		#! /bin/sh runschemescript

runschemescript is a Bourne script which looks like:
********************************************
foo=$1
shift
echo > .tmp '(set! $* (quote ('$*')))'
echo -n >> .tmp ';'
cat $foo >> .tmp
echo >> .tmp '(exit)'
scheme .tmp	#this line could be 'exec scheme .tmp' but I don't know if 
		#that saves anything
********************************************

A Scheme script 'ss' looks like:
********************************************
#! /bin/sh runschemescript
<a body of Scheme code which
 refers to the list of arguments
 of `ss' by the variable $*>
********************************************
This Scheme code doesn't have the restriction of not being able to use quotes
in it. It also looks less ugly with the user not having to mention $* and use
Bourne quotes in his Scheme scripts. 

ss, when called with arguments a1 ..., gets converted (by the #!) to the call

	/bin/sh runschemescript ss a1 ...

which is the same as (with one subshell invocation)

	runschemescript ss a1 ...

runschemescript then creates a .tmp file which sets a Scheme global variable
$* to a list (a1 ...) of ss's arguments; followed by the contents of the file
ss; followed by an (exit). [A judicious ';' is inserted at the right spot to 
comment out the only non-Scheme portion of the file ss: the first line (with 
#! ...).] ss can therefore be used as a regular shell script which is written 
in Scheme rather than in Bourne.

I would like the first line of Scheme script to be just

		#! runschemescript

instead of

		#! /bin/sh runschemescript

but as said earlier, only standard Un*x commands seem to be accepted by #!.
Improvements are welcome.

--dorai

mwm@eris.berkeley.edu (Mike (I'm in love with my car) Meyer) (07/27/88)

In article <various@many.sites> many people write shell scripts with
lines like:

	echo > .tmp '(set! $* (quote ('$*')))'

in them. This has one nasty problem - try doing "cd /; <scheme-program>".
You'll find (well, you ought to find) that it dies - you can't create
the scratch file you want. Of course, if two people are in the
directory you're running in and running that command, you'll have
probles too.

Instead, try doing:

	echo > /tmp/runscheme.$$ '(set! $* (quote ('$*')))'

This creates a file in /tmp with the process id of the shell executing
the script in it's name. That solves both problems at once.

	<mike

--
My feet are set for dancing,				Mike Meyer
Won't you turn your music on.				mwm@berkeley.edu
My heart is like a loaded gun,				ucbvax!mwm
Won't you let the water run.				mwm@ucbjade.BITNET

ok@quintus.uucp (Richard A. O'Keefe) (07/28/88)

In article <12601@agate.BERKELEY.EDU> mwm@eris.berkeley.edu (Mike Meyer) writes:
...
>Instead, try doing:
>	echo > /tmp/runscheme.$$ '(set! $* (quote ('$*')))'
>This creates a file in /tmp ...

Um, /tmp is for system programs.  /usr/tmp is the place for user temporary
files.  A number of system V utilities support the convention of trying to
create files in $TMPDIR.  It might be better, then, to do
	scheme_temp=${TMPDIR:-/usr/tmp}/runscheme.$$
	echo '(set! $* (quote ('$*')))' >$scheme_temp
You'll want to keep track of the file name so that you can delete it.