3

fgets() is a safe function to read a line of input, but it stores the new line byte '\n' read from the file in the array if it fits.

In many if not most cases, this new line must be removed before further processing the line contents.

Several simple methods can be used for that, but I have seen a compact and tricky proposal:

strtok(line, "\n");    // strip the newline

Why is this method incorrect and why does it not always work?

chqrlie
  • 98,886
  • 10
  • 89
  • 149
  • 1
    Closely related: [Removing trailing newline character from fgets() input](https://stackoverflow.com/q/2693776/10077). – Fred Larson Jan 25 '21 at 22:18
  • @FredLarson: interesting reference. I am specifically targeting the misuse of `strtok()` which I have seen multiple times recently. – chqrlie Jan 25 '21 at 22:22
  • 1
    Yes, and which is part of the accepted answer on the question I linked. – Fred Larson Jan 25 '21 at 22:23

1 Answers1

6

The method is tricky as the strtok() function has side effects on a global hidden state variable. This may affect surrounding code and prove hard to debug.

Furthermore, there is a simple case where strtok(line, "\n") will not overwrite the '\n' with a null byte: If the line read by fgets() is an empty line containing only a single new line byte. For this contents, strtok() will skip the initial new line, searching for a different character, which is not present, and return NULL not modifying the array. Hence it will not strip the new line.

This is a compelling reason to not use strtok(line, "\n") to strip the new line byte.

Of course one can fix this issue by writing:

   if (*line == '\n')
       *line = '\0';
   else
       strtok(line, "\n");

Or cumbersome one-liners:

    (void)(*line == '\n' ? (*line = '\0') : (strtok(line, "\n"), 0);
    if (!strtok(line, "\n")) *line = '\0';
    (void)(strtok(line, "\n") || (*line = '\0'));

But the code is no longer compact and still has other side effects.

Other methods are available:

  • using an explicit for statement:

      for (char *p = line; *p; p++) {
          if (*p == '\n')
              *p = '\0';
      }
    
  • using strlen():

      size_t len = strlen(line);
      if (len > 1 && line[len - 1] == '\n') {
          line[--len] = '\0';
      }
      // len is the length if the stripped line
    
  • using strchr():

      char *p = strchr(line, '\n');
      if (p) {
          *p = '\0';
      }
    
  • using strcspn() in a one-liner:

      line[strcspn(line, "\n")] = '\0';  // strip the newline if any.
    
chqrlie
  • 98,886
  • 10
  • 89
  • 149