将字符串解析为argv / argc

在C中是否有办法解析一段文本并获取argv和argc的值,就像文本已经在命令行中传递给应用程序一样?

这不一定适用于Windows,只需要Linux – 我也不关心引用参数。

如果glib解决方案对您的情况来说太过分了,您可以考虑自己编写一个。

然后你可以:

  • 扫描字符串并计算有多少参数(并得到你的argc)
  • 分配一个char *数组(对于你的argv)
  • 重新扫描字符串,在分配的数组中分配指针并用’\ 0’替换空格(如果你不能修改包含参数的字符串,你应该复制它)。
  • 别忘了释放你所分配的东西!

下图应该澄清(希望):

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

可能的API可能是:

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

您需要其他注意事项才能安全地实现freeparsedargs()。

如果您的字符串很长并且您不想扫描两次,则可以考虑使用替代方法,例如为argv数组分配更多元素(并在需要时重新分配)。

编辑:提出的解决方案(des not handle引用的参数)。

  #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); } 

我很惊讶没有人使用标准POSIXfunction提供最简单的答案:

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

这是我的贡献。 它很好而且很短,但需要注意的是:

  • 使用strtok修改原始的“commandLine”字符串,用\ 0结束字符串分隔符替换空格
  • argv []最终指向“commandLine”,因此在完成argv []之前不要修改它。

代码:

 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; 

您现在可以使用argc和argv,或将它们传递给声明为“foo(int argc,char ** argv)”的其他函数。

永远美妙的glib有g_shell_parse_args() ,听起来就像你追求的那样。

如果你对引用甚至不感兴趣,这可能是矫枉过正的。 您需要做的就是使用空格作为标记字符进行标记。 写一个简单的例程来做到这一点不应该花很长时间,真的。

如果你对记忆并不吝啬,那么在没有重新分配的情况下一次性完成它应该很容易; 假设每个第二个字符都是空格的最坏情况,因此假设一个n字符的字符串包含最多(n + 1) / 2参数,并且(当然)最多n个字节的参数文本(不包括终结符)。

这是Windows和Unix的解决方案(在Linux,OSX和Windows上测试)。 与Valgrind和记忆博士一起测试。

它使用wordexp用于POSIX系统,而CommandLineToArgvW用于Windows。

请注意,对于Windows解决方案,大多数代码都使用漂亮的Win32 API在char **wchar_t **之间进行转换,因为没有可用的CommandLineToArgvA (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; } 

我刚刚为普通C中的嵌入式项目做了这个,我有一个CLI来解析串口输入并使用参数执行一组有限的命令。

这可能不是最好的,但是我可以得到它的小而有效:

 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; } 

去测试:

 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); } 

Matt Peitrek的LIBTINYC有一个名为argcargv.cpp的模块,它接受一个字符串并将其解析为参数数组,并将引用的参数考虑在内。 请注意,它是特定于Windows的,但它非常简单,因此应该很容易移动到您想要的任何平台。

我最后写了一个函数来自己做这个,我觉得它不是很好,但它适用于我的目的 – 随意建议改进其他需要这个的人:

 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; } 

那些不想使用动态内存分配的解决方案(例如嵌入式)

我为嵌入式项目编写了tokenise_to_argc_argv() ,它使用strtok_r()作为将命令字符串标记为argc和argvforms的基础。 与此处的大多数答案不同,我通常静态分配内存。 因此,我的实现假设您有一个argv_length的上限。 对于大多数典型的嵌入式应用程序,这已经足够了。 我在下面也包含了示例代码,因此您可以快速使用它。

 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 } 

注意:

  • 提供的字符缓冲区必须是可修改的(因为strtok_r()将\0插入缓冲区以分隔字符串标记)。
  • 此函数中的strtok_r当前使用" "空格字符作为唯一的分隔符。 这模拟了典型命令行界面中的行为main(int argc, char *argv[])
  • 此函数不使用malloc或calloc,而是必须单独分配argv数组,并显式提供argv的长度。 这是因为我打算在嵌入式设备中使用它,因此宁愿手动分配它。
  • strtok_r()因为它是线程安全的(因为strtok()使用内部静态指针)。 它也是标准C库string.h一部分,因此非常便携。

下面是演示代码及其输出。 此外,这表明tokenise_to_argc_argv()可以处理大多数字符串情况,因此已经过测试。 此函数也不依赖于malloc或calloc,因此适用于嵌入式用法(在使用stdint.h类型之后)。


示范代码

 /******************************************************************************* 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]); } 

产量

tokenise_to_argc_argv()示例

案例0:正常

  • 初始状态
    • 缓冲区内容tokenising example0 | HEX:74 6F 6B 65 6E 69 73 69 6E 67 20 65 78 61 6D 70 6C 65 00
  • 标记后
    • 缓冲区内容tokenising0example0 | HEX:74 6F 6B 65 6E 69 73 69 6E 67 00 65 78 61 6D 70 6C 65 00
  • Argv内容 (argc = 2):
    • argv[0] = tokenising
    • argv[1] = example

案例1:空字符串

  • 初始状态
    • 缓冲内容0 | 十六:00
  • 标记后
    • 缓冲内容0 | 十六:00
  • Argv内容 (argc = 0):Argv为空

案例2:额外空间

  • 初始状态
    • 缓冲内容extra space here0 | HEX:65 78 74 72 61 20 20 73 70 61 63 65 20 68 65 72 65 00
  • 标记后
    • 缓冲区内容extra0 space0here0 | HEX:65 78 74 72 61 00 20 73 70 61 63 65 00 68 65 72 65 00
  • Argv内容 (argc = 3):
    • argv[0] = extra
    • argv[1] = space
    • argv[2] = here

案例3:一个单词串

  • 初始状态
    • 缓冲内容one-word0 | HEX:6F 6E 65 2D 77 6F 72 64 00
  • 标记后
    • 缓冲内容one-word0 | HEX:6F 6E 65 2D 77 6F 72 64 00
  • Argv内容 (argc = 1):
    • argv[0] = one-word

某种程度上在工具链中缺少string.h或strtok_r()?

如果由于某种原因你的工具链没有strtok_r()。 您可以使用strtok_r()的简化版本。 它是strtok_r()的GNU C实现的修改版本,但简化为仅支持空格字符。

要使用它,只需将它放在tokenise_to_argc_argv()之上,然后用strtok_r( NULL, " ", &buffer)替换strtok_r( NULL, " ", &buffer) 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; } 

不幸的是C ++,但对于其他人可能会搜索这种类型的库我推荐:

ParamContainer – 易于使用的命令行参数解析器

真的很小很容易。

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

programname –long-name = value

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

根据我的经验:

  • 非常有用和简单
  • 生产稳定
  • 经过充分测试(由我)

考虑另一种实现。 跑 。

 #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 

使用代码

 #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."< 

输出:

 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