Day 6: Command-Line Processing and the Environment

The user wants to be able to invoke ROBIN with various command-line switches to control its initial state. The user also wants to be able to dictate how ROBIN generally responds, and where it creates and maintains its files. Today you will learn

Command-Line Processing

Many operating systems, such as DOS and UNIX, enable the user to pass parameters to your program when the program starts. These are called command-line arguments, and typically are separated by spaces on the command line, as in the following example:

    SomeProgram Param1 Param2 Param3

These parameters are not passed to main() directly. Instead, every program's main() function is passed three parameters. The first parameter is an integer count of the number of arguments on the command line. The program name itself is counted, so every program has at least one parameter. The example command line shown here has four parameters. (The name SomeProgram plus the three parameters makes a total of four command-line arguments.)

The second parameter passed to main() is an array of pointers to character strings. Because an array name is a constant pointer to the first element of the array, you can declare this argument to be a pointer to a pointer to char, or a pointer to an array of char.

The third argument also is an array of pointers to char, but this contains the system's environment. In DOS and UNIX, the environment typically includes the PATH statement and a variety of other defined values. Every environment variable, however, is a string. If you write SET MYVAR = 5 in your AUTOEXEC.BAT (in DOS), you define a variable named MYVAR and assign to it the string "5".

Typically, the first argument is called argc (argument count), but you can call it anything you want. The second argument often is called argv (argument vector) but, again, this is just a convention. The third argument often is called env.

It is legal to define main() to take any number of arguments from zero through three. If you declare main() to take no arguments, of course, none of these values will be legal. As you declare each of these arguments, their values become available, but they must include all the arguments to the left. The legal declarations of main's arguments follow:

    int main()
    int main(int argc)
    int main(int argc, char** argv)
    int main(int argc, char** argv, char** env)

You cannot try to declare the char** without also declaring the int, for example.

It is common to test argc to ensure that you have received the expected number of arguments, and to use argv to access the arguments themselves. Note that argv[0] is the name of the program, and argv[1] is the first parameter to the program, represented as a string. If your program takes two numbers as arguments, you will need to translate these numbers to strings. Listing 6.1 illustrates how to use the command-line arguments.

Listing 6.1 Using Command-Line Arguments

    1:     #include <iostream.h>
    2:       void main(int argc, char **argv, char ** env)
    3:       {
    4:          cout << "Received " << argc << " arguments...\n";
    5:          for (int i=0; i<argc; i++)
    6:             cout << "argument " << i << ": " << argv[i] << endl;
    7:          for (int j = 0; env[j]!=0; j++)
    8:           cout << "env var: " << env[j] << endl;
    9:       }

Output:

    d:\bc4\book2\exe>0601 Teach Yourself More C++ In 21 Days
    Received 8 arguments...
    argument 0: D:0601.EXE
    argument 1: Teach
    argument 2: Yourself
    argument 3: More
    argument 4: C++
    argument 5: In
    argument 6: 21
    argument 7: Days
    env var: CONFIG=Normal
    env var: PROMPT=$p$g
    env var: TEMP=c:\temp
    env var: TMP=c:\temp

Analysis:

The function main() declares three arguments: argc is an integer that contains the count of command-line arguments; argv is a pointer to the array of strings; and env is also a pointer to an array of strings, each of which holds an environmental variable.

Each string in the array pointed to by argv is a command-line argument. Note that argv just as easily could have been declared as char *argv[]. It is a matter of programming style how you declare argv and env; even though this program declared it as a pointer to a pointer, array offsets still were used to access the individual strings. Even the names argc, argv, and env are arbitrary; the program will work just as well if you call them Sleepy, Sneezy, and Doc.

In line 4, argc is used to print the number of command-line arguments: eight in all, counting the program name itself.

In lines 5 and 6, each of the command-line arguments is printed, passing the NULL-terminated strings to cout by indexing into the array of strings. Similarly, in lines 7 and 8, the environment variables are accessed. Note that the output has been modified to remove a number of environment variables on my computer. Run the program and print your own environment; you might be surprised at what you find.

DO and DON'T

DO use the names argc, argv, and env.

DON'T forget that argv is indexed from 0 to argc-1.

DO remember that argv[0] is the name of the program.

DO treat argv as an array of pointers to C-style strings.

Command-Line Flags

ROBIN expects the first argument other than the name of the program (argv[1]) to contain a flag. You can process that flag with a switch statement. Because the protocol for ROBIN dictates that only the first letter of the flag is significant, the code for switching on the flag is straightforward, as shown in listing 6.2.

