Разобрать строку в argv / argc - PullRequest
30 голосов
/ 10 ноября 2009

Есть ли в C способ проанализировать фрагмент текста и получить значения для argv и argc, как если бы текст был передан приложению из командной строки?

Это не должно работать на Windows, только на Linux - мне также не важно цитировать аргументы.

Ответы [ 12 ]

28 голосов
/ 03 октября 2010

Я удивлен, что никто не предоставил самый простой ответ, используя стандартные функции POSIX:

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

15 голосов
/ 08 ноября 2012

Вот мой вклад. Это красиво и коротко, но нужно с осторожностью относиться:

  • Использование 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)".

11 голосов
/ 10 ноября 2009

Если решение glib является чрезмерным для вашего случая, вы можете подумать о его кодировании самостоятельно.

Тогда вы можете:

  • просканируйте строку и посчитайте, сколько аргументов есть (и вы получите свой аргумент)
  • выделить массив 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 (и перераспределение при необходимости).

РЕДАКТИРОВАТЬ: Предлагаемое решение (не обрабатывать аргумент в кавычках).

    #include <stdio.h>

    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);
    }
9 голосов
/ 10 ноября 2009

Всегда замечательный glib имеет <a href="http://library.gnome.org/devel/glib/stable/glib-Shell-related-Utilities.html#g-shell-parse-argv" rel="noreferrer">g_shell_parse_args()</a>, что звучит как то, что вы ищете.

Если вы не заинтересованы даже в цитировании, это может быть излишним. Все, что вам нужно сделать, это токенизировать, используя пробел в качестве символа токена. Написание простой рутины для этого не займет много времени, на самом деле.

Если вы не слишком скупы на память, сделать это за один проход без перераспределения должно быть легко; просто предположим, что каждый второй символ является пробелом в худшем случае, предполагая, что строка из n символов содержит не более (n + 1) / 2 аргументов и (конечно) не более n байтов текста аргумента (исключая терминаторы).

7 голосов
/ 01 марта 2015

Вот решение для Windows и Unix (проверено на Linux, OSX и Windows). Протестировано с Valgrind и Dr. Память .

Используется wordexp для систем POSIX и CommandLineToArgvW для Windows.

Обратите внимание, что для решения Windows большая часть кода преобразуется между char ** и wchar_t ** с прекрасным Win32 API, поскольку CommandLineToArgvA (ANSI-версия) недоступна.

#ifdef _WIN32
#include <windows.h>
#else
#include <wordexp.h>
#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;
}
3 голосов
/ 15 июня 2014

Я только что сделал это для встроенного проекта на простом 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);
}
2 голосов
/ 10 ноября 2009

Я закончил тем, что написал функцию, чтобы сделать это сам, я не думаю, что это очень хорошо, но она работает для моих целей - не стесняйтесь предлагать улучшения для тех, кто нуждается в этом в будущем:

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;
}
2 голосов
/ 10 ноября 2009

Matt Peitrek's LIBTINYC имеет модуль argcargv.cpp, который принимает строку и анализирует ее в массиве аргументов с учетом аргументов в кавычках. Обратите внимание, что это зависит от Windows, но это довольно просто, поэтому должно быть легко перейти на любую платформу, которую вы хотите.

1 голос
/ 10 февраля 2019

Этот, который я написал, также рассматривает кавычки (но не вложенные)

Не стесняйтесь вносить свой вклад.

/*
Tokenize string considering also quotes.
By Zibri <zibri AT zibri DOT org>
https://github.com/Zibri/tokenize
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

int main(int argc, char *argv[])
{
  char *str1, *token;
  int j;
  char *qstart = NULL;
  bool quoted = false;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s string\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  for (j = 1, str1 = argv[1];; j++, str1 = NULL) {
    token = strtok(str1, " ");
    if (token == NULL)
      break;
    if ((token[0] == 0x27) || (token[0] == 0x22)) {
      qstart = token + 1;
      quoted = true;
    }
    if ((token[strlen(token) - 1] == 0x27) || (token[strlen(token) - 1] == 0x22)) {
      quoted = false;
      token[strlen(token) - 1] = 0;
      printf("%d: %s\n", j, qstart);
    } else {
      if (quoted) {
        token[strlen(token)] = 0x20;
        j--;
      } else
        printf("%d: %s\n", j, token);
    }
  }

  if (quoted) {
    fprintf(stderr, "String quoting error\n");
    return EXIT_FAILURE;
  } else
    return EXIT_SUCCESS;
}

Пример вывода:

$ ./tokenize "1 2 3 '4 5 6' 7 8 \"test abc\" 10 11"
1: 1
2: 2
3: 3
4: 4 5 6
5: 7
6: 8
7: test abc
8: 10
9: 11
1 голос
/ 26 июля 2017

Решение для тех, кто не хочет использовать динамическое выделение памяти (например, встроенный)

Я написал tokenise_to_argc_argv() для встроенного проекта, который использует strtok_r() в качестве основы для токенизации командной строки в форме argc и argv. В отличие от большинства ответов здесь, я обычно выделяю память статически. Таким образом, моя реализация предполагает, что у вас есть верхняя граница 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 <stdio.h>  // printf()
#include <ctype.h>  // isprint()
#include <string.h> // 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() Примеры

Case0: Normal

  • Исходное состояние
    • Буферный контент 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

Case1: пустая строка

  • Исходное состояние
    • Буферный контент 0 | HEX: 00
  • После токенизации
    • Буферный контент 0 | HEX: 00
  • Содержание Argv (argc = 0): Argv Is Empty

Case2: дополнительное пространство

  • Исходное состояние
    • Буферный контент 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_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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...