How to build Shell-CLI conforming to POSIX utility conventions--Argtable3 source parsing

Keywords: C shell

2021SC@SDUSC

How to build Shell-CLI conforming to POSIX utility conventions

What is Argtable

Argtable is an open source ANSI C library that parses GNU-style command line options. It simplifies command line analysis by defining declarative API s that can be used to specify command line syntax.
argtable automatically generates consistent text descriptions of error handling logic and command line syntax, which are necessary but cumbersome for a robust cli program.

Introduction of Argtable3

The parser's command line has always been a major distracting programming task. The Argtable3 library simplifies jobs by allowing programmers to define command line options directly in the source code as static structural arrays, and then**
Pass this array to the argtable3 library functions, which parse the command line accordingly. The values extracted from the command line are saved directly into user-defined program variables**, which are accessible by the main program.
Argtable3 can also generate a description of the command line syntax from the same array for display as online help. This library is free under the third BSD license terms. Note: argtable3 is not backward compatible.

Quick Start

demo code

  • A small example of getting started
// Introducing argtable3
#include "argtable3.h"

/* global arg_xxx structs */
struct arg_lit *help;
struct arg_end *end;

int main(int argc, char *argv[]) {
    /* the global arg_xxx structs are initialised within the argtable */
    void *argtable[] = {
            help = arg_litn(NULL, "help", 0, 1, "display this help and exit"),
            end = arg_end(20)
    };
    int exitcode = 0;
    char program[] = "argtable";
    arg_parse(argc, argv, argtable);
    /* special case: '--help' takes precedence over error reporting */
    if (help->count > 0) {
        printf("Usage: %s", program);
        arg_print_syntax(stdout, argtable, "\n");
        printf("Demonstrate command-line parsing in argtable3.\n\n");
        arg_print_glossary(stdout, argtable, "  %-25s %s\n");
        exitcode = 0;
        /* deallocate each non-null entry in argtable[] */
        arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
    }
    return exitcode;
}
  • Function
./argtable --help

# output
# Usage: argtable [--help]
# Demonstrate command-line parsing in argtable3.
#
#  --help                    display this help and exit

how it works

Argtable3 provides a set of arg_xxx structures, one for each type of parameter it supports (text, integer, double precision, string, file name, and so on).
Each structure can handle multiple occurrences of this parameter on the command line. In addition, you can provide alternative short (-x) or long (-x x) options for each option that you can use interchangeably.
In fact, each option can even have multiple alternative short or long options, or both.

