scheme shell

From: (Olin Shivers)
Subject: replicating standard input -- a Y connector for stdin.
Newsgroups: comp.lang.scheme.scsh
Date: 24 Jun 1996 13:34:25 -0400
Organization: Artificial Intelligence Lab, MIT
Message-ID: <>

    From: (Janet Bell)
    Subject: awk in scsh
    Newsgroups: comp.lang.scheme.scsh
    Date: Sun, 23 Jun 1996 15:38:02 GMT

    How would you construct a pipeline in SCSH that piped output from a single
    program into two other programs.  I want to do this:

    cat foo ------> wc -l
	      |---> grep something --------> wc -l
				     |-----> cut --> sort --> uniq
    etc. etc.  and I don't want to create temp files in between
    because the disk thrashing on this moby file would
    kill the machine.  Can this even be done in Unix? or is this
    yet another example of ``no one would ever want to have
    that functionality, so I won't implement it''.

The code to do this is below. You'll have to drop down from the process
notation to Scheme, but it's pretty straightforward.
Bear in mind that if one of the child processes exits early, Y-CONN
will raise an exception when it tries to write to the dead child. You
could hack this code to catch the writing-to-dead-child error, and
have it thereafter just write the other, live child, returning if
both children die, but that is not obviously what you want in all cases,
so I kept it simple.
Here's an example use:
	(run (begin (y-conn (lambda () (exec-epf (enscript)))
			    (lambda () (exec-epf (spell) (> spell.err))))))

This spell-checks stdin and also queues it for printing.

Too bad I can't hack this into the process notation -- it would be
nice to be able to write

	(run (y-conn-epf ((enscript))
                         ((spell) (> spell.err))))

without all the lambda-thunking. Scsh's process-notation macro machinery is
set in stone. Perhaps I should think about providing an extension mechanism.

Also, one should probably generalise the code below for >2 thunks.
;;; Fork two processes; they both get copies of our std input.

(define (y-conn thunk1 thunk2)
  (receive (from-master1 to-p1) (pipe)		; Fork the children.
    (receive (from-master2 to-p2) (pipe)
      (let ((p1 (fork (lambda ()
			(close to-p1)
			(close to-p2)
			(close from-master2)
			(set-current-input-port! (dup from-master1 0))
			(close from-master1)

	    (p2 (fork (lambda ()
			(close to-p1)
			(close to-p2)
			(close from-master1)
			(set-current-input-port! (dup from-master2 0))
			(close from-master2)

	    (buf (make-string 1024)))

	(close from-master1)			; Clean up unused ports
	(close from-master2)			; in master proc.

	(set-port-buffering to-p1 bufpol/none)	; No buffering -- move those
	(set-port-buffering to-p2 bufpol/none)	; bits along.

	(let lp ()				; Do the copy.
	  (cond ((read-string!/partial buf) =>
		 (lambda (nread)
		   (write-string buf to-p1 0 nread)
		   (write-string buf to-p2 0 nread)

	(close to-p1)				; Tell children there's 
	(close to-p2)				; no more.

	(wait p1)				; Wait for children to
	(wait p2)))))				; finish.