Listing 6.2 Switching on Command-Line Flags

    1:     // Listing 6.2 - Switching on Command-Line Flags
    2:
    3:     #include <iostream.h>
    4:
    5:     // function prototypes
    6:     void Process(int argc, char ** argv);
    7:     int main(int argc, char **argv, char ** env);
    8:
    9:     // driver program
    10:    int main(int argc, char **argv, char ** env)
    11:    {
    12:       // rudimentary usage testing
    13:       if (argc < 3 || argv[1][0] != '-')
    14:       {
    15:          cout << "Usage: " << argv[0] << " -Flag [arguments]" << endl;
    16:          return 1;
    17:       }
    18:       Process (argc, argv);
    19:       return 0;
    20:    }
    21:
    22:    // switch on command line arguments
    23:    void Process(int argc, char ** argv)
    24:    {
    25:       switch (argv[1][1])
    26:       {
    27:          case 'I':
    28:          case 'i':
    29:             cout << "Index following text" << endl;
    30:             break;
    31:
    32:          case 'F':
    33:          case 'f':
    34:             cout << "Index from a File" << endl;
    35:             break;
    36:
    37:          case 'L':
    38:          case 'l':
    39:             cout << "Make a note for each line in a file" << endl;
    40:             break;
    41:
    42:          case 'S':
    43:          case 's':
    44:          case '?':
    45:             cout << "Search for following text" << endl;
    46:             break;
    47:          }
    48:    }

Output:

    d:\>0602 -I this and that
    Index following text

    d:\>0602 -S this and that
    Search for following text

    d:\>0602 F this and that
    Usage: d:\>0602.EXE -Flag [arguments]

Analysis:

In line 13, the number of arguments is checked. This program expects at least two (the name of the program and the flags, and at least one more). If there are fewer than three arguments, or if the first character of the second argument (the flag) isn't a dash, a usage statement is printed in line 15, and the program exits in line 16.

Assuming that the program passes this rudimentary usage testing, control is passed to the Process() function, and argc and argv are passed along.

The tests and actions illustrated in this function are a skeleton of what the actual program will do. Note that the text of the full word is not checked; the user might have entered 0602 -Fail and this program would have treated it exactly as if the user had written 0602 -File.

An Alternative to Flags

This program switches on one of a very limited set of flags. Exactly the same effect could be accomplished by copying the executable to a set of new names and then switching on argv[0]. In DOS, this is wasteful of disk space. In UNIX, however, links can be used (ln), thereby providing the same functionality with virtually no wasted disk space. Listing 6.3 illustrates this idea.

Listing 6.3 Using the Name of the File

    1:     // Listing 6.3 - Using the Name of the File
    2:
    3:     #include <iostream.h>
    4:     #include <string.h>
    5:
    6:     // function prototypes
    7:     void Process(char **argv);
    8:     int main(int argc, char **argv, char ** env);
    9:
    10:    // driver program
    11:    int main(int argc, char **argv, char ** env)
    12:    {
    13:       // rudimentary usage testing
    14:       if (argc < 2 )
    15:       {
    16:          cout << "Usage: " << argv[0] << " [arguments]" << endl;
    17:          return 1;
    18:       }
    19:       Process (argv);
    20:       return 0;
    21:    }
    22:
    23:    // switch on command line arguments
    24:    void Process(char ** argv)
    25:    {
    26:       // find the name without the path
    27:       int len = strlen(argv[0]);
    28:       for (char *ptr = &argv[0][len-1]; *(ptr-1) != '\\';ptr-- );
    29:
    30:       if (strcmp(ptr,"INDEX.EXE") == 0)
    31:          cout << "Index following text" << endl;
    32:
    33:       if (strcmp(ptr,"FILE.EXE") == 0)
    34:          cout << "Index from a File" << endl;
    35:
    36:       if (strcmp(ptr,"LIST.EXE") == 0)
    37:          cout << "Make a note for each line in a file" << endl;
    38:
    39:       if (strcmp(ptr,"FIND.EXE") == 0)
    40:          cout << "Search for following text" << endl;
    41:    }

Output:

    d:\>copy 0603.exe index.exe
    d:\>copy 0603.exe find.exe

    d:\>Index don't forget to rename the file
    Index following text

    d:\>find the text you saved
    Search for following text

Note: Your compiler may warn you that the env parameter to main() is never used in this program and many of the subsequent listings.

Analysis:

Listing 6.3 is similar to listing 6.2. This time, however, process() is sent only the value of argv. In fact, you might want to send in only argv[0], because that is the only argument that Process() really needs to consider.

In line 28, a pointer is set to the end of the string held in argv[0]. The pointer ticks backward, searching for the backslash character that DOS puts at the beginning of each directory. This takes a string such as D:\PROGRAMS\TYMCPP\DAY6\FIND.EXE and turns it into FIND.EXE. In lines 30, 33, 36, and 39, this string then is compared with the expected names for the program; when a match is found, the correct function will be called.

Although ROBIN does not take advantage of this technique, many other utilities do. There are a set of UNIX utilities that really are one program with a number of different names. The program "does the right thing," depending on how you call it.

Note: The specific processing of the file name as shown in line 28 is appropriate for DOS programs. If you are programming in a different environment, you may need to modify this line slightly. In UNIX, for example, the slash goes the other way (/).

