Part of C in 100s

C in 100 Seconds: fgets | Episode 13

Celest KimCelest Kim

Video: C in 100 Seconds: Safe Input with fgets | Episode 13 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

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 \n with strcspn.

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 — usually stdin; can be a file pointer.
  • Returnsstr on success, NULL on 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:

  1. fgets — bounded, predictable read.
  2. 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.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.