/* # getline.c # Written by D'Arcy J.M. Cain # darcy@druid.net # Copyright 1991, 1992 # This code may be used on any computer system for any purpose by anyone NAME getline, xgetline, wgetline SYNOPSIS char *getline(FILE *fp, char *buf); char *xgetline(FILE *fp, char *buf, size_t *linenum); char *sgetline(FILE *fp, char *buf); xcat [file ...] # shell function DESCRIPTION Reads a line from the stream given by fp (or from the window given by win in wgetline) and returns a pointer to the string. There is no length restriction on the returned string. Space is dynamically allocated for the string as needed. The pointer given is realloced so the calling function should not expect the pointer to be valid after the call. It should reset the pointer to the return value of this function. At end of file the space will be freed. The way to use this function would be something like this: char *buf = NULL; FILE *fp; ... open file ... while ((buf = getline(fp, buf)) != NULL) ... do stuff with buf ... Because the xgetline may read an unknown number of lines, the linenum variable allows the calling process to track the real line number. If it is non-NULL then it is taken as a pointer to a variable that gets incremented each time the function reads another raw line in. In the xgetline version anything from '#' till the end of line is ignored and A trailing '\' character is treated as a continuation character. After this processing the resulting line is ignored if it is empty. The '#' and '\' characters can be included by preceding them with a '\'. Note that the leading backslash is left in the input string and must be dealt with by the caller. This can be somewhat of a problem at the end of a line but in general should be workable. If the caller wants to keep the memory it should call with the buf argument set to NULL every time. If the first argument is NULL then the function returns its second arg. Defining XCAT causes a main function to be included allowing the xgetline function to be available in shell programs. I prototype getline and xgetline in stdio.h since their use always involves stdio routines. See further discussion below. Note there used to be a curses version but it was so seldom used that I removed it for simplicity. You generally don't want to have unbounded input in a curses window. The sgetline version is for a special case in one of my packages and you shouldn't worry about it. RETURNS A pointer to the string without the terminating newline is returned if successful or NULL if there was an error or end of file. Use feof(3) and ferror(3) to find out if it was a file error, memory allocation problem or EOF condition. AUTHOR D'Arcy J.M. Cain (darcy@druid.net) */ #include #include #include #include #ifndef CTRL #define CTRL(x) ((x) & 0x1f) #endif /* I originally was going to use 80 here as the most common case but */ /* decided that a few extra bytes to save a malloc from time to time */ /* would be a better choice. Comments welcome. */ #define CHUNK 128 /* XCAT implies XGETLINE */ #ifdef XCAT #ifndef XGETLINE_VERSION #define XGETLINE_VERSION #endif #endif #ifdef XGETLINE_VERSION char *xgetline(FILE *fp, char *buf, size_t *linenum); void xgetline_cchar(char c); char comment_char = '#'; #else char *getline(FILE *fp, char *buf); #endif #ifdef STREAM_VERSION # include "stream.h" # define getline sgetline # define INPUT_STREAM int # define mygetc(fp) stream_getc(fp) #else # define INPUT_STREAM FILE * # ifdef XGETLINE_VERSION # define getline xgetline int xskipwhitespace = 0; /* skips leading space if true */ # endif /* XGETLINE_VERSION */ # ifdef __MSDOS__ # define mygetc(fp) getc(fp) # else /* this let's it read DOS files */ static int mygetc(INPUT_STREAM fp) { int c; static int last = -1; if (last != -1) { c = last; last = -1; return c; } if ((c = getc(fp)) == '\r') { if ((c = getc(fp)) != '\n') last = c; return('\n'); } return(c); } # endif /* __MSDOS__ */ #endif /* STREAM_VERSION */ #ifdef XGETLINE_VERSION void xgetline_cchar(char c) { comment_char = c; } char * getline(INPUT_STREAM fp, char *buf, size_t *linenum) #else char * getline(INPUT_STREAM fp, char *buf) #endif { size_t sz = CHUNK; /* this keeps track of the current size of buffer */ size_t i = 0; /* index into string tracking current position */ char *ptr; /* since we may set buf to NULL before returning */ int c; /* to store getc return */ #ifdef XGETLINE_VERSION int in_comment = 0; /* if we are in a comment */ int in_quote = 0; /* if we are in quote and holds quote character */ #endif /* no real good idea for handling this */ if (!fp) return(buf); /* start out with buf set to CHUNK + 2 bytes */ if ((buf = realloc(buf, CHUNK + 2)) == NULL) return(NULL); /* get characters from stream until EOF */ #ifdef XGETLINE_VERSION while ((c = mygetc(fp)) != EOF) #else while ((c = mygetc(fp)) != EOF && c != '\n') #endif { #ifdef XGETLINE_VERSION if (xskipwhitespace && !i && isspace(c) && c != '\n') continue; buf[i] = c; if (c == '\n') { in_comment = in_quote = 0; /* nl ends comment and quote */ if (linenum) (*linenum)++; /* keep track of current line */ /* lose trailing spaces */ for (i++; i && isspace((int) buf[i - 1]); i--) buf[i - 1] = 0; if (i) break; } else if (in_comment) { /* continuation still ends comment - alternative would be silly */ if (c == '\\' && (c = mygetc(fp)) == '\n') { in_comment = 0; if (linenum) (*linenum)++; while (isspace(c = mygetc(fp))) ; ungetc(c, fp); } } else if (in_quote) { i++; if (c == in_quote) in_quote = 0; } else if (c == '\'' || c == '"') { in_quote = c; i++; } else if (c == comment_char) in_comment = 1; else if (c == '\\') { if ((c = mygetc(fp)) != '\n' && c != EOF) { buf[i++] = '\\'; buf[i++] = c; } else { while (isspace(c = mygetc(fp))) ; ungetc(c, fp); buf[i++] = ' '; if (linenum) (*linenum)++; } } else i++; #else /* the following needed in case we are in cbreak or raw mode */ if (c != '\b') buf[i++] = c; else if (i) i--; #endif /* check for buffer overflow */ if (i >= sz) if ((buf = realloc(buf, (sz += CHUNK) + 2)) == NULL) return(NULL); } /* is there anything to return? */ if (c == EOF && !i) { free(buf); return(NULL); } buf[i++] = 0; /* yes I want the ++ */ /* test for error but don't bother explaining if it fails */ if ((ptr = realloc(buf, i)) == NULL) ptr = buf; return(ptr); } #ifdef TEST_MODULE int main(void) { char *p; while ((p = getline(stdin, 0)) != NULL) printf("%s\n", p); return(0); } #endif #ifdef XCAT #include static void xcat(FILE *fp) { char *p = NULL; while ((p = xgetline(fp, p, 0)) != NULL) printf("%s\n", p); } int main(int argc, char **argv) { FILE *fp; int k; if (argc < 2) xcat(stdin); else for (k = 1; k < argc; k++) { if ((fp = fopen(argv[k], "r")) == NULL) fprintf(stderr, "xcat: Can't open file %s - %s\n", argv[k], strerror(errno)); else { xcat(fp); fclose(fp); } } return(0); } #endif /* XCAT */