Analizar cadena en argv/argc

11 minutos de lectura

avatar de usuario
caja de código

¿Hay alguna forma en C de analizar un fragmento de texto y obtener valores para argv y argc, como si el texto se hubiera pasado a una aplicación en la línea de comandos?

Esto no tiene que funcionar en Windows, solo en Linux; tampoco me importa citar argumentos.

  • ¿Para qué plataforma? La forma en que las líneas de comando se analizan en argc/argv es bastante diferente entre los sistemas basados ​​en Windows y UNIX, por ejemplo. En UNIX, el shell generalmente transforma la línea de comandos de manera significativa, lo que incluye hacer globbing (expansión de patrón de archivo) y sustitución de variables. En Windows, la expansión del patrón de archivo no la realiza el shell (a menos que esté usando algo como cygwin o MKS Toolkit, por supuesto).

    – Laurence Gonsalves

    10 de noviembre de 2009 a las 9:18

  • Si ni siquiera necesita manejar argumentos entre comillas, realmente sugeriría codificar su propia función en lugar de introducir una biblioteca de terceros solo para esta tarea.

    – Remo.D

    10 de noviembre de 2009 a las 10:15

  • ¿Intentaste getopt()? (hombre 3 getopt). Puede ver la mayoría de las fuentes de herramientas estándar de UNIX/Linux para ver ejemplos, una GRAN cantidad de ellos. Incluso la página de manual (al menos una de Linux) contiene un ejemplo decente. También hay una cantidad de envoltorios (ver recomendaciones aquí) pero getopt() parece ser el único disponible para CUALQUIER plataforma UNIX (en realidad parece ser parte del estándar POSIX).

    – Roman Nikitchenko

    10 de noviembre de 2009 a las 11:26

  • Si todavía está interesado y quiere fuerza industrial desde cero, en un paquete de código pequeño. Buscar en esta página para nargv De lejos, la mejor solución que he visto aquí desde el código c puro. ¡Vote esta respuesta! Para que otros puedan encontrarlo.

    usuario735796

    9 de abril de 2012 a las 10:32

  • @ user735796 Busqué nargv y tu comentario es el único acierto. Así que busqué en Google: github.com/hypersoft/nargv … Sin embargo, algunos comentarios. Esto usa C99, por lo que no funcionará en el compilador de Microsoft C. También una idea es tener pruebas unitarias con un montón de casos de prueba que verifiquen cada tipo de escenario para que el analizador verifique que funcione como se espera.

    – Joaquín

    1 de marzo de 2015 a las 11:14

Me sorprende que nadie haya proporcionado la respuesta más simple usando la funcionalidad POSIX estándar:

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

  • Eso puede hacer más de lo que quieres. Por ejemplo, hace expansiones de palabras de shell, incluida la sustitución de variables de entorno, por ejemplo, sustituyendo $PATH con el camino actual.

    –Craig McQueen

    3 de abril de 2013 a las 0:57

  • Supongo que depende de lo que quieras decir con analizar en argc/argv; ciertamente eso implica algo de lo que hace el shell (procesamiento de cotizaciones), pero la expansión variable y otras cosas son más cuestionables. por cierto wordexp tiene una opción para deshabilitar la expansión de comandos.

    – R.. GitHub DEJA DE AYUDAR A ICE

    3 de abril de 2013 a las 1:49

  • Si te refieres a WRDE_NOCMDeso no parece impedir la expansión de $PATHni en expansión * a los nombres de los archivos en el directorio actual.

    –Craig McQueen

    3 de abril de 2013 a las 1:57

  • No dije que impidiera la expansión variable, solo que otra cosa que quizás desee desactivar, la expansión de comandos, se puede desactivar.

    – R.. GitHub DEJA DE AYUDAR A ICE

    3 de abril de 2013 a las 2:21

  • Esto es exactamente lo que estaba buscando y parece funcionar muy bien. Lo necesitaba para pasar un comando definido por el usuario a posix_spawn, sin saber si habría argumentos adicionales. Sin embargo, un ejemplo de código corto haría que esta respuesta fuera mucho mejor. Sí, incluso ahora, más de siete años después. 🙂

    – domsson

    19 de febrero de 2018 a las 20:43


avatar de usuario
Remo.D

Si la solución simplista es excesiva para su caso, puede considerar codificar una usted mismo.

Entonces tú puedes:

  • escanee la cadena y cuente cuántos argumentos hay (y obtendrá su argc)
  • asigne una matriz de char * (para su argv)
  • vuelva a escanear la cadena, asigne los punteros en la matriz asignada y reemplace los espacios con ‘\0’ (si no puede modificar la cadena que contiene los argumentos, debe duplicarla).
  • ¡No olvides liberar lo que has asignado!

El siguiente diagrama debería aclarar (con suerte):

             aa bbb ccc "dd d" ee         <- original string

             aa0bbb0ccc00dd d00ee0        <- transformed string
             |  |   |    |     |
   argv[0] __/  /   /    /     /
   argv[1] ____/   /    /     /
   argv[2] _______/    /     /
   argv[3] ___________/     /
   argv[4] ________________/ 

Una posible API podría ser:

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

Necesitará consideraciones adicionales para implementar freeparsedargs() de forma segura.