Sorting Command-Line Arguments

It is convenient that DOS (and other operating systems) present the command line as an array of pointers to C-style strings. It therefore is easy to adapt the QuickSort() routine from day 4 to sort the command-line arguments. Listing 6.4 extends listing 6.2 to include the capability to sort the command line (except the file name and the flag) when the -I flag is used.

Note: This program requires inclusion of string.hpp that you wrote earlier, and that you compile in string.cpp as a module of this program.

Listing 6.4 Sorting the Arguments

    1:     // Listing 6.4 - Sorting the Arguments
    2:
    3:     #include <iostream.h>
    4:     #include <string.h>
    5:     #include "string.hpp"
    6:
    7:     // function prototypes
    8:     void QuickSort(String**, int, int);
    9:     void Swap(String*& i, String*& j);
    10:    int main(int argc, char **argv, char ** env);
    11:    void Process(int argc, char ** argv);
    12:
    13:    // driver program
    14:    int main(int argc, char **argv, char ** env)
    15:    {
    16:       // rudimentary usage testing
    17:       if (argc < 3 || argv[1][0] != '-')
    18:       {
    19:          cout << "Usage: " << argv[0] << " -Flag [arguments]" << endl;
    20:          return 1;
    21:       }
    22:       Process (argc, argv);
    23:       return 0;
    24:    }
    25:
    26:    // switch on command line arguments
    27:    void Process(int argc, char ** argv)
    28:    {
    29:       String *buffer[100];
    30:       int i;
    31:       switch (argv[1][1])
    32:       {
    33:
    34:          case 'I':
    35:          case 'i':
    36:                      for (i=0; i<100 && i < argc-2; i++)
    37:                         buffer[i] = new String(argv[i+2]);
    38:                      QuickSort(buffer,0,argc-2);
    39:                      for (i = 0; i < argc-3; i++)
    40:                         cout << *buffer[i] << ", ";
    41:                      cout << *buffer[argc-3] << endl;
    42:             break;
    43:
    44:          case 'F':
    45:          case 'f':
    46:             cout << "Index from a File" << endl;
    47:             break;
    48:
    49:          case 'L':
    50:          case 'l':
    51:             cout << "Make a note for each line in a file" << endl;
    52:             break;
    53:
    54:          case 'S':
    55:          case 's':
    56:          case '?':
    57:             cout << "Search for following text" << endl;
    58:             break;
    59:          }
    60:    }
    61:
    62:    // QuickSort implemented with Median of Three
    63:    void QuickSort(String** Input, int left, int right)
    64:    {
    65:       if (right > left)
    66:       {
    67:          int i = left-1;
    68:          int x = right;
    69:          for (;;)
    70:          {
    71:             while (*Input[++i] < *Input[right-1])
    72:             ;
    73:             while (*Input[--x] > *Input[right-1])
    74:             ;
    75:
    76:             if (i >= x)
    77:                break;
    78:             Swap(Input[i], Input[x]);
    79:          }
    80:          Swap(Input[i], Input[right-1]);
    81:          QuickSort(Input,left,i);
    82:          QuickSort(Input,i+1,right);
    83:       }
    84:    }
    85:
    86:    void Swap(String*& i, String*& j)
    87:    {
    88:       String* temp;
    89:       temp = j;
    90:       j = i;
    91:       i = temp;
    92:    }

Output:

    d:\>0604 -I Eternal Vigilance Is The Price Of Liberty
    Eternal, Is, Liberty, Of, Price, The, Vigilance

Analysis:

Listing 6.4 sorts the command-line arguments. It does so by creating an array of strings one for each command-line argument other than the name of the program and the -I switch, and then passing the array of strings to QuickSort().

In line 37, the array is created; in line 38, it is passed to QuickSort(). One terrible flaw in this approach is that the array must be of fixed size, as shown in line 29. This is wasteful of memory and limits your program to the number of parameters specified as the upper limit of the array.