To define command line options, you must create a structure for each type of parameter (text, integer, double precision, string, file name, and so on) required by arg_xxx and organize them into an array we call the argument table.
The order of structures in the parameter table defines the expected order of command-line options, but the parsing order is actually only important for unmarked options (similar to python's function parameter rules).
The parameter table itself is just an array of void pointers (void* arg_table[]),
By convention, each arg_xxx structure has a known arg_hdr structure as its first entry, which is used by the Argtable3 function to identify the structure.

arg_hdr

typedef struct arg_hdr {
    char flag;             /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
    const char* shortopts; /* String defining the short options */
    const char* longopts;  /* String defiing the long options */
    const char* datatype;  /* Description of the argument data type */
    const char* glossary;  /* Description of the option as shown by arg_print_glossary function */
    int mincount;          /* Minimum number of occurences of this option accepted */
    int maxcount;          /* Maximum number of occurences if this option accepted */
    void* parent;          /* Pointer to parent arg_xxx struct */
    arg_resetfn* resetfn;  /* Pointer to parent arg_xxx reset function */
    arg_scanfn* scanfn;    /* Pointer to parent arg_xxx scan function */
    arg_checkfn* checkfn;  /* Pointer to parent arg_xxx check function */
    arg_errorfn* errorfn;  /* Pointer to parent arg_xxx error function */
    void* priv;            /* Pointer to private header data for use by arg_xxx functions */
} arg_hdr_t;

The most basic type, and all other types contain this type. Explain the meaning of the parameters in one by one:

  • shortopts: The short name of a parameter, such as a in ls-a, is the short name;
  • longopts: Long names in parameters, such as all in ls-all, are long names;
  • datatype: The data types of the parameters are, NULL, (time type passed in format string), (rex type passed in matching string), etc.
  • glossary: Description of parameters;
  • mincount: The minimum number of parameters (you can specify whether there must be parameters such as 0 is not required, 1...n is required);
  • maxcount: Number of parameters most, Unix style, e.g. kernel.exe-l 1-L 2-L 3
  • Other parameters are not required.

arg_xxx Structural Family

// integer
struct arg_int
{
    struct arg_hdr hdr;
    int count;
    int *ival;
};
// Written words
struct arg_lit
{
    struct arg_hdr hdr;
    int count;
};
// Double
struct arg_dbl
{
    struct arg_hdr hdr;
    int count;
    double *dval;
};
// Character string
struct arg_str
{
    struct arg_hdr hdr;
    int count;
    const char **sval;
};

struct arg_rex
{
    struct arg_hdr hdr;
    int count;
    const char **sval;
};

struct arg_file
{
    struct arg_hdr hdr;
    int count;
    const char **filename;
    const char **basename;
    const char **extension;
};

struct arg_date
{
    struct arg_hdr hdr;
    const char *format;
    int count;
    struct tm *tm_val;
};

Detailed description of arg_int

struct arg_int
{
    struct arg_hdr hdr;
    int count;
    int *ival;
};

The first data member of the structure, hdr, holds private data used by the Argtable3 library function. It contains tag strings for parameters, and so on, which can be understood as metadata.
It is possible, but rarely necessary, to access this data directly and publicly.
Its ival member variable points to the array holding the extracted from the command line, count being the length of the array. The ival array is stored and allocated during the arg_int construction.
This must be done through the arg_int constructor.
Note that you should never manually instantiate any arg_xxx structure yourself, but always use the provided constructor to allocate the structure and release it when you're done using arg_freetable.

  • Give an example
struct arg_int *s;
s = arg_intn(NULL, "scalar", "<n>", 3, 5, "foo value");

When s is complete, it points to a structure named arg_int, which contains a memory block of five elements array.
You can even set multiple aliases, with short options assigned to a single string of characters and long options assigned to a comma-separated string.
For example, s = arg_intn ('kKx','scalar, foo','<n>', 3, 5,'foo value');

[External chain picture transfer failed, source station may have anti-theft chain mechanism, it is recommended to save the picture and upload it directly (img-Vo3FZ9oz-1633800152784)(example.png)]
As shown in the figure, the s->hdr structure retains references to string parameters of the constructor, and so on.
The s->count variable is initialized to zero because it represents the number of valid values stored in the array after s->ival parses the command line.
The size of the s->ival array is given by s->hdr.maxcount.

  • Instance Code
// Introducing argtable3
#include "argtable3.h"

/* global arg_xxx structs */
struct arg_int *int_arrays;
struct arg_end *end;

int main(int argc, char *argv[]) {
    /* the global arg_xxx structs are initialised within the argtable */
    void *argtable[] = {
            int_arrays = arg_intn("a", "array", "<number>", 2, 3, "please input an integer"),
            end = arg_end(20)
    };
    int exitcode = -1;
    int nerrors = arg_parse(argc, argv, argtable);
    if (nerrors == 0) {
        exitcode = 0;
        int *a = int_arrays->ival;
        int len = int_arrays->count;
        printf("input numbers are as following:\n");
        for (int i = 0; i < len; ++i) {
            printf("%d  ", a[i]);
        }
    }
    return exitcode;
}

# Function
./argtable -a 1 -a 2 --array 3
# output
# input numbers are as following:
# 1  2  3  

Other parameter constructs are used in much the same way.

The Argument Table

After building the structs for the various types of parameters, we need to unify them into an array of structs of type void **.

struct arg_lit *a = arg_litn("a", NULL, 0, 1, "the -a option");
struct arg_lit *b = arg_litn("b", NULL, 0, 1, "the -b option");
struct arg_lit *c = arg_litn("c", NULL, 0, 1, "the -c option");
struct arg_int *scal = arg_intn(NULL, "scalar", "<n>", 0, 1, "foo value");
struct arg_lit *verb = arg_litn("v", "verbose", 0, 1, "verbose output");
struct arg_file *o = arg_filen("o", NULL,"myfile", 0, 1, "output file");
struct arg_file *file = arg_filen(NULL, NULL, "<file>", 1, 2, "input files");
struct arg_end *end = arg_end(20);
// This is our parameter list
void *argtable[] = {a, b, c, scal, verb, o, file, end}; 

The arg_end structure is a special one because it does not represent any command line options.
It primarily marks the end of the argtable array, and it also stores any parser errors encountered when processing command line parameters.
The integer parameter passed to the arg_end constructor is the maximum number of errors it will store, and any more errors will be discarded and replaced with a single error message, Too Many Errors.
The arg_end must be present.

You can also use arg_nullcheck to check if there is a null pointer in the parameter table. (This feels like a chicken rib.)

if (arg_nullcheck(argtable) != 0)
    printf("error: insufficient memory\n");

Parse Command Line

We do this using the arg_parse function, which returns the number of parsing errors it encounters.

int nerrors = arg_parse(argc, argv, argtable);

If there are no errors (nerrors == 0), then we have successfully parsed the command line, and the data you want to extract from the command line also exists in the void** parameter table, so you can access it normally.

Set Default Value

The simple solution is to set the value of arg_table directly before calling arg_parse so that the value entered by the user is overwritten on the command line.
The rest is the default values you set at the beginning.

error handling

If the arg_parse function reports errors, we need to show them, since arg_parse itself will not do so.
With arg_print_errors, the internal structure of arg_end needs no concern.
Function Definition

void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);

