array for the call mine -c 10 2.0.

Team-FLY

2.6 Argument Arrays

A command line consists of tokens (the arguments) that are separated by white space: blanks, tabs or a backslash ( \ ) at the end of a line. Each token is a string of characters containing no white space unless quotation marks are used to group tokens. When a user enters a command line corresponding to a C executable program, the shell parses the command line into tokens and passes the result to the program in the form of an argument array. An argument array is an array of pointers to strings. The end of the array is marked by an entry containing a NULL pointer. Argument arrays are also useful for handling a variable number of arguments in calls to execvp and for handling environment variables . (Refer to Section 3.5 for an example of their application.)

Example 2.11

The following command line contains the four tokens: mine , -c , 10 and 2.0 .

 mine -c 10 2.0 

The first token on a command line is the name of the command or executable. Figure 2.2 shows the argument array for the command line of Example 2.11.

Figure 2.2. The argv array for the call mine -c 10 2.0 .

graphics/02fig02.gif

Example 2.12

The mine program of Example 2.11 might start with the following line.

 int main(int argc, char *argv[]) 

In Example 2.12, the argc parameter contains the number of command-line tokens or arguments (four for Example 2.11), and argv is an array of pointers to the command-line tokens. The argv is an example of an argument array.

2.6.1 Creating an argument array with makeargv

This section develops a function, makeargv , that creates an argument array from a string of tokens. The makeargv function illustrates some complications introduced by static variables. We use this function in several projects and exercises of subsequent chapters.

Example 2.13

Here is a prototype for a makeargv function that creates an argument array from a string of tokens.

 char **makeargv(char *s); 

The makeargv of Example 2.13 has a string input parameter and returns a pointer to an argv array. If the call fails, makeargv returns a NULL pointer.

Example 2.14

The following code segment illustrates how the makeargv function of Example 2.13 might be invoked.

 int i; char **myargv; char mytest[] = "This is a test"; if ((myargv = makeargv(mytest)) == NULL)    fprintf(stderr, "Failed to construct an argument array\n"); else    for (i = 0; myargv[i] != NULL; i++)       printf("%d:%s\n", i, myargv[i]); 
Example 2.15

The following alternative prototype specifies that makeargv should pass the argument array as a parameter. This alternative version of makeargv returns an integer giving the number of tokens in the input string. In this case, makeargv returns 1 to indicate an error.

 int makeargv(char *s, char ***argvp); 
Example 2.16

The following code segment calls the makeargv function defined in Example 2.15.

 int i; char **myargv; char mytest[] = "This is a test"; int numtokens; if ((numtokens = makeargv(mytest, &myargv)) == -1)    fprintf(stderr, "Failed to construct an argument array\n"); else    for (i = 0; i < numtokens; i++)        printf("%d:%s\n", i, myargv[i]); 

Because C uses call- by-value parameter passing, Example 2.15 shows one more level of indirection ( * ) when the address of myargv is passed. A more general version of makeargv allows an extra parameter that represents the set of delimiters to use in parsing the string.

Example 2.17

The following prototype shows a makeargv function that has a delimiter set parameter.

 int makeargv(const char *s, const char *delimiters, char ***argvp); 

The const qualifier means that the function does not modify the memory pointed to by the first two parameters.

Program 2.1 calls the makeargv function of Example 2.17 to create an argument array from a string passed on the command line. The program checks that it has exactly one command-line argument and outputs a usage message if that is not the case. The main program returns 1 if it fails, and 0 if it completes successfully. The call to makeargv uses blank and tab as delimiters. The shell also uses the same delimiters, so be sure to enclose the command-line arguments in double quotes as shown in Example 2.18.

Example 2.18

If the executable for Program 2.1 is called argtest , the following command creates and prints an argument array for This is a test .

 argtest "This is a test" 
Program 2.1 argtest.c

A program that takes a single string as its command-line argument and calls makeargv to create an argument array .

 #include <stdio.h> #include <stdlib.h> int makeargv(const char *s, const char *delimiters, char ***argvp); int main(int argc, char *argv[]) {    char delim[] = " \t";    int i;    char **myargv;    int numtokens;    if (argc != 2) {       fprintf(stderr, "Usage: %s string\n", argv[0]);       return 1;    }    if ((numtokens = makeargv(argv[1], delim, &myargv)) == -1) {       fprintf(stderr, "Failed to construct an argument array for %s\n", argv[1]);       return 1;    }    printf("The argument array contains:\n");    for (i = 0; i < numtokens; i++)       printf("%d:%s\n", i, myargv[i]);    return 0; } 

2.6.2 Implementation of makeargv

This section develops an implementation of makeargv based on the prototype of Example 2.17 as follows .

 int makeargv(const char *s, const char *delimiters, char ***argvp); 

The makeargv function creates an argument array pointed to by argvp from the string s , using the delimiters specified by delimiters . If successful, makeargv returns the number of tokens. If unsuccessful , makeargv returns 1 and sets errno .

