Step 7 of 7
Challenge: a real command-line tool
Command-line tools are glued together by scripts and other programs, and that only works because they all follow a few conventions. Writing tools that follow them is part of writing professional C.
stdout and stderr
A program has two output streams. stdout is for the program's actual results; stderr is for errors and diagnostics. They're kept separate so that ./tool > results.txt saves only results, while errors still appear on the screen. Write errors with fprintf(stderr, ...).
Exit codes
The value main returns (or passes to exit()) is the exit code. 0 means success; anything else means failure, and different numbers can signal different problems (by convention, 2 often means "used incorrectly"). Shell scripts check it: if ./tool; then ..., and CI systems fail a build when a step exits nonzero. <stdlib.h> names the basics EXIT_SUCCESS and EXIT_FAILURE.
#include <stdio.h>
#include <stdlib.h>
int check_all(const int *values, int n) {
int status = EXIT_SUCCESS;
for (int i = 0; i < n; i++) {
if (values[i] < 0) {
fprintf(stderr, "check: value %d is negative\n", i);
status = EXIT_FAILURE;
continue;
}
printf("value %d ok: %d\n", i, values[i]);
}
return status;
}
int main(void) {
int v[] = {3, 8, 5};
int status = check_all(v, 3);
printf("done\n");
return status;
}
value 0 ok: 3
value 1 ok: 8
value 2 ok: 5
done
Keep going, but remember the failure
A good tool processes everything it can: one missing file shouldn't stop the others from being counted. It reports each problem on stderr as it happens, remembers that something failed (the status variable), and returns the failure code at the end. Early exit is right only when continuing makes no sense, such as no arguments at all, where you print a usage message to stderr.
The tests on this site check stdout and the exit code separately, just like a script would.
Your task
Write lines FILE..., a tool that prints COUNT NAME for each file and a final COUNT total line. A file that can't be opened prints lines: cannot open NAME to stderr, is skipped, and makes the program exit with code 1 at the end (after the total). With no arguments at all, print usage: lines FILE... to stderr and exit with code 2.
Challenge 2
Write head N FILE, which prints the first N lines of FILE (fewer if the file is shorter). Errors go to stderr:
- wrong number of arguments:
usage: head N FILE, exit code 2 Nisn't a whole number of 0 or more:head: bad count: N, exit code 2- the file can't be opened:
head: cannot open FILE, exit code 1
Challenge 3
Write find WORD FILE..., a small version of grep. For every line that contains WORD, print FILE: LINE. Follow grep's exit codes:
- 0 if at least one line matched
- 1 if nothing matched
- 2 on an error: fewer than two arguments (print
usage: find WORD FILE...to stderr), or a file that can't be opened (printfind: cannot open FILEto stderr, skip it, and still search the others)
Lines are at most 255 characters long.