Основание: меня еще 10 лет назад утомили мелкие глюки и разное поведение различных libc getopt.
Note: не все проверки сделаны
/* * Copyright 2023 Oleg Borodin <borodin@unix7.org> */ #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <stdio.h> static bool isprefixed(char* str, char* prefix) { if ((str == NULL) || (prefix == NULL)) { return false; } if (strncmp(str, prefix, strlen(prefix)) == 0) { return true; } return false; } static bool ismember(char elem, char* set) { if (set == NULL) return false; for(size_t i = 0; i < strlen(set); i++) { if (set[i] == elem) return true; } return false; } #if 0 static bool haschar(char* set, char elem) { if (set == NULL) return false; for(size_t i = 0; i < strlen(set); i++) { if (set[i] == elem) return true; } return false; } #endif static char* ltrim(char* str, char* set) { if (str == NULL) return NULL; size_t pos = 0; size_t strsize = strlen(str); if (set != NULL) { for (pos = 0; pos < strsize; pos++) { if (!ismember(str[pos], set)) break; } } size_t memsize = strsize - pos + 1; char* dst = malloc(memsize); memset(dst, '\0', memsize); strcpy(dst, &(str[pos])); return dst; } static size_t strsplit(char* str, char*** dst, char sep, size_t max) { if (str == NULL) return -1; size_t strsize = strlen(str); if (dst != NULL) { *dst = malloc(sizeof(char*) * (max + 1)); for (size_t i = 0; i < (max + 1); i++) { (*dst)[i] = NULL; } } size_t begin = 0; size_t end = 0; size_t num = 0; for (size_t i = 0; i < strsize; i++) { if (str[i] == sep) { end = i; size_t memsize = end - begin; if (dst != NULL) { char* word = malloc(memsize + 1); memset(word, '\0', memsize + 1); memcpy(word, &(str[begin]), end - begin); (*dst)[num] = word; } num++; begin = end + 1; } if (num == max - 1) break; } if (end < strsize + 1) { if (num > 1) begin = end + 1; end = strsize; if (dst != NULL) { size_t memsize = end - begin; char* word = malloc(memsize + 1); memset(word, '\0', memsize + 1); memcpy(word, &(str[begin]), end - begin); (*dst)[num] = word; } } return ++num; } static char* copystr(char* src) { size_t srcsize = strlen(src) + 1; char* dst = malloc(srcsize); memset(dst, '\0', srcsize); strcpy(dst, src); return dst; } static void strtolower(char* str) { size_t strsize = strlen(str); for (size_t i = 0; i < strsize; i++) { str[i] = tolower(str[i]); } } typedef struct { char* name; bool vopt; int type; union { char** sval; bool* bval; int* ival; }; } arg_t; #define GETOPT_STRING 1 #define GETOPT_INTEGER 2 #define GETOPT_BOOL 3 arg_t* new_arg_string(char* name, char** val) { arg_t* arg = malloc(sizeof(arg_t)); arg->name = name; arg->vopt = true; arg->sval = val; arg->type = GETOPT_STRING; return arg; } arg_t* new_arg_integer(char* name, int* val) { arg_t* arg = malloc(sizeof(arg_t)); arg->name = name; arg->vopt = true; arg->ival = val; arg->type = GETOPT_INTEGER; return arg; } arg_t* new_arg_bool(char* name, bool* val) { arg_t* arg = malloc(sizeof(arg_t)); arg->name = name; arg->vopt = false; arg->bval = val; arg->type = GETOPT_BOOL; return arg; } typedef struct { arg_t** args; size_t size; size_t capa; bool err; char* errstr; } getopt_t; #define GETOPT_INITCAPA 16 void getopt_init(getopt_t* getopt) { getopt->args = malloc(sizeof(arg_t) * GETOPT_INITCAPA); getopt->capa = GETOPT_INITCAPA; getopt->size = 0; getopt->err = false; getopt->errstr = NULL; return; } #define RES_BIND_OK ((int)0) #define RES_BIND_ERR ((int)-1) int getopt_check_capa(getopt_t* getopt) { if (getopt->size == getopt->capa) { size_t newcapa = getopt->capa * 2; arg_t** newargs = realloc(getopt->args, newcapa); if (newargs == NULL) return RES_BIND_ERR; getopt->args = newargs; getopt->capa = newcapa; } return RES_BIND_OK; } int getopt_bind_string(getopt_t* getopt, char* name, char** val) { int res = 0; if ((res = getopt_check_capa(getopt)) != RES_BIND_OK) { return res; } arg_t* arg = new_arg_string(name, val); getopt->args[getopt->size] = arg; getopt->size++; return RES_BIND_OK; } int getopt_bind_integer(getopt_t* getopt, char* name, int* val) { int res = 0; if ((res = getopt_check_capa(getopt)) != RES_BIND_OK) { return res; } arg_t* arg = new_arg_integer(name, val); getopt->args[getopt->size] = arg; getopt->size++; return RES_BIND_OK; } int getopt_bind_bool(getopt_t* getopt, char* name, bool* val) { int res = 0; if ((res = getopt_check_capa(getopt)) != RES_BIND_OK) { return res; } arg_t* arg = new_arg_bool(name, val); getopt->args[getopt->size] = arg; getopt->size++; return RES_BIND_OK; } int getopt_parse(getopt_t* getopt, char** argv, int argc) { int shift = 0; while (shift < argc) { char* cliarg = argv[shift]; if (isprefixed(cliarg, "--")) { char* scliarg = ltrim(cliarg, "-"); for (size_t i = 0; i < getopt->size; i++) { arg_t* arg = getopt->args[i]; char** subargs = NULL; size_t count = 2; size_t numsub = strsplit(scliarg, &subargs, '=', count); if (strcmp(arg->name, subargs[0]) == 0) { switch(arg->type) { case GETOPT_BOOL: { if (numsub > 1 && arg->bval != NULL) { strtolower(subargs[1]); if (strcmp(subargs[1], "yes") == 0) { *(arg->bval) = true; } else if (strcmp(subargs[1], "true") == 0) { *(arg->bval) = true; } else if (strcmp(subargs[1], "t") == 0) { *(arg->bval) = true; } else if (strcmp(subargs[1], "no") == 0) { *(arg->bval) = false; } else if (strcmp(subargs[1], "false") == 0) { *(arg->bval) = false; } else if (strcmp(subargs[1], "f") == 0) { *(arg->bval) = false; } else { asprintf(&(getopt->errstr), "%s: invalid boolean value: %s", cliarg, subargs[1]); getopt->err = true; return -1; } } else { *(arg->bval) = true; } break; } case GETOPT_STRING: { if (numsub > 1) { if (strlen(subargs[1]) == 0 && arg->vopt == true) { asprintf(&(getopt->errstr), "%s: value is necessary", cliarg); getopt->err = true; return -1; } *(arg->sval) = copystr(subargs[1]); } else if (arg->vopt == true) { asprintf(&(getopt->errstr), "%s: value is necessary", cliarg); getopt->err = true; return -1; } break; } case GETOPT_INTEGER: { if (numsub > 1) { char* endptr = NULL; *(arg->ival) = (int)strtol(subargs[1], &endptr, 10); if (endptr[0] != '\0') { asprintf(&(getopt->errstr), "%s: invalid integer value: %s", cliarg, subargs[1]); getopt->err = true; return -1; } } break; } } } for (int i = 0; i < numsub; i++) { free(subargs[i]); } free(subargs); } free(scliarg); } shift++; } return 0; } void getopt_destroy(getopt_t* getopt) { if (getopt != NULL) free(getopt->args); return; } int main(int argc, char **argv) { getopt_t getopt; getopt_init(&getopt); int intval = 0; bool boolval = false; char* strval = NULL; getopt_bind_integer(&getopt, "port", &intval); getopt_bind_bool(&getopt, "help", &boolval); getopt_bind_string(&getopt, "ident", &strval); int res = getopt_parse(&getopt, argv, argc); if (res < 0) { printf("error: %s\n", getopt.errstr); } printf("bool = %d, str = %s, int = %d\n", boolval, strval, intval); return 0; }
$ ./getopt2 --help=yes --port=12345 --ident=qwerty bool = 1, str = qwerty, int = 12345 $ ./getopt --help=no --port=12345 --ident=qwerty bool = 0, str = qwerty, int = 12345 $ ./getopt --help --port=12345 --ident=qwerty bool = 1, str = qwerty, int = 12345 $ ./getopt --help --port=12345 --ident error: --ident: value is necessary bool = 1, str = (null), int = 12345 $ ./getopt --help=da --port=12345 --ident error: --help=da: invalid boolean value: da bool = 0, str = (null), int = 0 $ ./getopt --help=yes --port=x18907 --ident error: --port=x18907: invalid integer value: x18907 bool = 1, str = (null), int = 0