Note, by the way, that the buffer and int must be declared in lines 29 and 30outside of the switch statement itself. It is tempting to declare them in line 36, but a case statement does not have scope and it is an error to declare variables there unless you use braces to create a scope. Lines 29 through 42 could be rewritten:

    29:       switch (argv[1][1])
    30:       {
    31:
    32:          case 'I':
    33:          case 'i':
    34                     {
    25:                 String *buffer[100];
    36:                      for (int i=0; i<100 && i < argc-2; i++)
    37:                         buffer[i] = new String(argv[i+2]);
    38:                      QuickSort(buffer,0,argc-2);
    39:                      for (i = 0; i < argc-3; i++)
    40:                         cout << *buffer[i] << ", ";
    41:                      cout << *buffer[argc-3] << endl;
    42:                     }
    43:             break;

This code would have worked just as well.

Command-Line Arguments and Linked Lists

As noted in the preceding section, listing 6.4 suffers the fatal flaw that it must declare a fixed-size buffer of String pointers. This problem is rectified easily by creating a dynamic array of strings, implemented using an unsorted linked list, as shown in listing 6.5. Note that even this program should be modified to flesh out the Array class and to parameterize it.

Listing 6.5 Sorting a List of Command-Line Arguments

    1:     // Listing 6.5 - Sorting a List of Command-Line Arguments
    2:
    3:     #include <iostream.h>
    4:     #include <string.h>
    5:     #include "string.hpp"
    6:     typedef unsigned long ULONG;
    7:
    8:      // **************** String Node class ************
    9:      class Node
    10:     {
    11:     public:
    12:        Node (String*);
    13:        Node ();
    14:        ~Node();
    15:        void InsertAfter(Node*);
    16:        Node * GetNext() { return itsNext; }
    17:        const Node *  GetNext() const { return itsNext; }
    17:        void SetNext(Node * next) { itsNext = next; }
    18:        String* GetString() const;
    19:        String*& GetString();
    20:
    21:     private:
    22:        String *itsString;
    23:        Node * itsNext;
    24:     };
    25:
    26:
    27:     // **************** String List ************
    28:     class StringList
    29:     {
    30:     public:
    31:        StringList();
    32:        ~StringList();
    33:        ULONG   GetCount() const { return itsCount; }
    34:        void    Insert(String *);
    35:        void    Iterate(void (String::*f)()const) ;
    36:        String*   operator[](ULONG) const ;
    37:        String*&   operator[](ULONG) ;
    38:
    39:     private:
    40:        Node  itsHead;
    41:        ULONG itsCount;
    42:    };
    43:
    44:    // *** node implementations ****
    45:
    46:      Node::Node(String* pString):
    47:      itsString(pString),
    48:      itsNext(0)
    49:      {}
    50:
    51:      Node::Node():
    52:      itsString(0),
    53:      itsNext(0)
    54:      {}
    55:
    56:
    57:      Node::~Node()
    58:      {
    59:         delete itsString;
    60:         itsString = 0;
    61:         delete itsNext;
    62:         itsNext = 0;
    63:      }
    64:
    65:      void Node::InsertAfter(Node* newNode)
    66:      {
    67:       newNode->SetNext(itsNext);
    68:       itsNext=newNode;
    69:      }
    70:
    71:      String * Node::GetString() const
    72:      {
    73:         if (itsString)
    74:            return itsString;
    75:         else
    76:            return NULL; //error
    77:      }
    78:
    79:       String*& Node::GetString()
    80:      {
    81:            return itsString;
    82:      }
    83:
    84:       // Implementations for Lists...
    85:
    86:      StringList::StringList():
    87:         itsCount(0),
    88:         itsHead(0)  // initialize head node to have no String
    89:         {}
    90:
    91:      StringList::~StringList()
    92:      {
    93:      }
    94:
    95:      String *  StringList::operator[](ULONG offSet) const
    96:      {
    97:         const Node* pNode = itsHead.GetNext();
    98:
    99:         if (offSet+1 > itsCount)
    100:           return NULL; // error
    101:
    102:        for (ULONG i=0;i<offSet; i++)
    103:           pNode = pNode->GetNext();
    104:
    105:       return   pNode->GetString();
    106:     }
    107:
    108:    String*&  StringList::operator[](ULONG offSet)
    109:     {
    110:
    111:        if (offSet+1 > itsCount)
    112:        {
    113:            Node* NewNode = new Node;
    114:            for (Node *pNode = &itsHead;;pNode = pNode->GetNext())
    115:            {
    116:               if (pNode->GetNext() == NULL ) //|| *(pNode->GetNext()) <
                       *NewNode)
    117:               {
    118:                  pNode->InsertAfter(NewNode);
    119:                  itsCount++;
    120:                  return NewNode->GetString();
    121:               }
    122:            }
    123:        }
    124:        Node *pNode   = itsHead.GetNext();
    125:        for (ULONG i=0;i<offSet; i++)
    126:           pNode = pNode->GetNext();
    127:       return   pNode->GetString();
    128:     }
    129:
    130:     void StringList::Insert(String* pString)
    131:     {
    132:         Node * NewNode = new Node(pString);
    133:         itsCount++;
    134:         for (Node * pNode = &itsHead;;pNode = pNode->GetNext())
    135:         {
    136:            if (pNode->GetNext() == NULL ) //|| *(pNode->GetNext()) < *NewNode)
    137:            {
    138:               pNode->InsertAfter(NewNode);
    139:               break;
    140:            }
    141:         }
    142:     }
    143:
    144:    void StringList::Iterate(void (String::*func)()const)
    145:    {
    146:       for (Node* pNode = itsHead.GetNext();
    147:            pNode;
    148:            pNode=pNode->GetNext()
    149:            )
    150:             (pNode->GetString()->*func)();
    151:
    152:    }
    153:
    154:   // function prototypes
    155:   void QuickSort(StringList&, int, int);
    156:   void Swap(String*& i, String*& j);
    157:   int main(int argc, char **argv, char ** env);
    158:   void Process(int argc, char ** argv);
    159:
    160:   // driver program
    161:   int main(int argc, char **argv, char ** env)
    162:   {
    163:      // rudimentary usage testing
    164:      if (argc < 3 || argv[1][0] != '-')
    165:      {
    166:         cout << "Usage: " << argv[0] << " -Flag [arguments]" << endl;
    167:         return 1;
    168:      }
    169:      Process (argc, argv);
    170:      return 0;
    171:   }
    172:
    173:   // switch on command line arguments
    174:   void Process(int argc, char ** argv)
    175:   {
    176:      StringList buffer;
    177:      int i;
    178:      switch (argv[1][1])
    179:      {
    180:
    181:         case 'I':
    182:         case 'i':
    183:                     for (i=0; i<100 && i < argc-2; i++)
    184:                     buffer[i] = new String(argv[i+2]);
    185:                     QuickSort(buffer,0,argc-2);
    186:                     for (i = 0; i < argc-3; i++)
    187:                        cout << *buffer[i] << ", ";
    188:                     cout << *buffer[argc-3] << endl;
    189:            break;
    190:
    191:         case 'F':
    192:         case 'f':
    193:            cout << "Index from a File" << endl;
    194:            break;
    195:
    196:         case 'L':
    197:         case 'l':
    198:            cout << "Make a String for each line in a file" << endl;
    199:            break;
    200:
    201:         case 'S':
    202:         case 's':
    203:         case '?':
    204:            cout << "Search for following text" << endl;
    205:            break;
    206:         }
    207:   }
    208:
    209:   // QuickSort implemented with Median of Three
    210:   void QuickSort(StringList& Input, int left, int right)
    211:   {
    212:      if (right > left)
    213:      {
    214:         int i = left-1;
    215:         int x = right;
    216:         for (;;)
    217:         {
    218:               while (*Input[++i] < *Input[right-1])
    219:                ;
    220:               while (*Input[--x] > *Input[right-1])
    221:               ;
    222:
    223:            if (i >= x)
    224:               break;
    225:            Swap(Input[i], Input[x]);
    226:         }
    227:         Swap(Input[i], Input[right-1]);
    228:         QuickSort(Input,left,i);
    229:         QuickSort(Input,i+1,right);
    230:      }
    231:   }
    232:
    233:   void Swap(String*& i, String*& j)
    234:   {
    235:      String* temp;
    236:      temp = j;
    237:      j = i;
    238:      i = temp;
    239:   }

