From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from oldp.astro.wisc.edu ([128.104.39.15]) by hawkwind.utcs.toronto.edu with SMTP id <2223>; Sat, 15 May 1993 00:14:51 -0400 Received: by oldp.astro.wisc.edu (5.65/DEC-Ultrix/4.3) id AA01457; Fri, 14 May 1993 23:14:17 -0500 Message-Id: <9305150414.AA01457@oldp.astro.wisc.edu> To: rc@hawkwind.utcs.toronto.edu Subject: read Date: Sat, 15 May 1993 00:14:16 -0400 From: Alan Watson X-Mts: smtp 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