Exercise 5-12. Extend
entabanddetabto accept the shorthandentab -m +nto mean tab stops everyncolumns, starting at columnm. Choose convenient (for the user) default behavior.
Two new argument forms extend the programs from Exercise 5-11: -m sets the starting column and +n sets the interval. So detab -4 +3 means stops at columns 4, 7, 10, 13, … Default if omitted: start at column 8, interval 8 (same as before). Plain numeric arguments (e.g., detab 4 8 16) continue to work as explicit stop positions.
Solution — Argument Parsing
/* K&R Exercise 5-12 — detab/entab with -m +n shorthand
* Compile: gcc -ansi -Wall detab12.c -o detab12
* Usage: ./detab12 -4 +3 OR ./detab12 4 8 12 OR ./detab12 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXSTOPS 200
#define DEFAULT_START 8
#define DEFAULT_INTERVAL 8
static int tabstops[MAXSTOPS];
static int ntabs = 0;
static void build_stops(int start, int interval)
{
int col;
ntabs = 0;
for (col = start; col < MAXSTOPS && ntabs < MAXSTOPS; col += interval)
tabstops[ntabs++] = col;
}
static int next_tabstop(int col)
{
int i;
for (i = 0; i < ntabs; i++)
if (tabstops[i] > col)
return tabstops[i];
/* past all defined stops: revert to interval-8 */
return col - (col % DEFAULT_INTERVAL) + DEFAULT_INTERVAL;
}
static void parse_args(int argc, char *argv[])
{
int start = -1, interval = -1, i;
int explicit[MAXSTOPS];
int nexplicit = 0;
for (i = 1; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] != '\0')
start = atoi(argv[i] + 1);
else if (argv[i][0] == '+')
interval = atoi(argv[i] + 1);
else if (nexplicit < MAXSTOPS)
explicit[nexplicit++] = atoi(argv[i]);
}
if (nexplicit > 0) {
/* explicit stops take precedence */
for (i = 0; i < nexplicit; i++)
tabstops[ntabs++] = explicit[i];
} else {
build_stops(start < 0 ? DEFAULT_START : start,
interval < 0 ? DEFAULT_INTERVAL : interval);
}
}
int main(int argc, char *argv[])
{
int c, col = 0, spaces, next;
parse_args(argc, argv);
while ((c = getchar()) != EOF) {
if (c == '\t') {
next = next_tabstop(col);
for (spaces = next - col; spaces > 0; spaces--)
putchar(' ');
col = next;
} else if (c == '\n') {
putchar(c); col = 0;
} else {
putchar(c); col++;
}
}
return 0;
}
Test It
printf "\thello\tworld\n" | ./detab12 # every 8 (default)
printf "\thello\tworld\n" | ./detab12 -4 +4 # stops at 4,8,12,16...
printf "\thello\tworld\n" | ./detab12 4 8 16 # explicit stops
What This Exercise Teaches
- Multi-form argument parsing — a single
argvscan distinguishes three forms by the leading character (-,+, digit); this is the classic Unix option-parsing pattern beforegetopt - Generating stop tables at runtime — when stops follow an arithmetic pattern (
-m +n), precompute the full table rather than computing on every character; faster inner loop - Default selection —
start < 0 ? DEFAULT_START : startuses the sentinel value -1 to mean “not specified”; clean and avoids a separate flag variable
Set Up Your C Environment
← Exercise 5-11 |
Chapter 5 Solutions |
Exercise 5-13 →
Book: The C Programming Language, 2nd Ed — Kernighan & Ritchie