Output:

    d:\>0605 -I Eternal Vigilance Is The Price Of Liberty
    Eternal, Is, Liberty, Of, Price, The, Vigilance

Analysis:

In lines 8 through 24, a string node class is declared. Two constructors are offered, one takes a pointer to a string, and the other is the default constructor, which takes no parameters.

In lines 15 through 19, simple accessor and manipulation functions are provided; and in lines 22 and 23, the member variables are declared.

In lines 27 through 42, the StringList class is declared. A StringList is an unsorted linked list of String objects. The critical method is declared in line 37: the non-constant offset operator[] is declared to return a reference to a pointer to a string. This is what enables you to swap strings in QuickSort().

The implementations for the StringList are much like what you saw on day 3, except for the implementation of the non-constant offset operator[]. If the user requests access to an element that does not yet exist, a new node is created in line 118, and the count of nodes is incremented in line 119. In line 120, the new node's string pointer is returned by reference. This allows the user to write the line of code that appears in line 184:

    buffer[i] = new String(argv[i+2]);

This is a critical line of code, so examine it closely. A new String object is being created, initialized with a command-line argument. The pointer returned by new() is passed into the StringList using the offset operator[], but the offset requested does not yet exist in the array. A new node is created, which returns a reference to its pointer to String. That pointer then is set to point to the new string just created, so the command-line argument is put into the array.

The array is passed to QuickSort() in line 185, and then the results are printed in lines 186 through 188. QuickSort() operates as in previous examples, except that the array is in fact a StringList, passed into QuickSort() by reference to save memory. This cannot be a constant reference, of course, because QuickSort() changes the array.

Using the Environment Variables

The DOS operating system (and others as well) utilizes what is known as the environment. The environment consists of values stored by the operating system and available to your program. You can access these variables as an array of strings from your command line, much like the command arguments themselves. Listing 6.6 reworks listing 6.5 to present a sorted list of the environment variables.