Si su cadena es muy larga y no desea escanear dos veces, puede considerar alternativas como asignar más elementos para las matrices argv (y reasignar si es necesario).

EDITAR: Solución propuesta (no maneja el argumento citado).

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

  • porque getopt hace un trabajo diferente. Toma una serie de argumentos y busca opciones en él. Esta pregunta se trata de dividir una cadena de “argumentos” en una matriz de char * que es algo que getopt no puede hacer

    – Remo.D

    10 de noviembre de 2009 a las 20:28

  • Si transforma una cadena de entrada de esa manera, no puede hacer una concatenación de cadenas con comillas” como “esto” o “esto”. Vea mi respuesta para una solución con todas las funciones.

    usuario735796

    09/04/2012 a las 18:32

  • (quisquilloso por delante) Tenga en cuenta que falta una pequeña cosa para cumplir con el estándar argc/argv disposición: La entrada detrás de la última válida en argv siempre está configurado para NULL ("foo bar": argv[0] -> "foo", argv[1] -> "bar", argv[2] -> NULL).

    –Max Truxa

    20 de octubre de 2014 a las 13:11


avatar de usuario
sstteevvee

Aquí está mi contribución. Es agradable y breve, pero las cosas a tener en cuenta son:

  • El uso de strtok modifica la cadena original de “línea de comandos”, reemplazando los espacios con delimitadores de fin de cadena \0
  • argv[] termina apuntando a “línea de comandos”, así que no lo modifique hasta que haya terminado con argv[].

El código:

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;

Ahora puede usar argc y argv, o pasarlos a otras funciones declaradas como “foo(int argc, char **argv)”.

  • Gracias, eso ahorró algo de tiempo. Para cualquier otra persona que use esto: “char* p1” (aunque su compilador se lo habría dicho =])

    – jrr

    30 de septiembre de 2013 a las 21:02

  • ¿Cuenta esto con argumentos escapados como “alguna/ruta larga/al archivo.txt”?

    – Gredo

    17 de marzo a las 15:19

  • No, tendrías que buscar y manejar las cotizaciones tú mismo después. Si su código se ejecuta en un sistema operativo “real”, le recomiendo ver lo que ofrece. Por ejemplo, el simplista como se sugiere en otra solución, que debería brindarle ese tipo de características.

    – sstteevvee

    19 de marzo a las 8:58


avatar de usuario
relajarse

La siempre maravillosa fácil posee g_shell_parse_args() que suena como lo que buscas.

Si ni siquiera está interesado en citar, esto podría ser excesivo. Todo lo que necesita hacer es tokenizar, usando espacios en blanco como carácter simbólico. Escribir una rutina simple para hacer eso no debería tomar mucho tiempo, de verdad.

Si no es muy tacaño con la memoria, hacerlo de una sola vez sin reasignaciones debería ser fácil; simplemente asuma el peor de los casos de que cada segundo carácter sea un espacio, suponiendo así una cadena de n caracteres contiene como máximo (n + 1) / 2 argumentos, y (por supuesto) como máximo n bytes de texto de argumento (excluyendo terminadores).

avatar de usuario
Joaquín

Aquí hay una solución para Windows y Unix (probada en Linux, OSX y Windows). Probado con Valgrind y Dr. Memoria.

Usa wordexp para sistemas POSIX, y CommandLineToArgvW para ventanas.

Tenga en cuenta que para la solución de Windows, la mayor parte del código se convierte entre char ** y wchar_t ** con la hermosa API Win32, ya que no hay CommandLineToArgvA disponible (versión 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;
}

avatar de usuario
Y yo

Acabo de hacer esto para un proyecto incrustado en C simple, donde tengo un pequeño CLI que analiza la entrada del puerto serie y ejecuta un conjunto limitado de comandos con los parámetros.

Probablemente este no sea el más ordenado, pero sí el más pequeño y eficiente que pude conseguir:

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

Probar:

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

avatar de usuario
miguel rebabas

de Matt Peitrek LIBTINYC tiene un módulo llamado argcargv.cpp que toma una cadena y la analiza en la matriz de argumentos teniendo en cuenta los argumentos citados. Tenga en cuenta que es específico de Windows, pero es bastante simple, por lo que debería ser fácil de mover a cualquier plataforma que desee.

  • Con el pequeño problema de que es C++ y no C 🙂

    – Remo.D

    10 de noviembre de 2009 a las 9:14

  • Cambie el nombre del archivo a argcargv.c y es C. Literalmente.

    – Michael Burr

    10 de noviembre de 2009 a las 15:10

  • La biblioteca del Sr. Peitrek parece ser muy débil en comparación con las reglas reales de Microsoft para separar una línea de comando en argc/argv (ver msdn.microsoft.com/en-us/library/17w5ykft.aspx para sus reglas.) No parece manejar cadenas entrecomilladas incrustadas, múltiples barras invertidas o incluso caracteres de comillas escapados. No hay problema si eso no es necesario, por supuesto, ¡pero la gente debe asegurarse de obtener lo que necesita!

    – Steve Valliere

    23 mayo 2013 a las 15:07

  • Además, es totalmente innecesario ya que Microsoft no solo le brinda la especificación de cómo analizan la línea de comando, sino que también brindan una API para esto: CommandLineToArgvW

    – Joaquín

    28 de febrero de 2015 a las 23:20


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad