Reputation: 69944
I want to read a list of words from a file, which has one word per line. The words should have up to 4 characters each. How can I produce an error if one of the lines is longer than that?
I tried reading the words using fgets
char buf[5];
fgets(buf, 5, stdin);
and with scanf
char buf[5];
scanf("%4s", &buf);
but in both cases it splits long lines into smaller lines. For example qwerasdf
is read as two words, qwer
and asdf
. Is there a way to detect that it tried to read a long line with more than 4 characters and give an error instead?
The only alternative I can think of is reading the input character-by-character and taking care of everything by myself. But is there a simpler solution using functions from the standard library?
Upvotes: 0
Views: 992
Reputation: 2829
Here I made this function to read the file char by char and returns only one line per call
so now you can read your file line by line, the
type Line has an array of chars value
where we store
the line and an int hasNextLine
1 or 0 (bool)
that tell you if the file has another line or no,
this is handy when you loop over the file line by line.
#include <stdlib.h>
#include <stdio.h>
typedef struct {
char *value;
int hasNextLine;
} Line;
Line * getLine(FILE *file) {
Line *line = (Line *)malloc(sizeof(Line));
if(line == NULL) {
return NULL;
}
line->value = NULL;
line->hasNextLine = 1;
int n = 0, c;
while(1) {
c = getc(file);
char *tmpStr = (char *)realloc(line->value, n + 2);
if(tmpStr == NULL) {
line->hasNextLine = -1;
return line;
}
line->value = tmpStr;
if(c == EOF) {
line->hasNextLine = 0;
line->value[n] = '\0';
return line;
}
if(c == '\n') {
line->value[n] = '\0';
return line;
}
line->value[n] = c;
n++;
}
return line;
}
Usage:
// example reading one line
int main() {
FILE *f = fopen("your_file.txt", "r");
if(f == NULL) {
printf("File not found!");
return 1;
}
Line *l = getLine(f);
if(l != NULL) {
printf("%s\n", l->hasNextLine != -1 ? l->value :
"Error: while getting the line");
free(l->value);
free(l);
}
fclose(f);
return 0;
}
// example reading the whole file
int main() {
FILE *f = fopen("your_file.txt", "r");
if(f == NULL) {
printf("File not found!");
return 1;
}
Line *l;
int hasNextLine;
while(1) {
l = getLine(f);
if(l != NULL) {
printf("%s\n", l->hasNextLine != -1 ? l->value :
"Error: while getting the line");
free(l->value);
hasNextLine = l->hasNextLine;
free(l);
}
if(hasNextLine <= 0) {
break;
}
}
fclose(f);
return 0;
}
you can make a custom function for user input
char * sgetLine(char *msg) {
printf("%s", msg);
Line *l = getLine(stdin);
char *strLine = NULL;
if(l == NULL) {
return NULL;
}else {
if(l->hasNextLine == -1) {
free(l->value);
free(l);
return NULL;
}
strLine = l->value;
free(l);
return strLine;
}
}
so now you can use one function call to print the question and to get the answer (char array)
int main() {
char *l = sgetLine("What is your name? ");
if(l != NULL) {
printf("%s\n", l);
}
free(l);
return 0;
}
Upvotes: 1
Reputation: 84569
You are making an excellent choice reading with fgets()
, the only rule-of-thumb you are breaking is don't skimp on buffer size. But, even if you do, you can handle things properly with fgets()
.
When you read a line from a file, fgets()
(or POSIX getline()
) read and include the '\n'
as part of the buffer they fill (if there is room). If you are expecting up to 4-characters, then a buffer size of 5 is too-short-by-one to accommodate all your characters, the nul-terminating character, and the '\n'
. Your circumstance attempting to read a 4-character line ("cats"
) with a 5-character buffer with fgets()
would result in buf
holding:
+---+---+---+---+---+
| c | a | t | s | \0| --> '\n' remains unread
+---+---+---+---+---+
You can gracefully handle that as well (but better not to skimp on buffer size) To gracefully handle the issue you need to check:
'\n'
is the last char in the buffer, complete line read, trim '\n'
by overwriting with nul-terminating character;'\n'
, then OK, you read all chars and there wasn't room for the '\n'
which you just read and checked -- continue reading the next line;EOF
, then you read all characters in the last line in a file with a non-POSIX end-of-file (no '\n'
after the final line of data), break read loop you found EOF
;'\n'
or EOF
is foundPutting that logic together, you could do:
#include <stdio.h>
#include <string.h>
int main (void) {
char buf[5];
while (fgets (buf, 5, stdin)) { /* read each line */
if (strchr (buf, '\n')) /* if '\n' found - line read */
buf[strcspn (buf, "\n")] = 0; /* nul-termiante at '\n' */
else { /* otherwise */
int c = getchar(); /* read next chars */
if (c == '\n') /* if '\n', OK read next line */
continue;
else if (c == EOF) /* if EOF, OK, non-POSIX eof */
break;
fputs ("error: line too long - discarding remainder.\n", stderr);
for (; c != '\n' && c != EOF; c = getchar()) {}
}
}
}
Look things over and let me know if you have further questions.
Upvotes: 2
Reputation: 27116
You could check for the length of the read string and since fgets also reads the newline character, you could explicitly check for '\n' as the last input character.
char buf[6];
while (fgets(buf, sizeof(buf), stdin)) {
if (strlen(buf) > 5
|| (strlen(buf) == 5 && buf[strlen(buf) - 1] != '\n')) {
fprintf(stderr, "line too long\n");
exit(EXIT_FAILURE);
}
}
The buffer must consist of at least six characters: 4 input characters + 1 newline character + the string terminating NUL byte.
Upvotes: 2