Listing 6.6 Sorting the Environment Variables

    1:     //Listing 6.6 - Sorting the Environment Variables
    2:
    3:     #include <iostream.h>
    4:     #include <string.h>
    5:     #include "string.hpp"
    6:
    7:     typedef unsigned long ULONG;
    8:
    9:      // **************** String Node class ************
    10:     class Node
    11:     {
    12:     public:
    13:        Node (String*);
    14:        Node ();
    15:        ~Node();
    16:        void InsertAfter(Node*);
    17:        Node * GetNext() { return itsNext; }
    18:        const Node *  GetNext() const { return itsNext; }
    19:        void SetNext(Node * next) { itsNext = next; }
    20:        String* GetString() const;
    21:        String*& GetString();
    22:
    23:     private:
    24:        String *itsString;
    25:        Node * itsNext;
    26:     };
    27:
    28:     // **************** String List ************
    29:     class StringList
    30:     {
    31:     public:
    32:        StringList();
    33:        ~StringList();
    34:        ULONG   GetCount() const { return itsCount; }
    35:        void    Insert(String *);
    36:        void    Iterate(void (String::*f)()const) ;
    37:        String*   operator[](ULONG) const ;
    38:        String*&   operator[](ULONG) ;
    39:
    40:     private:
    41:        Node  itsHead;
    42:        ULONG itsCount;
    43:    };
    44:
    45:    // *** node implementations ****
    46:
    47:      Node::Node(String* pString):
    48:      itsString(pString),
    49:      itsNext(0)
    50:      {}
    51:
    52:      Node::Node():
    53:      itsString(0),
    54:      itsNext(0)
    55:      {}
    56:
    57:
    58:      Node::~Node()
    59:      {
    60:         delete itsString;
    61:         itsString = 0;
    62:         delete itsNext;
    63:         itsNext = 0;
    64:      }
    65:
    66:      void Node::InsertAfter(Node* newNode)
    67:      {
    68:       newNode->SetNext(itsNext);
    69:       itsNext=newNode;
    70:      }
    71:
    72:      String * Node::GetString() const
    73:      {
    74:         if (itsString)
    75:            return itsString;
    76:         else
    77:            return NULL; //error
    78:      }
    79:
    80:       String*& Node::GetString()
    81:      {
    82:            return itsString;
    83:      }
    84:
    85:       // Implementations for Lists...
    86:
    87:      StringList::StringList():
    88:         itsCount(0),
    89:         itsHead(0)  // initialize head node to have no String
    90:         {}
    91:
    92:      StringList::~StringList()
    93:      {
    94:      }
    95:
    96:      String *  StringList::operator[](ULONG offSet) const
    97:      {
    98:         const Node* pNode = itsHead.GetNext();
    99:
    100:        if (offSet+1 > itsCount)
    101:           return NULL; // error
    102:
    103:        for (ULONG i=0;i<offSet; i++)
    104:           pNode = pNode->GetNext();
    105:
    106:       return   pNode->GetString();
    107:     }
    108:
    109:    String*&  StringList::operator[](ULONG offSet)
    110:     {
    111:
    112:        if (offSet+1 > itsCount)
    113:        {
    114:            Node* NewNode = new Node;
    115:            for (Node *pNode = &itsHead;;pNode = pNode->GetNext())
    116:            {
    117:               if (pNode->GetNext() == NULL ) //|| *(pNode->GetNext()) <
                       *NewNode)
    118:               {
    119:                  pNode->InsertAfter(NewNode);
    120:                  itsCount++;
    121:                  return NewNode->GetString();
    122:               }
    123:            }
    124:        }
    125:        Node *pNode   = itsHead.GetNext();
    126:        for (ULONG i=0;i<offSet; i++)
    127:           pNode = pNode->GetNext();
    128:       return   pNode->GetString();
    129:     }
    130:
    131:     void StringList::Insert(String* pString)
    132:     {
    133:         Node * NewNode = new Node(pString);
    134:         itsCount++;
    135:         for (Node * pNode = &itsHead;;pNode = pNode->GetNext())
    136:         {
    137:            if (pNode->GetNext() == NULL ) //|| *(pNode->GetNext()) < *NewNode)
    138:            {
    139:               pNode->InsertAfter(NewNode);
    140:               break;
    141:            }
    142:         }
    143:     }
    144:
    145:    void StringList::Iterate(void (String::*func)()const)
    146:    {
    147:       for (Node* pNode = itsHead.GetNext();
    148:            pNode;
    149:            pNode=pNode->GetNext()
    150:            )
    151:             (pNode->GetString()->*func)();
    152:
    153:    }
    154:
    155:   // function prototypes
    156:   void QuickSort(StringList&, int, int);
    157:   void Swap(String*& i, String*& j);
    158:   int main(int argc, char **argv, char ** env);
    159:
    160:
    161:   // driver program
    162:   int main(int argc, char **argv, char ** env)
    163:   {
    164:      StringList buffer;
    165:      for (int i = 0; env[i] != NULL; i++)
    166:         buffer[i] = new String(env[i]);
    167:      int NumEnv = i;
    168:
    169:      QuickSort(buffer,0,NumEnv);
    170:
    171:      for (i = 0; i < NumEnv-1; i++)
    172:         cout << *buffer[i] << "\n";
    173:      cout << *buffer[NumEnv-1] << endl;
    174:
    175:      return 0;
    176:   }
    177:
    178:   void QuickSort(StringList& Input, int left, int right)
    179:   {
    180:      if (right > left)
    181:      {
    182:         int i = left-1;
    183:         int x = right;
    184:         for (;;)
    185:         {
    186:               while (*Input[++i] < *Input[right-1])
    187:                ;
    188:               while (*Input[--x] > *Input[right-1])
    189:               ;
    190:
    191:            if (i >= x)
    192:               break;
    193:            Swap(Input[i], Input[x]);
    194:         }
    195:         Swap(Input[i], Input[right-1]);
    196:         QuickSort(Input,left,i);
    197:         QuickSort(Input,i+1,right);
    198:      }
    199:   }
    200:
    201:   void Swap(String*& i, String*& j)
    202:   {
    203:      String* temp;
    204:      temp = j;
    205:      j = i;
    206:      i = temp;
    207:   }

