Подтвердить что ты не робот

Разбирайте строку в argv/argc

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

Это не нужно работать в Windows, просто в Linux - мне также не нужно указывать аргументы.

4b9b3361

Ответ 1

Если 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 (и перераспределение при необходимости).

EDIT: предлагаемое решение (дескриптор не указан).

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

Ответ 3

Вот мой вклад. Его приятный и короткий, но нужно быть осторожным:

  • Использование strtok изменяет исходную строку "commandLine", заменяя пробелы метками конца строки
  • 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)".

Ответ 4

Всегда замечательный glib имеет g_shell_parse_args(), который звучит так, как вы.

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

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

Ответ 5

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

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

Обратите внимание, что для решения Windows большая часть кода конвертирует между char ** и wchar_t ** с красивым API Win32, поскольку нет 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;
}

Ответ 6

Я просто сделал это для встроенного проекта в простой 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);
}

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

Я написал 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() Примеры

Случай 0: Обычный

  • Начальное состояние
    • Содержимое буфера tokenising example0 | HEX: 74 6F 6B 65 6E 69 73 69 6E 67 20 65 78 61 6D 70 6C 65 00
  • После Tokenizing
    • Содержимое буфера 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 | HEX: 00
  • После Tokenizing
    • Содержимое буфера 0 | HEX: 00
  • Содержимое Argv (argc = 0): Argv Empty

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

  • Начальное состояние
    • Содержимое буфера extra space here0 | HEX: 65 78 74 72 61 20 20 73 70 61 63 65 20 68 65 72 65 00
  • После Tokenizing
    • Содержимое буфера 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

Case3: строка из одного слова

  • Начальное состояние
    • Содержимое буфера one-word0 | HEX: 6F 6E 65 2D 77 6F 72 64 00
  • После Tokenizing
    • Содержимое буфера one-word0 | HEX: 6F 6E 65 2D 77 6F 72 64 00
  • Содержание Argv (argc = 1):
    • argv[0]= one-word

Отсутствует string.h или strtok_r() в вашей инструментальной цепочке?

Если по какой-то причине ваша toolchain не имеет strtok_r(). Вы можете использовать эту упрощенную версию strtok_r(). Это модифицированная версия реализации GNU C strtok_r(), но упрощена для поддержки только символа пространства.

Чтобы использовать это, просто поместите его поверх 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;
}

Ответ 10

К сожалению, С++, но для других, которые могут искать такую ​​библиотеку, я рекомендую:

ParamContainer - простой в использовании синтаксический анализатор параметров командной строки

Действительно маленький и очень простой.

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

имя_программы --long-name = значение

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

Из моего опыта:

  • очень полезно и просто
  • стабильный при производстве
  • хорошо протестирован (мной)

Ответ 11

Рассмотрим еще одну реализацию. Запустить.

#include <cctype>  // <ctype.h>  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<argc_MAX-1  &&  splitArgv(&str, &argv[*argc]) ){
        ++(*argc);
        if( *str == '\0' )
            break;
    }
    argv[*argc] = nullptr;
    return str;
};

Код использования

#include <iostream>
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."<<endl;

    cout << "argc : "<< c << endl;
    for( int i=0; i<c; i++ )
        cout<<"argv["<<i<<"] : "<<v[i] <<endl;
    /*//or condition is `v[i]`
    for( int i=0; v[i]; i++ )
        cout<<"argv["<<i<<"] : "<<v[i] <<endl;*/
}



int main(int argc, char* argv[])
{
    char inputs[][500] ={
              "Just another TEST\r\n"
            , "  Hello my world 'in quotes' \t !"
            , "./hi 'Less is more'"
            , "Very long line with \"double quotes\" should be parsed several times if argv[] buffer is small"
            , "   \t\f \r\n"
    };

    for( int i=0; i<5; ++i ){
        cout<<"Parsing line \""<<inputs[i]<<"\":"<<endl;
        parseAndPrintOneString(inputs[i]);
        cout<<endl;
    }
}

Вывод:

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