Why should strtok(line, "\n") **not** be used to strip the newline left by fgets()
Asked Answered
N

1

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?

Nimbostratus answered 25/1, 2021 at 22:12 Comment(3)
Closely related: Removing trailing newline character from fgets() input.Postmistress
@FredLarson: interesting reference. I am specifically targeting the misuse of strtok() which I have seen multiple times recently.Nimbostratus
Yes, and which is part of the accepted answer on the question I linked.Postmistress
N
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.
    
Nimbostratus answered 25/1, 2021 at 22:12 Comment(2)
+ it is like programming two days the robotic arm for one screw.Bochum
@0___________: or use a steam hammer to drive a nail :)Nimbostratus

© 2022 - 2024 — McMap. All rights reserved.