Output:

    d:\>Set
    CONFIG=Normal
    CFG_COMPILER=MSC8
    COMSPEC=C:\NDOS.COM
    CMDLINE=C:\AUTOEXEC.BAT
    PROMPT=$p$g
    PATH=C:\BIN
    NU=d:\nu
    PROCOMM=\comm\pro\
    MOUSE=C:\MSMOUSE
    TEMP=c:\temp
    TMP=c:\temp
    EPSPATH=d:\eps65
    ESESSION=d:\eps65\EPSILON.SES

    d:\>0606
    CFG_COMPILER=MSC8
    CMDLINE=proj0004
    COMSPEC=C:\NDOS.COM
    CONFIG=Normal
    EPSPATH=d:\eps65
    ESESSION=d:\eps65\EPSILON.SES
    MOUSE=C:\MSMOUSE
    NU=d:\nu
    PATH=C:\BIN
    PROCOMM=\comm\pro\
    PROMPT=$p$g
    TEMP=c:\temp
    TMP=c:\temp

Analysis:

Listing 6.6 is similar to listing 6.5; however, this time it is the environment variables that are sorted. The output reflects an abridged environment set, first printed as it is output by DOS, and then reprinted using the program listed in listing 6.6.

In line 164, a StringList object is created, and it is filled by iterating through all the strings provided by the third parameter to main(). Because there is no equivalent to argc for the environment parameters, the for loop terminates when a Null string is found. The number of strings is recorded in the local variable NumEnv, and this number is passed into QuickSort() in line 169, and then used again when printing the list in lines 171 through 173.

Using getenv

It is possible to search the environment for a single variable. DOS, UNIX, and the ANSI-proposed standard all call for the getenv() function, which returns a pointer to the environment variable or NULL if the variable doesn't exist.

Listing 6.7 Using getenv()

    1:     // Listing 6.7 - Using getenv()
    2:
    3:     #include <ctype.h>
    4:     #include <stdio.h>
    5:     #include <stdlib.h>
    6:     #include <string.h>
    7:     #include "string.hpp"
    8:     #include <dos.h>
    9:
    10:
    11:    typedef unsigned long ULONG;
    12:
    13:      int main()
    14:     {
    15:       char *ptr;
    16:       char buffer[100];
    17:
    18:       cout << "What do you want to search for? ";
    19:       cin.getline(buffer,100);
    20:
    21:       for (int i=0; i<strlen(buffer); i++)
    22:          buffer[i] = toupper(buffer[i]);
    23:
    24:       ptr = getenv(buffer);
    25:
    26:       if (ptr)
    27:       {
    28:          String found(ptr);
    29:          cout << "found: " << found << endl;
    30:       }
    31:       else
    32:          cout << "Not found. Try again. \n" << endl;
    33:
    34:       return 0;
    35:    }

Output:

    What do you want to search for? include
    found: y:\h;y:\rsrc;d:\bc4\include

    What do you want to search for? abc
    Not found. Try again.

Analysis:

The user is prompted for a string in line 18. The string is forced to all uppercase letters in lines 21 and 22 to match the environment variables on my machine. In line 24, the string is passed to getenv() and the result is assigned to the pointer ptr.

In line 26, the results are tested. If they are not null, a String object is created and displayed; otherwise, a message is printed.

Using putenv

It is possible to change the value of an environment variable once you have a pointer to it. Listing 6.8 demonstrates getting, changing, and displaying an environment variable.

