Analizza la stringa in argv / argc

C’è un modo in C per analizzare un pezzo di testo e ottenere valori per argv e argc, come se il testo fosse stato passato a un’applicazione sulla riga di comando?

Questo non deve funzionare su Windows, solo su Linux, anche a me non interessa la citazione degli argomenti.

Se la soluzione di glib è overkill per il tuo caso, potresti prendere in considerazione la possibilità di crearne uno tu stesso.

Allora puoi:

  • scansiona la stringa e conta quanti argomenti ci sono (e ottieni il tuo argc)
  • allocare un array di char * (per il tuo argv)
  • ripetere la scansione della stringa, assegnare i puntatori nell’array assegnato e sostituire gli spazi con ‘\ 0’ (se non è ansible modificare la stringa contenente gli argomenti, è necessario duplicarlo).
  • non dimenticare di liberare ciò che hai assegnato!

Lo schema seguente dovrebbe chiarire (si spera):

  aa bbb ccc "dd d" ee <- original string aa0bbb0ccc00dd d00ee0 <- transformed string | | | | | argv[0] __/ / / / / argv[1] ____/ / / / argv[2] _______/ / / argv[3] ___________/ / argv[4] ________________/ 

Una ansible API potrebbe essere:

  char **parseargs(char *arguments, int *argc); void freeparsedargs(char **argv); 

Avrai bisogno di ulteriori considerazioni per implementare freeparsedargs () in modo sicuro.

Se la stringa è molto lunga e non si desidera eseguire la scansione due volte, è ansible considerare un'alternativa come l'allocazione di più elementi per gli array argv (e la riallocazione se necessario).

EDIT: Soluzione proposta (non trattare argomenti citati).

  #include  static int setargs(char *args, char **argv) { int count = 0; while (isspace(*args)) ++args; while (*args) { if (argv) argv[count] = args; while (*args && !isspace(*args)) ++args; if (argv && *args) *args++ = '\0'; while (isspace(*args)) ++args; count++; } return count; } char **parsedargs(char *args, int *argc) { char **argv = NULL; int argn = 0; if (args && *args && (args = strdup(args)) && (argn = setargs(args,NULL)) && (argv = malloc((argn+1) * sizeof(char *)))) { *argv++ = args; argn = setargs(args,argv); } if (args && !argv) free(args); *argc = argn; return argv; } void freeparsedargs(char **argv) { if (argv) { free(argv[-1]); free(argv-1); } } int main(int argc, char *argv[]) { int i; char **av; int ac; char *as = NULL; if (argc > 1) as = argv[1]; av = parsedargs(as,&ac); printf("== %d\n",ac); for (i = 0; i < ac; i++) printf("[%s]\n",av[i]); freeparsedargs(av); exit(0); } 

Sono sorpreso che nessuno abbia fornito la risposta più semplice utilizzando la funzionalità POSIX standard:

http://www.opengroup.org/onlinepubs/9699919799/functions/wordexp.html

Ecco il mio contributo. È bello e breve, ma le cose di cui diffidare sono:

  • L’uso di strtok modifica la stringa “commandLine” originale, sostituendo gli spazi con delimitatori \ 0 di fine stringa
  • argv [] finisce con l’indicare “commandLine”, quindi non modificarlo finché non hai finito con argv [].

Il codice:

 enum { kMaxArgs = 64 }; int argc = 0; char *argv[kMaxArgs]; char *p2 = strtok(commandLine, " "); while (p2 && argc < kMaxArgs-1) { argv[argc++] = p2; p2 = strtok(0, " "); } argv[argc] = 0; 

Ora puoi usare argc e argv, o passarli ad altre funzioni dichiarate come "foo (int argc, char ** argv)".

Il glib sempre meraviglioso ha g_shell_parse_args() che suona come quello che stai g_shell_parse_args() .

Se non ti interessa nemmeno citare, questo potrebbe essere eccessivo. Tutto ciò che devi fare è tokenize, usando gli spazi bianchi come carattere token. Scrivere una semplice routine per farlo non dovrebbe richiedere molto tempo.

Se non sei super-avaro nella memoria, farlo in un solo passaggio senza riallocazioni dovrebbe essere facile; supponiamo semplicemente che il caso peggiore di ogni secondo carattere sia uno spazio, assumendo che una stringa di n caratteri contenga al massimo (n + 1) / 2 argomenti e (ovviamente) al massimo n byte di argomento testo (esclusi i terminatori).

