The Joys of Iteration

Suppose that you’re testing code and need to run a program six times in a row. You could keep repeating the program, running it multiple times, but why not write a utility that does the same thing? That would be a good and practical way to put the system() function to work.

The other day, I was testing some code and needed to run the program dozens of times. The way most terminal users accomplish that task is to press the up arrow key to fetch the preceding command:

Up-Enter. Up-Enter. Up-Enter. And so on.

I have to do this chore often enough, so I wrote a command line utility to handle the task. I was going to call the utility repeat, and that makes a lot of sense, but the word iterate is more programmish, so the utility is named iterate.

I’ve not seen a repeat/iterate command in any operating system. Generally speaking, you code the shell to repeat a command. The shell scripting command for is frequently used, which explains why operating systems don’t come with a repeat/iterate command. That shouldn’t stop you from coding your own utility just to make it easier.

The format for my iterate utility is:

iterate n command...

The value n is required. It ranges from 1 to whatever and indicates how many times the command repeats.

command is the command to repeat, which need not be enclosed in double quotes. Because of that, and due to the way a C program handles command line arguments, the code must stitch together the command string from multiple command line arguments.

To carry out the command, the code uses the system() function:

/*
   Iterate - repeat a given command
   Dan Gookin, May 19, 2015
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFSIZE 1024

void usage(void)
{
    puts("Usage:\n");
    puts("  iterate n command\n");
    puts("n       - repeat count, must be specified.");
    puts("          Range is 1 to n, where `n' is some");
    puts("          really big number.");
    puts("command - program name or command. Need");
    puts("          not be enclosed in quotes.");
}

int main(int argc, char *argv[])
{
    int repeat_count,buffer_size,x;
    char *command_buffer;

    /* Confirm proper number of arguments */
    if( argc <= 2)
    {
        usage();
        return(1);
    }

    /* Obtain iteration value */
    repeat_count = atoi(argv[1]);
    if( repeat_count < 1)
    {
        usage();
        return(1);
    }

    /* Allocate storage for command line */
    command_buffer = (char *)malloc( sizeof(char) * BUFSIZE);
    if( command_buffer == NULL)
    {
        puts("iterate: Some kind of memory allocation error.");
        return(2);
    }

    /* de-parse the command string */
    *command_buffer = '\0';
    buffer_size = 0;
    for(x=2;x<argc;x++)
    {
        strcat(command_buffer,argv[x]);
        strcat(command_buffer," ");
            /* Track buffer size, plus trailing space */
        buffer_size += strlen(argv[x])+1;
            /* Check for  buffer overflow */
        if(buffer_size > BUFSIZE)
        {
            puts("iterate: Buffer overflow.");
            return(2);
        }
    }
        /* Eliminate last trailing space */
    buffer_size--;
    *(command_buffer+buffer_size) = '\0';

    /* Execute command repeat_count times */
    for(x=0;x<repeat_count;x++)
        system(command_buffer);

    return(0);
}

This code uses some of the command line parsing tricks covered in earlier Lessons. At least two arguments are required, which is tested for at Line 29. Further, the n argument must be greater than one, which is tested for at Line 37.

The command passed to the code might be a single argument or it could be multiple items that need to be assembled. That concatenation takes place starting at Line 54. After the buffer is initialized (Line 52), the strcat() function (Line 56). moves command line arguments into the buffer one after the other. Each argument is appended by a space (Line 58). The buffer size is tracked to prevent overflow (Lines 59 and 61).

After the command line string is assembled, the final trailing space is removed at Lines 68 and 69. This step isn’t really necessary, but I’m a neat-and-tidy person.

The repeating action happens at Lines 72 and 73. The system() function passes the command_buffer‘s text to the operating system repeat_count number of times.

This code represents my first version of the iterate utility. For later versions I added an option to separate output by adding spaces or other characters, plus an option that injects a pause between each iteration. That option is necessary when using random number generation based on the system clock. Feel free to add those switches on your own.

Leave a Reply