[net.unix] Bourne shell programming question...

dan@rna.UUCP (Dan Ts'o) (05/09/84)

Hi,
	I've just started to use the Bourne shell in non-trivial
command script writing and have run into a number of problems. I am
converting some shell scripts from an older shell (V6-like with variables).
	In particular,
	1) How do you read a single line from /dev/tty (or an arbitrary file,
		NOT stdin) and assign that line to a variable ?
	2) How do you arrange for a single instance of common shell code ?
		The Bourne shell has no procedures and no goto statement.

	For reading a single line into a variable from /dev/tty, I've had to
use the ridiculous:

	a=`dd if=/dev/tty count=1 2>/dev/null`

or

	a=`(read a
		echo $a) < /dev/tty`

	The read command doesn't seem to like its stdin re-directed (I know its
a builtin command). There appears to a possibility of making /dev/tty the stdin
by saying

	exec 0< /dev/tty

	but this throw away the old stdin.

	Even if these two problems have easy solution which I've overlooked,
I'd like to say that the Bourne shell sucks. If people are accurate in saying
the C-shell is worst than the Bourne shell for programming...
	I know the Korn shell has shell procedures and all its builtin commands
are supposed to be I/O redirectable.


						Cheers,
						Dan Ts'o
						...cmcl2!rna!dan

matt@UCLA-LOCUS.ARPA (05/13/84)

From:            "Matthew J. Weinstein" <matt@UCLA-LOCUS.ARPA>

>Date: 8 May 84 16:06:31-PDT (Tue)
>To: info-unix@BRL-VGR.ARPA
>From: hplabs!hao!seismo!cmcl2!rna!dan@ucb-vax.ARPA
>Subject: Bourne shell programming question...
>
>Article-I.D.: rna.250
>
>Hi,
>	I've just started to use the Bourne shell in non-trivial
>command script writing and have run into a number of problems. I am
>converting some shell scripts from an older shell (V6-like with variables).
>	In particular,
>	1) How do you read a single line from /dev/tty (or an arbitrary file,
>		NOT stdin) and assign that line to a variable ?

1) Sh doesn't seem to handle redirection properly for read.  So, use a 
   program that reads one line from stdin and exits.  This could be the
   `head' program in 4.1:

	    READ="head -1"
	    ...
	    a=`$READ </dev/tty`

   The short C program:

	    #include <stdio.h>

	    main()
	    {
		int c;
		while ((c=getchar()) != EOF) {
		    putchar(c);
		    if (c=='\n') break;
		}
	    }

    also approximates a solution.

>	2) How do you arrange for a single instance of common shell code ?
>		The Bourne shell has no procedures and no goto statement.
>

2) Procedures are possible, but each one has to be a shell script (to
   the best of my knowledge).  Tricky scripts combined with eval can
   usually get some interesting results (nothing quite replaces local
   vars).

   Try something like:
   
	var='commands'
	...
	eval "$var"

   You can (sort of) pass params to this with the subterfuge:

	param1="blah.." param2="foo.." eval "$var"

   A short example:

	proc='for i in $arg; do 
	echo arg:$i ;
	done'

	arg="alpha beta gamma" eval $proc

	arg="A B C" eval $proc

   Note that `;'s etc. have to be in the right place here, since the
   multiples lines are scrunched together.  There may also be quoting
   losses.

This is all a bit ad-hoc (and it's late at night too).  Anyone have 
better suggestions (please!)?

						- Matt

merlyn@sequent.UUCP (05/14/84)

> From: dan@rna.UUCP
> Message-ID: <250@rna.UUCP>
> Date: Tue, 8-May-84 16:06:31 PDT
> 
> Hi,
> 	I've just started to use the Bourne shell in non-trivial
> command script writing and have run into a number of problems. I am
> converting some shell scripts from an older shell (V6-like with variables).
> 	In particular,
> 	1) How do you read a single line from /dev/tty (or an arbitrary file,
> 		NOT stdin) and assign that line to a variable ?
> 	2) How do you arrange for a single instance of common shell code ?
> 		The Bourne shell has no procedures and no goto statement.

First, number 2.  Can't, at least not without stashing it in a separate file
and calling it as a subroutine (very icky).  Also, they call up (at least)
one more process.  Time consuming on most Un*xes.

Second, number 1.  I agree that your solutions leave lots to be desired.
Bourne himself in his (not real great) book "The Unix System", gives this
flavor of a solution:

	exec 3<&0 </dev/tty
	read foo
	exec <&3 3<&-

if you can believe that.  For a bit of explanation, this maximal esotericism
does the following:

	(1) the first line copies stdin (whatever that is) to file descriptor
	"3" (the first one after stdin=0, stdout=1, and stderr=2), and
	at the same time, reopens stdin to be /dev/tty.  This is all done
	without starting a new process... exec is one of the few builtins
	that allows I/O redirection.

	(2) the second line does the read into shell variable "foo" from
	/dev/tty.

	(3) the third line copies file descriptor 3 back to stdin, and then
	closes file descriptor 3.

I kinda like this one, since it doesn't use any new processes.  But,
only the author of the Bourne shell (and a very esoteric one at that) would
resort to coming up with such bizarre uses for that little-used feature
of assigning specific file descriptors to specific files.  [Slight editorial
comment.]

Another favorite, although it still needs a separate process is
	foo="`head -1 /dev/tty`"
but you have to be using a Berkeley Un*x to take advantage of that.
(You could write a quicky C program to do the same thing.)

-- A particularly personal and original observation from the thought-stream of
Randal L. ("</dev/null >&0 2>&1") Schwartz, esq. (merlyn@sequent.UUCP)
	(Official Legendary Sorcerer of the 1984 Summer Olympics)
Sequent Computer Systems, Inc. (503)626-5700 (sequent = 1/quosine)
UUCP: {decwrl,ogcvax,pur-ee,rocks34,shell,unisoft,vax135,verdix}!sequent!merlyn

P.S. Unix is a trademark of some divested part of TPC.  (Who owns that now?)

P.P.S.  I have no personal gripes with Bourne.  I just happen to notice that
some of the things that he does are a bit odd.  "Pay no attention to the man
behind the curtain."

ka@hou3c.UUCP (Kenneth Almquist) (05/14/84)

If you have the line(1) program, you can read a line from the
terminal by saying:

	a=`line < /dev/tty`

As you point out, it is also possible to use exec to redirect
the standard input (allowing you to use read).  You can move
stdin to a different file descriptor first if you will need it
later:
	exec 3<&0 </dev/tty	# open /dev/tty as stdin
	read a			# read a line from the terminal
	exec <&3 3<&-		# restore original standard input

Kenneth Almquist

avr@CS-Arthur (Andrew V Royappa) (05/15/84)

x <-- 'x'

	to read a line off a file:
-----
	a=`head -1 $file`
-----
If $file is null, stdin will be read. For shared code, you could do
-----
	CODE="lines of code separated by ; "

	eval $CODE		# use code
-----
If you do complicated things with quotes, you'll probably have to
backslash a lot (backslash ? backstab ?).

Another way to do this is to put all commands in a file (in 
/tmp/code.$$ or such), do eval `cat <file>`, and remove the
file later.

				Andrew Royappa
				{ucbvax,decvax,ihnp4,pur-ee}!purdue!avr

yep, I should use 'r', but something weird happened when I did.