Ecco una soluzione per Windows e Unix (testato su Linux, OSX e Windows). Testato con Valgrind e Dr. Memory .

Usa wordexp per i sistemi POSIX e CommandLineToArgvW per Windows.

Si noti che per la soluzione Windows, la maggior parte del codice sta convertendo tra char ** e wchar_t ** con la bellissima API Win32, poiché non è disponibile CommandLineToArgvA (versione ANSI).

 #ifdef _WIN32 #include  #else #include  #endif char **split_commandline(const char *cmdline, int *argc) { int i; char **argv = NULL; assert(argc); if (!cmdline) { return NULL; } // Posix. #ifndef _WIN32 { wordexp_t p; // Note! This expands shell variables. if (wordexp(cmdline, &p, 0)) { return NULL; } *argc = p.we_wordc; if (!(argv = calloc(*argc, sizeof(char *)))) { goto fail; } for (i = 0; i < p.we_wordc; i++) { if (!(argv[i] = strdup(p.we_wordv[i]))) { goto fail; } } wordfree(&p); return argv; fail: wordfree(&p); } #else // WIN32 { wchar_t **wargs = NULL; size_t needed = 0; wchar_t *cmdlinew = NULL; size_t len = strlen(cmdline) + 1; if (!(cmdlinew = calloc(len, sizeof(wchar_t)))) goto fail; if (!MultiByteToWideChar(CP_ACP, 0, cmdline, -1, cmdlinew, len)) goto fail; if (!(wargs = CommandLineToArgvW(cmdlinew, argc))) goto fail; if (!(argv = calloc(*argc, sizeof(char *)))) goto fail; // Convert from wchar_t * to ANSI char * for (i = 0; i < *argc; i++) { // Get the size needed for the target buffer. // CP_ACP = Ansi Codepage. needed = WideCharToMultiByte(CP_ACP, 0, wargs[i], -1, NULL, 0, NULL, NULL); if (!(argv[i] = malloc(needed))) goto fail; // Do the conversion. needed = WideCharToMultiByte(CP_ACP, 0, wargs[i], -1, argv[i], needed, NULL, NULL); } if (wargs) LocalFree(wargs); if (cmdlinew) free(cmdlinew); return argv; fail: if (wargs) LocalFree(wargs); if (cmdlinew) free(cmdlinew); } #endif // WIN32 if (argv) { for (i = 0; i < *argc; i++) { if (argv[i]) { free(argv[i]); } } free(argv); } return NULL; } 

L’ho appena fatto per un progetto embedded in plain C, dove ho una piccola CLI che analizza l’input della porta seriale ed esegue un set limitato di comandi con i parametri.

Questo probabilmente non è il più accurato, ma il più piccolo ed efficiente che potessi ottenere:

 int makeargs(char *args, int *argc, char ***aa) { char *buf = strdup(args); int c = 1; char *delim; char **argv = calloc(c, sizeof (char *)); argv[0] = buf; while (delim = strchr(argv[c - 1], ' ')) { argv = realloc(argv, (c + 1) * sizeof (char *)); argv[c] = delim + 1; *delim = 0x00; c++; } *argc = c; *aa = argv; return c; } 

testare:

 int main(void) { char **myargs; int argc; int numargs = makeargs("Hello world, this is a test", &argc, &myargs); while (numargs) { printf("%s\r\n", myargs[argc - numargs--]); }; return (EXIT_SUCCESS); } 

LIBTINYC di Matt Peitrek ha un modulo chiamato argcargv.cpp che prende una stringa e la analizza nell’array degli argomenti prendendo in considerazione gli argomenti citati. Nota che è specifico per Windows, ma è piuttosto semplice, quindi dovrebbe essere facile spostarsi su qualsiasi piattaforma tu voglia.

