How to determine number of characters that were read with fgets()?
Asked Answered
S

2

11

This is the description of fgets() from the man page:

char *fgets(char *s, int size, FILE *stream);
...

RETURN VALUE
  fgets() returns s on success, and NULL on error or  when  end  of  file
  occurs while no characters have been read.

It doesn't follow the pattern of read, which returns -1 on failure and the number of bytes read on success. Instead, it returns a char* which is NULL on failure and s on success. This doesn't give me any info about how long the input is. So if I have something like this:

char input_buffer[256];
fgets(input_buffer, sizeof(input_buffer), stdin);

After the fgets call, is there any way to tell how long the input is WITHOUT zero-initializing the buffer first?

Thanks.

Sheol answered 14/12, 2017 at 15:51 Comment(6)
Possible duplicate of Return value of fgets()Laid
int len= strlen(input_buffer); will give you that.Ligurian
That why I don't like the design of fgets(), I prefer fread() but it doesn't handle locale.Madden
Good to know strlen will do the trick, but is there any solution that doesn't involve O(n) iteration? Seems wasteful that you have to figure it out again when fgets already knows what the length is.Sheol
@Stargateur, fread() does not NUL terminate the input, but fgets() does. So strlen() works for fgets() but not for fread(). Each function has its' purpose and capabilities. the function fgets() is written so it can be 'chained' The function: fread() is not. the function: fgets() is for text input. the function: fread() is for binary input.Cismontane
@Cismontane Thanks captain obvious. Still, I don't like int type of size argument of fgets(), I don't like to need to use strlen() to know the size of my string. getline() is so mush better but posix, fread() don't add nul byte but you can do it easilly. Like I say fread() isn't equal to fgets() locale, nul byte etc... but it has a better design.Madden
J
11

How to determine number of characters that were read with fgets()?

char *fgets(char *s, int size, FILE *stream);

Use strlen(s) after checking the fgets() return value.

if (fgets(s, size, stream)) {
  printf("number of characters that were read: %zu\n", strlen(s));
} else if (feof(stream)) {
  printf("number of characters that were read:0 End-of-file\n");
} else  {
  printf("number of characters that were read unknown due to input error\n");
}

This works unless a null character '\0' is read as strlen() will encounter that '\0' before the appended one by the function. In that case, strlen(s) after fgets() will report a smaller value.

There are various tricks to pre-fill s and then call fgets(), yet it is undefined what happens to the rest of the unread buffer. Other short comings exist.

If null characters as part of a valid input stream are a concern, use fgetc() or something like getline().


A common scenario where null characters are text is when text is encoded as UTF-16. Of course code should not use fgets() to read that text, yet that requires prior knowledge. Much code that reads text has failed in mysterious ways due to the incorrect assumption that a text file is a non-null character text file.

Further, even with a text file supposedly lacking a null characters, what happens with the following code?

if (fgets(s, size, stream)) {
  size_t len = strlen(s);
  s[--len] = '\0';  // poor way to lop off the trailing \n, this could be UB
}

Such code invokes undefined behavior with a hacker exploit: slipping a null character in the file at the beginning of the line. (See this and this for better solutions to lop off the potential \n)

Robust code does not assume the text is well formed and takes measures to detect abnormalities.


Pedantic note: there are pathological problems with fgets(char *s, int size, FILE *stream); with a size < 2.

Juliajulian answered 14/12, 2017 at 18:10 Comment(1)
Kudos for a number of important points that generally fly right under the radar of most -- including me...Oyster
C
4

Yes there is. It is always null terminated in case of success. So it will be strlen(buf).

From standard 7.21.7.2

char *fgets(char * restrict s, int n,FILE * restrict stream); The fgets function reads at most one less than the number of characters specified by n from the stream pointed to by stream into the array pointed to by s. No additional characters are read after a new-line character (which is retained) or after end-of-file. A null character is written immediately after the last character read into the array.

Cinchona answered 14/12, 2017 at 15:53 Comment(7)
Provided, of course, that the input does not itself include a null character, which would break this scheme. But that is a common limitation.Franchot
@JohnBollinger.: Well yes that's a case but to avoid that we can also do another check (which is again not fully solid) that is to check for \n?Cinchona
Yes, checking for a newline does the job, provided that the buffer supplied to fgets() is large enough to accommodate the whole line, including the newline. In many cases it is safer to assume or require that the input not contain nulls than to assume or require that it not contain long lines. It is relevant to note here that fgets() is most appropriate for text, so that the proviso that the input must not contain nulls is usually a pretty low bar.Franchot
@JohnBollinger if your text is expected to contain null terminators, than fgets probably should not be your first choice of reading function.Sweetie
@JohnBollinger.: So suppose, if the input contains nulls and the line is not big enough to hold \n then I guess none of these would work. (which is very unlikely).Cinchona
Good to know strlen will do the trick, but is there any solution that doesn't involve O(n) iteration? Seems wasteful that you have to figure it out again when fgets already knows what the length is.Sheol
@JamesKo.: With fgets this is the best you can get. If you need anything more than this I guess you will have to use some other function etcCinchona

© 2022 - 2024 — McMap. All rights reserved.