Skip to content

Instantly share code, notes, and snippets.

@lelanthran
Last active May 27, 2019 21:15
Show Gist options
  • Save lelanthran/4d50105c2d0594b5c15aeaeed72f84c3 to your computer and use it in GitHub Desktop.
Save lelanthran/4d50105c2d0594b5c15aeaeed72f84c3 to your computer and use it in GitHub Desktop.
Single function to get command-line options in both long and short form.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
/* *****************************************************************
* A more robust command-line handling function than I normally use.
* There are only two functions, so this is suitable for copying and
* pasting into the file containing main().
*
* =======================================================================
* Skip all options on the command line, and set argc/argv to point to the
* program's non-option arguments:
* void cline_skipopt (int *argc, char ***argv);
*
* NOTE: the argc/argv parameters are modified by this function.
*
*
*
* =======================================================================
* Get an option from the command-line:
* const char *cline_getopt (int argc,
* char **argv,
* const char *longopt,
* char shortopt);
*
* argc: number of arguments
* argv: array of arguments
* longopt: string containing name of option. If NULL, short-opt is
* used to find the option.
* shortopt: character of the option to search for. If zero, longopt is
* used to find the option.
*
* RETURNS: If option is not found, then NULL is returned. If option is
* found then the string that is returned:
* 1. Will be empty if option did not have an argument
* such as "--option=" or "--option".
* 2. Will contain the value of the argument if the option had an
* argument, such as "--option=value", or "-o value", or
* "-ovalue".
*
* For short options, the caller must determine whether to use
* the returned string's value or not. If, for example, the
* option "-c" does not have arguments and the user entered
* "-cab" then the caller must only check for nullness in the
* return value.
*
* If the caller expects "-c" to have arguments, then the
* returned string for "-cab" will contain "ab".
*
* See EXAMPLES below for clarification.
*
* EXAMPLES:
*
* 1. Get a long option using cline_getopt()
* --long-option Returns empty string ""
* --long-option= Returns empty string ""
* --long-option=value Returns const string "value"
*
* 2. Get a short option using cline_getopt()
* -a -b -c Returns non-NULL for a, b and c
* -abc Same as above
* -ofoo Returns "foo" for o
* -o foo Same as above
* -abco foo Returns non-NULL for a, b and c, AND returns foo for o
* -abcofoo Same as above
*
* 3. When the same long-option and short-option is specified the
* long-option takes precedence.
*
* 4. Options processing ends with "--". Any arguments after a "--" is
* encountered must be processed by the caller.
*/
static const char *cline_getopt (int argc, char **argv,
const char *longopt,
char shortopt)
{
for (int i=1; i<argc; i++) {
if (argv[i][0]!='-')
continue;
if ((memcmp (argv[i], "--", 3))==0)
return NULL;
char *value = NULL;
if (argv[i][1]=='-' && longopt) {
char *name = &argv[i][2];
if ((memcmp (name, longopt, strlen (longopt)))==0) {
argv[i][0] = 0;
value = strchr (name, '=');
if (!value)
return "";
*value++ = 0;
return value;
}
}
if (!shortopt || argv[i][1]=='-')
continue;
for (size_t j=1; argv[i][j]; j++) {
if (argv[i][j] == shortopt) {
memmove (&argv[i][j], &argv[i][j+1], strlen (&argv[i][j+1])+1);
if (argv[i][j] == 0) {
return argv[i+1] ? argv[i+1] : "";
} else {
return &argv[i][j];
}
}
}
}
return NULL;
}
static void cline_skipopt (int *argc, char ***argv)
{
for (int i=1; i<(*argc); i++) {
if ((memcmp ((*argv)[i], "--", 3))==0) {
(*argv) = &(*argv)[i+1];
(*argc) = (*argc) - i - 1;
return;
}
}
}
static bool valcmp (const char *value, const char *expected)
{
if (value==NULL && expected==NULL)
return true;
if (value==NULL || expected==NULL)
return false;
return strcmp (value, expected)==0;
}
/* ***********************************************************
* Test program. Execute with:
* cline_test --long-one=long-one \
* -abcd foo \
* -fghbar \
* --long-two= \
* --long-three \
* --long-five \
* -- all the normal args go here -e
*/
int main (int argc, char **argv)
{
static const struct {
const char *l;
char s;
const char *result;
} testcases[] = {
{ "long-one", 0, "long-one" }, // --long-one=long-one
{ "long-two", 0, "" }, // --long-two=
{ "long-three", 0, "" }, // --long-three
{ "long-four", 0, NULL }, // --long-five
{ NULL, 'a', "bcd" }, // -abcd foo
{ NULL, 'b', "cd" }, // -abcd foo
{ NULL, 'c', "d" }, // -abcd foo
{ NULL, 'd', "foo" }, // -abcd foo
{ NULL, 'e', NULL }, // -abcd foo
{ NULL, 'h', "bar" }, // -fghbar
};
for (size_t i=0; i<sizeof testcases/sizeof testcases[0]; i++) {
const char *value =
cline_getopt (argc, argv, testcases[i].l, testcases[i].s);
const char *msg = valcmp (value, testcases[i].result) ? "pass" : "fail";
printf ("%s %zu: [%s/%c]: [%s:%s]\n", msg,
i,
testcases[i].l,
testcases[i].s,
testcases[i].result,
value);
}
// Find the start of the program's non-option arguments
cline_skipopt (&argc, &argv);
for (int i=0; i<argc; i++) {
printf ("[%i]:[%s]\n", i, argv[i]);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment