rc-list - mailing list for the rc(1) shell
 help / color / mirror / Atom feed
* Re: read
@ 1993-05-18  2:13 Alan Watson
  0 siblings, 0 replies; 5+ messages in thread
From: Alan Watson @ 1993-05-18  2:13 UTC (permalink / raw)
  To: rc

Gosh, this stuff is tricky, isn't it?  Here are both external and
built-in implementations of read, following v7 semantics on EOF
(i.e., set the variable to '' and return 1), and a corrected trip.read.
None of the other reads in the list or the examples get this right
(including my previous offerings).

Of course, all of this will be made redundant by bqstatus.

I take it by the silence that no one objects to a built-in read by
default ("yeah, right").

Alan.

; cat addon/read.c
/* 
 b_reads -- read a single line, terminated by \n or EOF, from the standard
 input, and assign the line without the terminator to av[1].
 */

static int
readchar (int fd)
{
   unsigned char c;

   if (read (fd, &c, 1) == 1)
      return c;
   else
      return EOF;
}

void
b_read (char **av)
{
   char *name;
   int c;
   char *line;
   SIZE_T len;
   SIZE_T max_len;
   SIZE_T max_len_quantum;

   /* check usage is "read name" */
   if (av[1] == NULL)
      rc_error ("missing variable name");
   if (av[2] != NULL)
      rc_error ("too many arguments to read");
   name = av[1];

   /* read a single line from stdin */
   line = NULL;
   len = 0;
   max_len = 0;
   max_len_quantum = 256;
   do {
      c = readchar (0);
      if (len == max_len) 
	 line = erealloc (line, max_len += max_len_quantum);
      if (c == '\n' || c == EOF)
	 line[len] = 0;
      else
	 line[len] = c;
      len++;
   } while (c != '\n' && c != EOF);

   /* assign whatever we read to the variable */
   assign (word (name, NULL), word (line, NULL), FALSE);
   efree (line);

   /* return TRUE if we terminated with a \n, otherwise FALSE */
   set (c == '\n');

   return;
}
; cat read.fn
fn read {
	switch ( $#* ) {
	case 0
		echo >[1=2] missing variable
		return 1
	case 1
	case *
		echo >[1=2] too many arguments to read
		return 1
	}
	lines = () {
		ifs = $nl { lines = `{ line ; echo $status } }
		if ( ~ $#lines 1 ) {
			$1 = ''
			return $lines
		} else {
			$1 = $lines(1)
			return $lines(2)
		}
	}
}
; cat trip.read
# trip.read -- check a read implementation performs like a true v7 sh read

fn check {
   echo $1 $2
   ./rc -c $2
   if ( ! ~ $status $1 ) {
      echo failed: $2
   }
}

check 0 true
check 1 false

nl = '
'
ifs = $nl

file = /etc/passwd
first = `{ cat $file }
first = $first(1)

echo 'start of trip.read'

# check read X and read '*' return 0
check 0 'exec <$file ; read X'
check 0 'exec <$file ; read ''*'''

# check read, read 1, read '=', and read '' return 1
check 1 'exec <$file ; read'
check 1 'exec <$file ; read 1'
check 1 'exec <$file ; read ''='''
check 1 'exec <$file ; read '''''

# check read X correctly reads first line
check 0 'exec <$file; read X ; ~ $X $first'
check 0 'echo -n $first | { read X ; ~ $X $first }'

# check read '*' correctly reads first line, preserves $0
check 0 'exec <$file ; old0 = $0 ; read ''*'' ; ~ $0 $old0 && ~ $1 $first && ~ $#* 1'

# check read returns 1 on EOF and I/O error
check 1 'exec </dev/null ; read X'
check 1 'exec <[0=]      ; read X'

# check read sets variable to '' when line is empty and on EOF and I/O error.
check 0 '{ X = X ; echo | { read X ; ~ $X '''' } }'
check 0 '{ exec </dev/null ; X = X ; read X ; ~ $X '''' }'
check 0 '{ exec <[0=]      ; X = X ; read X ; ~ $X '''' }'

echo 'end of trip.read'

# end-of-file


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: read
@ 1993-05-16  7:51 Alan Watson
  0 siblings, 0 replies; 5+ messages in thread
From: Alan Watson @ 1993-05-16  7:51 UTC (permalink / raw)
  To: Chris Siebenmann; +Cc: rc

Yes, I know.  I don't actually think it is that clumsy.

Since awk appeared in read in the examples file, I assumed awk performed
unbuffered reads (I was rather surprised, I must admit); direct
experiment reveals this not to be the case.  I suggest that the read in
the EXAMPLES file be replaced with

fn read {
	if ( ! ~ $#* 1 ) {
		echo >[1=2] Usage: read variable
		return 1
	} 
	var = $1 {
		ifs = $nl { * = `{ line ; echo $status } }
		$var = $1
		return $2
	}
}


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: read
  1993-05-16  6:49 read Alan Watson
@ 1993-05-16  7:12 ` Chris Siebenmann
  0 siblings, 0 replies; 5+ messages in thread
From: Chris Siebenmann @ 1993-05-16  7:12 UTC (permalink / raw)
  To: rc

 You can't do a read that works unless you have a program that reads
input without any buffering (note that anything using stdio doesn't
qualify; stdio will happily suck in a buffer's worth when stdin isn't
a terminal).

 If you assume line, you can do a clumsy read; $bgstatus makes it much
cleaner (thank you, Byron!).

	- cks


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: read
@ 1993-05-16  6:49 Alan Watson
  1993-05-16  7:12 ` read Chris Siebenmann
  0 siblings, 1 reply; 5+ messages in thread
From: Alan Watson @ 1993-05-16  6:49 UTC (permalink / raw)
  To: rc

I have just realized that my implementation of read returns the
wrong status if you type:

   ; read x
   foo<ctrl-d>

More seriously, and strengthening my suggestion that read should be a
builtin, I cannot find a way to get `awk' to simulate `line'.  In
response to the above input, line would print "foo\n" and set status to
1.  The obvious awk replacement, i.e.,

   ; fn line { awk '{print $0; exit 0}END{print $0; exit 1}' }

does not work in this way, rather I have to press <ctrl-d> twice, and
then it prints "foo\n0\nfoo\n1\n".

Your mission, should you choose to accept it, is to come up with a
portable (i.e., you can't use line) read which implements v7 semantics
on EOF (i.e., if anything was typed before EOF then set the variable to
this, otherwise unset it, and return 1 if an EOF is seen).  Actually, a
working implementation of line will be sufficient (although, obviously,
the C code for line will not do).

I'd also very much appreciate it if someone could restore my faith in
awk by explaining its output!

Alan.


^ permalink raw reply	[flat|nested] 5+ messages in thread

* read
@ 1993-05-15  4:14 Alan Watson
  0 siblings, 0 replies; 5+ messages in thread
From: Alan Watson @ 1993-05-15  4:14 UTC (permalink / raw)
  To: rc

I know we've spent some time on this topic before, but I would like to
suggest that a read primitive should be added to rc.  The promise of one
over in the es list has thrown me into an evangelical mood.

(a) Boyd Roberts wrote in response to a suggestion to implement test
functionality in rc `The shell is there to run programs, not to have
programs built into it,' and I agree.  However, the shell should also
possess an appropriate set of primitives for linking programs together
in an efficient manner, and it is my opinion that the likes of echo and
read are essential for this capability.

(b) There have been many implementations of read posted to the list, and
most of them have been contained bugs.  Even the one in the EXAMPLES
file does not behave like a true sh read upon encountering EOF (it sets
the variable to '1', rather than unsetting it).

(c) There are reads and there are reads: some take only one argument,
others take multiple arguments; some split the input line according to
ifs, others don't; behaviour on EOF is almost entirely unpredictable.  A
builtin read, documented in the man page, would be completely portable.
(This alone justifies the builtin echo independently of performance
issues -- there's no uncertainty as to whether one is following SYSV or
BSD semantics when one uses echo in an rc script.  I might even go as
far as to say that the NOECHO option should be removed, and those people
with religeous objections can use a function to override the builtin and
get at /bin/echo, so that builtin echo will always be predicable.)

(d) External implementations of read are slow as hell, and this really
limits their usefulness.  (C'mon, Byron, you added echo just for this
reason!)

To me, the most important points are (c) and (d) (after all, it is a
simple matter to fix the read in the EXAMPLES file).  If we had an
agreed on definition of the semantics of read, I could implement it as
an addon and the rest of you could implement it as a function and we'd
all be happy.  For better or for worse, rc does not have a mechanism for
implementing `standard' functions, unlike es, so there is no method
available to universally instantiate such a definition other than as a
builtin.  

Code bload is not an issue -- this builtin adds about 80 very simple and
straightforward lines.  The only issues are whether read should take
multiple arguments, and whether it should perform ifs separation on the
line to make a list.

I know you're busy, Byron, so I'll append my implementation of a builtin
read at the end of this message, together with a trip.read.  This read
takes only one argument, does not perform ifs separation, does strip the
trailing newline, and unsets the variable when no input is read.
(Basically, it is a single-argument analogue of the v7 sh read.)

Alan.

; diff -e ORIG/addon.h addon.h
23a
#ifndef NOREAD
#undef  ADDONS
#define ADDONS { b_read,        "read" },
extern void b_read (char **av);
#endif

.
; diff -e ORIG/addon.c addon.c
5a
#include "rc.h"
#include "addon.h"

#ifndef NOREAD

/* 
 b_reads -- read a single line, terminated by \n or EOF, from the standard
 input, and assign the line without the terminator to av[1].
 */

static int
readchar (int fd)
{
   unsigned char c;

   if (read (fd, &c, 1) == 1)
      return c;
   else
      return EOF;
}

void
b_read (char **av)
{
   char *name;
   int c;
   char *line;
   SIZE_T len;
   SIZE_T max_len;
   SIZE_T max_len_quantum;

   /* check usage is "read name" */
   if (av[1] == NULL)
      rc_error ("missing variable name");
   if (av[2] != NULL)
      rc_error ("too many arguments to read");
   name = av[1];

   /* read a single line from stdin */
   line = NULL;
   len = 0;
   max_len = 0;
   max_len_quantum = 256;
   do {
      c = readchar (0);
      if (len == 0 && c == EOF)
	 break;
      if (c == '\n' || c == EOF)
	 c = 0;
      if (len == max_len) {
	 max_len += max_len_quantum;
	 line = erealloc (line, max_len);
      }
      line[len] = c;
      len++;
   } while (c);

   /* if we successfully read a line assign it to name and return TRUE, 
    * otherwise unset name and return FALSE */
   if (line != NULL) {
      assign (word (name, NULL), word (line, NULL), FALSE);
      efree (line);
      set (TRUE);
   } else {
      assign (word (name, NULL), NULL, FALSE);
      set (FALSE);
   }

   return;
}

#endif

.
; cat trip.read
# trip.read -- check a read implementation performs like a true v7 sh read

fn check {
   ./rc -c $2 >[2=]
   if ( ~ $1 0 ) {
      ~ $status 0 || echo failed: $2
   } else {
      ! ~ $status 0 || echo failed: $2
   }
}

check 0 true
check 1 false

nl = '
'
ifs = $nl

file = /etc/passwd
first = `{ cat $file }
first = $first(1)

echo 'start of trip.read'

# check read X and read '*' return 0
check 0 'exec <$file ; read X'
check 0 'exec <$file ; read ''*'''

# check read, read 1, read '' return !0
check 1 'exec <$file ; read'
check 1 'exec <$file ; read 1'
check 1 'exec <$file ; read '''''

# check read X correctly reads first line
check 0 'exec <$file ; read X ; ~ $X $first'

# check read '*' correctly reads first line, preserves $0
check 0 'exec <$file ; old0 = $0 ; read ''*'' ; ~ $0 $old0 && ~ $1 $first && ~ $#* 1'

# check read returns !0 on EOF and I/O error
check 1 'exec </dev/null ; read X'
check 1 'exec <[0=]      ; read X'

# check read sets variable to () on EOF and I/O error.
check 0 '{ exec </dev/null ; X = X ; read X ; ~ X () }'
check 0 '{ exec <[0=]      ; X = X ; read X ; ~ X () }'

echo 'end of trip.read'

# end-of-file


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~1993-05-18  2:13 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1993-05-18  2:13 read Alan Watson
  -- strict thread matches above, loose matches on Subject: below --
1993-05-16  7:51 read Alan Watson
1993-05-16  6:49 read Alan Watson
1993-05-16  7:12 ` read Chris Siebenmann
1993-05-15  4:14 read Alan Watson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).