hugomg
hugomg

Reputation: 69944

In C, how can I produce an error if the input string is too big?

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

Answers (3)

Saadi Toumi Fouad
Saadi Toumi Fouad

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

David C. Rankin
David C. Rankin

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:

  • if '\n' is the last char in the buffer, complete line read, trim '\n' by overwriting with nul-terminating character;
  • otherwise, read next char;
    • if next char is '\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;
    • else if next char is 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;
  • else additional character remain unread in the line, read and discard characters until the next '\n' or EOF is found

Putting 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

Stephan Schlecht
Stephan Schlecht

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

Related Questions