One Unix command I never remember how to use properly is the seq command to produce a list of numbers. This is a simple but useful program when you need to test a program that works on text, because you know exactly what it will produce and how much it will print. Unfortunately, the usage changes if you use one, two, or three command line options—so I always have to look up the man page to remind myself how to use it. I had to look it up again so I could write this article:
- seq by itself prints the numbers 1 to 10
- seq i prints the numbers 1 to i
- seq i j prints the numbers from i to j, incrementing by 1
- seq i j k prints the numbers from i to k, incrementing by j
I never remember that off the top of my head, because each command line usage has a different arrangement of the start, stop, and step values. But the benefit to knowing a little programming means you can create your own version of the command that makes sense for how you use it. So let’s do that.
A new loop
The DO loop in FORTRAN makes a lot of sense for a command to list numbers, but that’s probably a function of having learned FORTRAN during my undergrad days. The DO loop is just a for loop in any other programming language, but the usage looks like this:
DO start, stop, step
That’s also an easy-to-remember command line usage to create a list of numbers. I call my version of the program num, because it prints numbers. The usage looks like this:
num [ start [ stop [ step ]]]
The default case will count from 1 to 10, incrementing by 1. Or if I write num 5, it should print from 5 to 10. If I give it num 1 5, it should print 1 to 5. And if I run it like num 10 0 -1, it should count down from 10 to zero.
A sample implementation
This command line usage turns out to be really easy to implement as a program. Because the program always knows the first option is the start value, the second is always the end, and the third is always the step, the program just evaluates the command line count for each, then uses a function to turn the command line string into a number:
if (argc > 1) {
start = atoi(argv[1]);
if (argc > 2) {
stop = atoi(argv[2]);
if (argc > 3) {
step = atoi(argv[3]);
if (argc > 4) {
fputs("usage: num [ start [ stop [ step ]]]\n", stderr);
fputs("extra options ignored\n", stderr);
}
}
}
}
This block only collects the command line arguments and turns them into start, stop and step values. Now, we’re ready to print the list of numbers. This is just a for loop, with an additional check for counting up or counting down, and if the bounds match the increment:
if ((start < stop) && (step > 0)) {
for (int i = start; i <= stop; i += step) {
printf("%d\n", i);
}
}
else if ((stop < start) && (step < 0)) {
for (int i = start; i >= stop; i += step) {
printf("%d\n", i);
}
}
We actually can make this program slightly smaller by incrementing the start variable, instead of a new counter variable. But the program isn’t that big to start with, and an extra integer variable makes the code easier to read without that much overhead. So let’s keep it simple and use this version.
And that’s all we need to write our own program that prints a list of numbers. The two if statements at the end neatly ensure that the program can only count up if the bounds are correct and the step is positive, or count down if the step is negative. The program prints nothing otherwise, including if the user provided a zero step size.
The full listing looks like this:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int start = 1, stop = 10, step = 1;
if (argc > 1) {
start = atoi(argv[1]);
if (argc > 2) {
stop = atoi(argv[2]);
if (argc > 3) {
step = atoi(argv[3]);
if (argc > 4) {
fputs("usage: num [ start [ stop [ step ]]]\n", stderr);
fputs("extra options ignored\n", stderr);
}
}
}
}
if ((start < stop) && (step > 0)) {
for (int i = start; i <= stop; i += step) {
printf("%d\n", i);
}
}
else if ((stop < start) && (step < 0)) {
for (int i = start; i >= stop; i += step) {
printf("%d\n", i);
}
}
return 0;
}
Testing the program
Let’s try a few examples to see how the new program works. First, these commands should work as expected because they count up or count down within a range:
Count from 5 to 10:
$ ./num 5
5
6
7
8
9
10
Count from 10 to 15:
$ ./num 10 15
10
11
12
13
14
15
Count from 2 to 10, by 2:
$ ./num 2 10 2
2
4
6
8
10
Count down from 10 to 5:
$ ./num 10 5 -1
10
9
8
7
6
5
If the step is too large, not every number will be printed, and that’s okay:
$ ./num 1 10 100
1
Valid start, stop and end values with leftover options also work, although the extra arguments have no effect and just print a warning:
$ ./num -1 1 1 x y z
usage: num [ start [ stop [ step ]]]
extra options ignored
-1
0
1
However, we would expect out of range command lines to fail, because they are impossible to complete. The program prints nothing and immediately exits:
Counting up from 10 to 5? Not possible.
$ ./num 10 5
Counting down from 5 to 10? Nothing to do.
$ ./num 5 10 -1
A zero increment? That won’t work either.
$ ./num 1 10 0
Roll your own
Open source gives you the ability to change a system to meet your personal needs. And with a little programming, you have the power to make your own tools to replace or augment the commands on your system. This is a fairly straightforward example, but it’s a real-life case of where I’ve created a small command line tool to help me do things the way I want. That’s why I love working with open source systems, especially those that give you compilers and other resources to build new tools.