C in 100 Seconds: fgets | Episode 13
Video: C in 100 Seconds: Safe Input with fgets | Episode 13 by Taught by Celeste AI - AI Coding Coach
C fgets: Safe Input
fgets(buf, sizeof(buf), stdin)reads a full line (or up to size-1 bytes), always null-terminates, never overflows. Strip the trailing\nwithstrcspn.
fgets is the safe, predictable input function. Unlike scanf, it reads a whole line, respects buffer size, and doesn't have the format-specifier minefield.
The basic shape
#include <stdio.h>
#include <string.h>
int main() {
char line[20];
printf("Enter a sentence: ");
fgets(line, sizeof(line), stdin);
line[strcspn(line, "\n")] = 0;
printf("You said: [%s]\n", line);
printf("Length: %lu\n", strlen(line));
return 0;
}
User input: "hello world" Output: "You said: [hello world] Length: 11"
fgets signature
char *fgets(char *str, int n, FILE *stream);
str— buffer to write into.n— maximum bytes to read (including null terminator).stream— usuallystdin; can be a file pointer.- Returns —
stron success,NULLon EOF or error.
Reads until either:
- A newline is encountered (newline is included in the buffer).
- n - 1 bytes have been read.
- EOF is reached.
Always null-terminates the result.
sizeof for the size
char buf[100];
fgets(buf, sizeof(buf), stdin); // sizeof gives 100 — exactly right
sizeof(buf) gives the actual size, no off-by-one risk. If you change 100 to 200, the size argument follows automatically.
For pointers (not arrays), sizeof(ptr) gives pointer size (usually 8). Don't use sizeof on a pointer if you mean buffer size — track the size separately.
Stripping the trailing newline
fgets includes the newline in the buffer:
char buf[100];
fgets(buf, sizeof(buf), stdin);
// User typed "hello"; buf is "hello\n\0"
To strip:
buf[strcspn(buf, "\n")] = '\0';
strcspn(s, "abc") returns the position of the first character of s that's in the set "abc". If there's a \n, this points to it; otherwise, it points to the terminating \0.
Setting that position to \0 truncates: "hello\n\0" becomes "hello\0".
Alternative: check the last char.
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '\0';
}
More verbose; same effect.
Reading numbers safely
char line[100];
fgets(line, sizeof(line), stdin);
int x;
if (sscanf(line, "%d", &x) == 1) {
printf("Got: %d\n", x);
} else {
printf("Not a valid number\n");
}
fgets reads the line; sscanf parses from it. Two-step approach:
- fgets — bounded, predictable read.
- sscanf — try to parse the contents.
If sscanf fails, the line stays in buf for inspection. If scanf had failed, the buffer would still be in stdin, complicating retries.
Reading until EOF
char line[100];
while (fgets(line, sizeof(line), stdin) != NULL) {
// process line
}
fgets returns NULL on EOF. Standard pattern for "read all input."
For interactive terminal: type Ctrl-D (Linux/macOS) or Ctrl-Z (Windows) to send EOF.
For piped input: echo -e "line1\nline2" | ./prog.
Detecting truncation
If the input is longer than your buffer, fgets reads only n-1 bytes. The buffer will not contain a \n. To detect:
char line[20];
fgets(line, sizeof(line), stdin);
if (strchr(line, '\n') == NULL) {
// line was truncated — input was longer than 19 bytes
// possibly drain rest of line:
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
Important when input length matters (parsing fixed-width records, etc.).
fgets vs gets
gets(buf) reads until newline with no length limit. It was so dangerous it's been removed from C11 onward (and deprecated in C99). Always use fgets.
If you see gets in old code, replace it.
Reading lines from a file
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) { perror("fopen"); return 1; }
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
// process line
}
fclose(fp);
Same fgets API, different stream. Episode 35 covers file I/O in depth.
Common mistakes
Forgetting to strip the newline. printf("[%s]\n", line) shows [hello\n] — extra blank line. Strip with strcspn.
Using sizeof on a pointer. void f(char *buf) { fgets(buf, sizeof(buf), stdin); } — sizeof(buf) is 8 (pointer size). Pass the size as a separate parameter.
Not handling NULL return. fgets returns NULL on EOF. Without checking, you process a stale buffer.
Mixing fgets and scanf. Switching between them creates leftover-newline issues. Pick one.
Buffer too small for typical input. A 20-char buffer for user names is fine; a 20-char buffer for a URL is not.
Why fgets > scanf for user input
| Aspect | scanf | fgets |
|---|---|---|
| Buffer overflow | Yes (without %Ns) |
Never |
| Reads whole line | No (stops at whitespace) | Yes |
| Leaves leftover input | Yes (newline trap) | No |
| Easy to retry on bad input | No (input still in buffer) | Yes (parse from string) |
| Format-string complexity | Yes | No |
For interactive programs and file processing, always use fgets. Save scanf for highly-formatted input you control.
What's next
Episode 14: #define, const, enum. Three ways to give names to constants. When to use each.
Recap
fgets(buf, sizeof(buf), stdin) for safe line input. Always null-terminates, never overflows. Includes the newline — strip with buf[strcspn(buf, "\n")] = '\0'. Returns NULL on EOF. Pair with sscanf for typed parsing. Avoid gets (removed from modern C). Use sizeof(buf) for actual buffer arrays; pass size separately for pointers.
Next episode: #define, const, enum.