Use examples such as:

If (nerrors > 0)
    arg_print_errors(stdout, end, "myprog"); // stdout file descriptor

The arg_parse function does not print error messages because it can be called multiple times to parse the command line using an alternative parameter table without displaying unrelated error messages prematurely.
Therefore, we can define separate parameter tables for assemblies with mutually exclusive command-line option sets and try each parameter table in turn until we find a successful candidate.
If all the parameter tables are unsatisfactory, we can choose to print the error messages for all the parameter tables, or we can only display the error messages that best match. In any case, we can control which messages are displayed.

Show option syntax and help

If you want your program to display online help, you can use the arg_print_syntax function to display command line syntax derived from the parameter table.
There are actually two forms of this function:

void arg_print_syntax(FILE *fp, void **argtable, const char *suffix);
void arg_print_syntaxv(FILE *fp, void **argtable, const char *suffix);

You can use void arg_print_glossary(FILE *fp, void **argtable, const char *format);To output the prompt.

The usage of these two functions has been shown in the examples at the beginning of the article, and will not be repeated here.

Clean up

At the end of our program, we need to free the memory allocated to each arg_xxx structure. We can do this by calling arg_freetable separately.
But there are functions that make it easier for us to do this.

arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));

It will iterate through a parameter table and call each of its elements on our behalf.
Note that the second parameter, sizeof(argtable)/sizeof(argtable[0]), only represents the number of elements in the argtable array. (Memory alignment used?)
Once this is done, all argtable array entries will be set to NULL.

Complete flowchart using Argtable3

[External chain picture transfer failed, source station may have anti-theft chain mechanism, it is recommended to save the picture and upload it directly (img-95kSIS5N-1633800152786)(flowchart.png)]

Related Links

Introduction to the Argtable3 tutorial
github address

Posted by shanksta13 on Sat, 09 Oct 2021 09:40:36 -0700