Ho finito per scrivere una funzione per farlo da solo, non penso sia molto buona ma funziona per i miei scopi – sentiti libero di suggerire miglioramenti per chiunque altro abbia bisogno di questo in futuro:

 void parseCommandLine(char* cmdLineTxt, char*** argv, int* argc){ int count = 1; char *cmdLineCopy = strdupa(cmdLineTxt); char* match = strtok(cmdLineCopy, " "); // First, count the number of arguments while(match != NULL){ count++; match = strtok(NULL, " "); } *argv = malloc(sizeof(char*) * (count+1)); (*argv)[count] = 0; **argv = strdup("test"); // The program name would normally go in here if (count > 1){ int i=1; cmdLineCopy = strdupa(cmdLineTxt); match = strtok(cmdLineCopy, " "); do{ (*argv)[i++] = strdup(match); match = strtok(NULL, " "); } while(match != NULL); } *argc = count; } 

Soluzione per coloro che non vogliono utilizzare l’allocazione dynamic della memoria (es. Embedded)

Ho scritto tokenise_to_argc_argv() per un progetto incorporato, che utilizza strtok_r() come base per la tokenizzazione di una stringa di comando in forma argc e argv. A differenza della maggior parte delle risposte qui, di solito alloco memoria in modo statico. Quindi la mia implementazione presuppone che tu abbia un limite superiore di argv_length . Per la maggior parte delle applicazioni embedded tipiche, questo è più che sufficiente. Ho incluso anche il codice di esempio qui sotto per poterlo usare rapidamente.

 int tokenise_to_argc_argv( char *buffer, ///< In/Out : Modifiable String Buffer To Tokenise int *argc, ///< Out : Argument Count char *argv[], ///< Out : Argument String Vector Array const int argv_length ///< In : Maximum Count For `*argv[]` ) { /* Tokenise string buffer into argc and argv format (req: string.h) */ int i = 0; for (i = 0 ; i < argv_length ; i++) { /* Fill argv via strtok_r() */ if ( NULL == (argv[i] = strtok_r( NULL , " ", &buffer)) ) break; } *argc = i; return i; // Argument Count } 

Nota:

  • Il buffer dei caratteri fornito deve essere modificabile (come strtok_r () inserisce \0 nel buffer per delimitare i token stringa).
  • strtok_r in questa funzione utilizza attualmente il carattere " " space come unico delimitatore. Questo emula il comportamento main(int argc, char *argv[]) nelle tipiche interfacce della riga di comando.
  • Questa funzione non usa malloc o calloc, ma dovrai allocare l'array argv separatamente e fornire esplicitamente la lunghezza di argv. Questo perché intendo utilizzarlo nei dispositivi embedded e quindi preferisco allocarlo manualmente.
  • strtok_r() è usato perché è threadsafe (Poiché strtok() usa un puntatore statico interno). Inoltre fa parte della libreria C standard.h, quindi è molto portabile.

Di seguito sono riportati il ​​codice dimostrativo e l'output. Inoltre, questo mostra che tokenise_to_argc_argv () può gestire la maggior parte dei casi di stringhe e quindi è stato testato. Anche questa funzione non si basa su malloc o calloc e quindi è adatta per l'uso incorporato (dopo aver usato i tipi stdint.h ).


Codice dimostrativo

 /******************************************************************************* Tokenise String Buffer To Argc and Argv Style Format Brian Khuu 2017 *******************************************************************************/ #include  // printf() #include  // isprint() #include  // strtok_r() /**----------------------------------------------------------------------------- @brief Tokenise a string buffer into argc and argv format Tokenise string buffer to argc and argv form via strtok_r() Warning: Using strtok_r will modify the string buffer Returns: Number of tokens extracted ------------------------------------------------------------------------------*/ int tokenise_to_argc_argv( char *buffer, ///< In/Out : Modifiable String Buffer To Tokenise int *argc, ///< Out : Argument Count char *argv[], ///< Out : Argument String Vector Array const int argv_length ///< In : Maximum Count For `*argv[]` ) { /* Tokenise string buffer into argc and argv format (req: string.h) */ int i = 0; for (i = 0 ; i < argv_length ; i++) { /* Fill argv via strtok_r() */ if ( NULL == (argv[i] = strtok_r( NULL, " ", &buffer)) ) break; } *argc = i; return i; // Argument Count } /******************************************************************************* Demonstration of tokenise_to_argc_argv() *******************************************************************************/ static void print_buffer(char *buffer, int size); static void print_argc_argv(int argc, char *argv[]); static void demonstrate_tokenise_to_argc_argv(char buffer[], int buffer_size); int main(void) { /* This shows various string examples */ printf("# `tokenise_to_argc_argv()` Examples\n"); { printf("## Case0: Normal\n"); char buffer[] = "tokenising example"; demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer)); } { printf("## Case1: Empty String\n"); char buffer[] = ""; demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer)); } { printf("## Case2: Extra Space\n"); char buffer[] = "extra space here"; demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer)); } { printf("## Case3: One Word String\n"); char buffer[] = "one-word"; demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer)); } } static void demonstrate_tokenise_to_argc_argv(char buffer[], int buffer_size) { /* This demonstrates usage of tokenise_to_argc_argv */ int argc = 0; char *argv[10] = {0}; printf("* **Initial State**\n"); print_buffer(buffer, buffer_size); /* Tokenise Command Buffer */ tokenise_to_argc_argv(buffer, &argc, argv, sizeof(argv)); printf("* **After Tokenizing**\n"); print_buffer(buffer, buffer_size); print_argc_argv(argc,argv); printf("\n\n"); } static void print_buffer(char *buffer, int size) { printf(" - Buffer Content `"); for (int i = 0 ; i < size; i++) printf("%c",isprint(buffer[i])?buffer[i]:'0'); printf("` | HEX: "); for (int i = 0 ; i < size; i++) printf("%02X ", buffer[i]); printf("\n"); } static void print_argc_argv(int argc, char *argv[]) { /* This displays the content of argc and argv */ printf("* **Argv content** (argc = %d): %s\n", argc, argc ? "":"Argv Is Empty"); for (int i = 0 ; i < argc ; i++) printf(" - `argv[%d]` = `%s`\n", i, argv[i]); } 

Produzione

tokenise_to_argc_argv() Esempi

Case0: normale

  • Stato iniziale
    • tokenising example0 contenuto del tokenising example0 | ESAGONO: 74 6F 6B 65 6E 69 73 69 6E 67 20 65 78 61 6D 70 6C 65 00
  • Dopo Tokenizing
    • Contenuto del buffer tokenising0example0 | ESAGONO: 74 6F 6B 65 6E 69 73 69 6E 67 00 65 78 61 6D 70 6C 65 00
  • Argv content (argc = 2):
    • argv[0] = tokenising
    • argv[1] = example

Caso 1: Stringa vuota

  • Stato iniziale
    • Contenuto del buffer 0 | HEX: 00
  • Dopo Tokenizing
    • Contenuto del buffer 0 | HEX: 00
  • Argv content (argc = 0): Argv è vuoto

Caso 2: spazio extra

  • Stato iniziale
    • Contenuto extra space here0 buffer extra space here0 | HEX: 65 78 74 72 61 20 20 73 70 61 63 65 20 68 65 72 65 00
  • Dopo Tokenizing
    • Contenuto del buffer extra0 space0here0 | ESAGONO: 65 78 74 72 61 00 20 73 70 61 63 65 00 68 65 72 65 00
  • Argv content (argc = 3):
    • argv[0] = extra
    • argv[1] = space
    • argv[2] = here

Case3: una stringa di Word

  • Stato iniziale
    • Contenuto del buffer one-word0 | ESAGONO: 6F 6E 65 2D 77 6F 72 64 00
  • Dopo Tokenizing
    • Contenuto del buffer one-word0 | ESAGONO: 6F 6E 65 2D 77 6F 72 64 00
  • Argv content (argc = 1):
    • argv[0] = one-word

String.sh o strtok_r () mancanti nella tua toolchain in qualche modo?

Se per qualche ragione la tua toolchain non ha strtok_r (). Puoi usare questa versione semplificata di strtok_r (). Si tratta di una versione modificata dell'implementazione GNU C di strtok_r (), ma semplificata per supportare solo il carattere dello spazio.

Per usarlo, basta posizionarlo sopra tokenise_to_argc_argv() quindi sostituire strtok_r( NULL, " ", &buffer) con strtok_space(&buffer)

 /**----------------------------------------------------------------------------- @brief Simplied space deliminated only version of strtok_r() - save_ptr : In/Out pointer to a string. This pointer is incremented by this function to find and mark the token boundry via a `\0` marker. It is also used by this function to find mutiple other tokens via repeated calls. Returns: - NULL : No token found - pointer to start of a discovered token ------------------------------------------------------------------------------*/ char * strtok_space(char **save_ptr) { /* strtok_space is slightly modified from GNU C Library `strtok_r()` implementation. Thus this function is also licenced as GNU Lesser General Public License*/ char *start = *save_ptr; char *end = 0; if (*start == '\0') { *save_ptr = start; return NULL; } /* Scan leading delimiters. */ while(*start == ' ') start++; if (*start == '\0') { *save_ptr = start; return NULL; } /* Find the end of the token. */ end = start; while((*end != '\0') && (*end != ' ')) end++; if (*end == '\0') { *save_ptr = end; return start; } /* Terminate the token and make *SAVE_PTR point past it. */ *end = '\0'; *save_ptr = end + 1; return start; } 

Sfortunatamente C ++ ma per altri che potrebbero cercare questo tipo di libreria consiglio:

ParamContainer – parser dei parametri della riga di comando facile da usare

Davvero piccolo e davvero facile.

 p.addParam("long-name", 'n', ParamContainer::regular, "parameter description", "default_value"); 

nomeprogramma –long-nome = valore

 cout << p["long-name"]; >> value 

Dalla mia esperienza:

  • molto utile e semplice
  • stabile sulla produzione
  • ben testato (da me)

Prendi in considerazione un’altra implementazione. Corri .

 #include  //  for isspace() /** * Parse out the next non-space word from a string. * @note No nullptr protection * @param str [IN] Pointer to pointer to the string. Nested pointer to string will be changed. * @param word [OUT] Pointer to pointer of next word. To be filled. * @return pointer to string - current cursor. Check it for '\0' to stop calling this function */ static char* splitArgv(char **str, char **word) { constexpr char QUOTE = '\''; bool inquotes = false; // optimization if( **str == 0 ) return NULL; // Skip leading spaces. while (**str && isspace(**str)) (*str)++; if( **str == '\0') return NULL; // Phrase in quotes is one arg if( **str == QUOTE ){ (*str)++; inquotes = true; } // Set phrase begining *word = *str; // Skip all chars if in quotes if( inquotes ){ while( **str && **str!=QUOTE ) (*str)++; //if( **str!= QUOTE ) }else{ // Skip non-space characters. while( **str && !isspace(**str) ) (*str)++; } // Null terminate the phrase and set `str` pointer to next symbol if(**str) *(*str)++ = '\0'; return *str; } /// To support standart convetion last `argv[argc]` will be set to `NULL` ///\param[IN] str : Input string. Will be changed - splitted to substrings ///\param[IN] argc_MAX : Maximum a rgc, in other words size of input array \p argv ///\param[OUT] argc : Number of arguments to be filled ///\param[OUT] argv : Array of c-string pointers to be filled. All of these strings are substrings of \p str ///\return Pointer to the rest of string. Check if for '\0' and know if there is still something to parse. \ /// If result !='\0' then \p argc_MAX is too small to parse all. char* parseStrToArgcArgvInsitu( char *str, const int argc_MAX, int *argc, char* argv[] ) { *argc = 0; while( *argc 

Codice di utilizzo

 #include  using namespace std; void parseAndPrintOneString(char *input) { constexpr size_t argc_MAX = 5; char* v[argc_MAX] = {0}; int c=0; char* rest = parseStrToArgcArgvInsitu(input,argc_MAX,&c,v); if( *rest!='\0' ) // or more clear `strlen(rest)==0` but not efficient cout<<"There is still something to parse. argc_MAX is too small."< 

Produzione:

 Parsing line "Just another TEST\r\n": argc : 3 argv[0] : Just argv[1] : another argv[2] : TEST Parsing line " Hello my world 'in quotes' !": There is still something to parse. argc_MAX is too small. argc : 4 argv[0] : Hello argv[1] : my argv[2] : world argv[3] : in quotes Parsing line "./hi 'Less is more'": argc : 2 argv[0] : ./hi argv[1] : Less is more Parsing line "Very long line with "double quotes" should be parsed several times if argv[] buffer is small": There is still something to parse. argc_MAX is too small. argc : 4 argv[0] : Very argv[1] : long argv[2] : line argv[3] : with Parsing line " ": argc : 0