Listing 6.8 Using putenv

    1:     #include <ctype.h>
    2:     #include <stdio.h>
    3:     #include <stdlib.h>
    4:     #include <string.h>
    5:     #include <dos.h>
    6:     #include <iostreams.h>
    7:       int main()
    8:      {
    9:        char *ptr;
    10:       char buffer[100];
    11:       char buff2[100];
    12:
    13:       cout << "What do you want to search for? ";
    14:       cin.getline(buffer,100);
    15:       for (int i=0; i<strlen(buffer); i++)
    16:          buffer[i] = toupper(buffer[i]);
    17:       ptr = getenv(buffer);
    18:       if (ptr)
    19:       {
    20:          cout << "found: " << ptr << endl;
    21:          cout << "What do you want to set it to? " ;
    22:          cin.getline(buff2,100);
    23:          for (int i=0; i<strlen(buff2); i++)
    24:             buff2[i] = toupper(buff2[i]);
    25:          strcat(buffer,"=");
    26:          strcat(buffer,buff2);
    27:          putenv(buffer);
    28:          cout << "Displaying..." << endl;
    29:          while (_environ[i])
    30:           cout << _environ[i++] << endl;
    31:       }
    32:       else
    33:          cout << "Not found. Try again. \n" << endl;
    34:
    35:       return 0;
    36:    }

Output:

    SET FUN=programming
    What do you want to search for? fun
    found: programming
    What do you want to set it to? sleeping
    Displaying...
    FUN=SLEEPING

Analysis:

This version dispenses with the String class altogether and uses old-fashioned, C-style strings. In line 13, the user is prompted for a string to search the environment for. If it is found, it is displayed in line 20 and the user is prompted for a replacement in line 21.

The original string, with the name of the environment variables, has an equal sign added to it in line 25 and the new value added in line 26, and that is inserted into the environment in line 27. The result is displayed in line 28.

Summary

Today you learned how to access the command-line parameters to your program in DOS and UNIX, and how to access the environment variables. These parameters and variables are text strings, and can be sorted, combined, and tested using normal text-manipulation functions.

There are two ways to access the environment variables in DOS: from the array of C-style strings passed to main(), and from the global array of C-style strings accessed using getenv().

Q&A

Q: Why is the argument list presented as two variables, the count (argc) and the vector (argv), but the environment is presented as only the vector (env), which is terminated with a null pointer?

A: The C programming language originally used just argc and argv, with no concept of the environment. The environment vector was added years later, after the C programming community had much more experience with vectors in general. By this time, it was known that a null-terminated list is easier to deal with than a count and a list. However, it was too late to go back and change all the existing programs, so argc and argv weren't changed. C++ inherits this behavior from C.

Q: : Why is it easier to use the name of the program rather than flags in UNIX than it is in DOS?

A: It is easier for the user, because there is one less thing to type. In UNIX, there is no real cost to using this approach, because you can create "links" to the file. These make it appear as if the executable file appears several times, under different names, but in reality it is stored only once.

Q: If I pass in a sentence as an argument, how can I prevent each word in the sentence from being considered to be a separate argument? I just want the whole sentence to be a single argument.

A: Surround the sentence in quotation marks.

Q: Are there command-line arguments in Windows?

A: There are, but it is awkward to access them, so most Windows programs don't require their use. If you invoke a program using File Run, you have an opportunity to add parameters, and you can add parameters to the command line associated with an icon via File Properties. Finally, if you run something that is not executable, but its extension is listed in the [Extensions] portion of your WIN.INI file, the executable that is associated with that extension is run, and the file name you originally selected is passed in as an argument.

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you have learned. Try to answer the quiz and exercise questions before checking the answers in Appendix A, and make sure that you understand the answers before continuing to the next chapter.

Quiz

  1. What are the four legal ways to declare the function main()?

  2. What are the two ways to access the environment variables?

  3. If you invoke a program with two parameters, what value will argc have?

  4. What string is at argv[0]?

[Click here for Answers]

Exercises

  1. Write a program in which main() takes no parameters, and in which you obtain and sort the environment variables.

  2. Write a program that inserts its own full path name into an environment variable called self.

  3. BUG BUSTERS: What is wrong with this code?
        1:     #include <ctype.h>
        2:     #include <stdio.h>
        3:     #include <stdlib.h>
        4:     #include <string.h>
        5:     #include <dos.h>
        6:     #include <iostream.h>
        7:       int main()
        8:      {
        9:        char *ptr;
        10:       char buffer[100];
        11:       char buff2[100];
        12:
        13:       cout << "What do you want to search for? ";
        14:       cin.getline(buffer,100);
        15:       for (int i=0; i<strlen(buffer); i++)
        16:          buffer[i] = toupper(buffer[i]);
        17:       ptr = getenv(buffer);
        18:       if (ptr)
        19:       {
        20:          cout << "found: " << ptr << endl;
        21:          cout << "What do you want to set it to? " ;
        22:          cin.getline(buff2,100);
        23:          for (int i=0; i<strlen(buff2); i++)
        24:             buff2[i] = toupper(buff2[i]);
        25:          strcat(ptr,"=");
        26:          strcat(ptr,buff2);
        27:          putenv(ptr);
        28:          cout << "Displaying..." << endl;
        29:          while (_environ[i])
        30:           cout << _environ[i++] << endl;
        31:       }
        32:       else
        33:          cout << "Not found. Try again. \n" << endl;
        34:
        35:       return 0;
        36:    }
    

Go to: Table of Contents | Next Page