The const qualifiers on s and delimiters show that makeargv does not modify either s or delimiters . The implementation does not make any a priori assumptions about the length of s or of delimiters . The function also releases all memory that it dynamically allocates except for the actual returned array, so makeargv can be called multiple times without causing a memory leak.

In writing general library programs, you should avoid imposing unnecessary a priori limitations on sizes (e.g., by using buffers of predefined size ). Although the system-defined constant MAX_CANON is a reasonable buffer size for handling command-line arguments, the makeargv function might be called to make an environment list or to parse an arbitrary command string read from a file. This implementation of makeargv allocates all buffers dynamically by calling malloc and uses the C library function strtok to split off individual tokens. To preserve the input string s , makeargv does not apply strtok directly to s . Instead, it creates a scratch area of the same size pointed to by t and copies s into it. The overall implementation strategy is as follows.

  1. Use malloc to allocate a buffer t for parsing the string in place. The t buffer must be large enough to contain s and its terminating `\0' .

  2. Copy s into t . Figure 2.3 shows the result for the string "mine -c 10 2.0" .

    Figure 2.3. The makeargv makes a working copy of the string s in the buffer t to avoid modifying that input parameter.

    graphics/02fig03.gif

  3. Make a pass through the string t , using strtok to count the tokens.

  4. Use the count ( numtokens ) to allocate an argv array.

  5. Copy s into t again.

  6. Use strtok to obtain pointers to the individual tokens, modifying t and effectively parsing t in place. Figure 2.4 shows the method for parsing the tokens in place.

    Figure 2.4. The makeargv parses the tokens in place by using strtok .

    graphics/02fig04.gif

The implementation of makeargv discussed here uses the C library function strtok to split a string into tokens. The first call to strtok is different from subsequent calls. On the first call, pass the address of the string to parse as the first argument, s1 . On subsequent calls for parsing the same string, pass a NULL for s1 . The second argument to strtok , s2 , is a string of allowed token delimiters.

  SYNOPSIS  #include <string.h>    char *strtok(char *restrict s1, const char *restrict s2);  POSIX:CX  

Each successive call to strtok returns the start of the next token and inserts a '\0' at the end of the token being returned. The strtok function returns NULL when it reaches the end of s1 .

It is important to understand that strtok does not allocate new space for the tokens, but rather it tokenizes s1 in place. Thus, if you need to access the original s1 after calling strtok , you should pass a copy of the string.

The restrict qualifier on the two parameters requires that any object referenced by s1 in this function cannot also be accessed by s2 . That is, the tail end of the string being parsed cannot be used to contain the delimiters. This restriction, one that would normally be satisfied in any conceivable application, allows the compiler to perform optimizations on the code for strtok . The const qualifier on the second parameter indicates that the strtok function does not modify the delimiter string.

Program 2.2 shows an implementation of makeargv . Since strtok allows the caller to specify which delimiters to use for separating tokens, the implementation includes a delimiters string as a parameter. The program begins by using strspn to skip over leading delimiters. This ensures that **argvp , which points to the first token, also points to the start of the scratch buffer, called t in the program. If an error occurs, this scratch buffer is explicitly freed. Otherwise, the calling program can free this buffer. The call to free may not be important for most programs, but if makeargv is called frequently from a shell or a long-running communication program, the unfreed space from failed calls to makeargv can accumulate. When using malloc or a related call, analyze whether to free the memory if an error occurs or when the function returns .

Program 2.2 makeargv.c

An implementation of makeargv .

 #include <errno.h> #include <stdlib.h> #include <string.h> int makeargv(const char *s, const char *delimiters, char ***argvp) {    int error;    int i;    int numtokens;    const char *snew;    char *t;    if ((s == NULL)  (delimiters == NULL)  (argvp == NULL)) {       errno = EINVAL;       return -1;    }    *argvp = NULL;    snew = s + strspn(s, delimiters);         /* snew is real start of string */    if ((t = malloc(strlen(snew) + 1)) == NULL)       return -1;    strcpy(t, snew);    numtokens = 0;    if (strtok(t, delimiters) != NULL)     /* count the number of tokens in s */       for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;                              /* create argument array for ptrs to the tokens */    if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {       error = errno;       free(t);       errno = error;       return -1;    }                         /* insert pointers to tokens into the argument array */    if (numtokens == 0)       free(t);    else {       strcpy(t, snew);       **argvp = strtok(t, delimiters);       for (i = 1; i < numtokens; i++)           *((*argvp) + i) = strtok(NULL, delimiters);     }     *((*argvp) + numtokens) = NULL;             /* put in final NULL pointer */     return numtokens; } 
Example 2.19 freemakeargv.c

The following function frees all the memory associated with an argument array that was allocated by makeargv . If the first entry in the array is not NULL , freeing the entry also frees the memory allocated for all the strings. The argument array is freed next. Notice that it would be incorrect to free the argument array and then access the first entry.

 #include <stdlib.h> void freemakeargv(char **argv) {    if (argv == NULL)       return;    if (*argv != NULL)       free(*argv);    free(argv); } 
Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net