2009-10-02 34 views
7

Tengo una std :: cadena que contiene un comando para ejecutarse con execv, ¿cuál es la mejor forma de "C++" para convertirlo a "char * argv []" que es requerido por el segundo parámetro de execv()?convertir cadena a argv en C++

Para aclarar:

std::string cmd = "mycommand arg1 arg2"; 
char *cmd_argv[]; 

StrToArgv(cmd, cmd_argv); // how do I write this function? 

execv(cmd_argv[0], cmd_argv); 

Respuesta

5
std::vector<char *> args; 
std::istringstream iss(cmd); 

std::string token; 
while(iss >> token) { 
    char *arg = new char[token.size() + 1]; 
    copy(token.begin(), token.end(), arg); 
    arg[token.size()] = '\0'; 
    args.push_back(arg); 
} 
args.push_back(0); 

// now exec with &args[0], and then: 

for(size_t i = 0; i < args.size(); i++) 
    delete[] args[i]; 

Por supuesto, esto no funcionará con Commans que utilizan citando como rm "a file.mp3". Puede considerar la función POSIX wordexp que se preocupa por eso y mucho más.

+0

Los comandos con comillas son exactamente la razón por la cual execv toma una matriz, para evitar tomar la decisión de cuáles deberían ser las reglas de cotización. A menos que el interlocutor diga qué reglas de cita quiere, no podemos responder a la pregunta, y para cuando las haya especificado correctamente, la respuesta será "generar un analizador para la gramática que acaba de especificar" ;-) –

+0

@onebyone true , probablemente en algún momento del programa, ya tenía los argumentos en una matriz, no lo sabemos. Pero si tiene solo una cadena, y tiene que hacer algo así como dividirla para algún propósito, como el registro o la auditoría, siempre es útil saber sobre 'wordexp', etc. Estoy de acuerdo en que, en general, simplemente presionar para' sh' es una buena idea. –

0

Puede utilizar la función c_str() de std :: string para convertir a char *. La función strtok dividirá la cadena con el delimitador ''.

+0

Tienes que dividir la cadena por espacios antes de pasar a 'execv'. Requiere que los argumentos se pasen como elementos separados en la matriz. –

+0

Sí, eso es correcto. No tenía el ejemplo de código al escribir la respuesta. –

2

Una combinación del método de cadena c_str() y strtok() para dividirla por espacios debe obtener la matriz de cadenas que debe pasar a exec() y sus funciones relacionadas.

+1

Esto podría estar bien si no tiene la intención de publicar citas para permitir que un solo argumento contenga espacios. –

+0

Bueno, sí, debes tener cuidado. Citar es una nueva lata de gusanos. –

1

Quizás split_winmain de Boost.ProgramOptions. Boost es una buena opción en la mayoría de los casos. http://www.boost.org/doc/libs/1_40_0/doc/html/program_options/howto.html#id1396212

Si usted está interesado sólo en Windows (otros granos en general, no saben de líneas de comandos en el sentido de Windows), puede utilizar la función de API CommandLineToArgvW que utiliza las mismas convenciones que el tiempo de ejecución de MS C.

En general, depende del estilo de cotización de la plataforma y/o el depósito. Microsoft C Runtime utiliza un estilo bastante diferente de, p. Ej. ¡intento!

10

Respuestas muy no únicas aquí. ¿Qué tiene de malo:

std::string cmd = "echo hello world"; 
execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), NULL); 

¿por qué preocuparse de escribir un analizador de línea de comandos cuando hay un perfectamente bueno que ya están en el sistema?

(Nota: una buena razón es porque no confía en la cadena que está por ejecutar. Se espera que esto ya sea cierto, pero la shell hará "más" con esa cadena que un espacio en blanco ingenuo- divisor de la voluntad y de este modo abrir más agujeros de seguridad si no se tiene cuidado.)

+0

¿Qué pasa si no hay uno en el sistema? –

+0

Un sistema con la llamada al sistema execve() pero sin shell? Nunca he oído hablar de semejante bestia. –

+0

cuando ejecuto un programa de prueba con solo las dos líneas de arriba, y cmd = "echo hello world", la llamada execl no tiene éxito, regresa. El errno es 2. cuando cambio el primer argumento de "sh" a "/ bin/sh", me da el error "-c: echo hello world: no existe ese archivo o directorio" – aaronstacy

0

Matt Peitrek's 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 debe ser fácil de mover a la plataforma que desee.

Si lo hace, también cámbielo para tomar como parámetros la ubicación para poner el conteo y el puntero a la matriz argv en lugar de usar externos (solo un poquito de consejo). Matt no lo necesitaba porque LIBTINYC era el tiempo de ejecución.

Alternativamente, puede buscar en el origen de tiempo de ejecución de su compilador (casi todos lo proporcionan) para ver qué hacen para analizar la línea de comandos y llamar directamente (si resulta viable) o tomar las ideas de ese bit de código.

1

Esta es una variación de la respuesta de litb, pero sin toda la asignación de memoria manual. Todavía no manejará las citas.

#include <vector> 
#include <string> 
#include <sstream> 

std::string cmd = "mycommand arg1 arg2"; 
std::istringstream ss(cmd); 
std::string arg; 
std::list<std::string> ls; 
std::vector<char*> v; 
while (ss >> arg) 
{ 
    ls.push_back(arg); 
    v.push_back(const_cast<char*>(ls.back().c_str())); 
} 
v.push_back(0); // need terminating null pointer 

execv(v[0], &v[0]); 

me siento un poco sucio de la const_cast <>, pero los programas realmente no debería modificar el contenido de las cadenas argv.

+0

Si 'v1' crece, todos los punteros en' v2' pueden invalidarse. –

+0

@BenJackson gracias, cambiado a una lista. –

1

Bien, he tropezado con esto yo mismo bastantes veces. Esta es una "C" recta, por lo que se puede conectar a C o C++. Trata las cadenas de comillas simples y dobles de manera diferente. La persona que llama es responsable de la desasignación de argv [0] (si no es NULO) y argv.

#include 
#include 
#include 
#include 

typedef enum { 
    STR2AV_OK  = 0, 
    STR2AV_UNBALANCED_QUOTE 
} str_to_argv_err_t; 

#ifndef NUL 
#define NUL '\0' 
#endif 

static char const nomem[] = "no memory for %d byte allocation\n"; 

static str_to_argv_err_t 
copy_raw_string(char ** dest_p, char ** src_p); 

static str_to_argv_err_t 
copy_cooked_string(char ** dest_p, char ** src_p); 

static inline void * 
Xmalloc(size_t sz) 
{ 
    void * res = malloc(sz); 
    if (res == NULL) { 
     fprintf(stderr, nomem, sz); 
     exit(EXIT_FAILURE); 
    } 
    return res; 
} 

static inline void * 
Xrealloc(void * ptr, size_t sz) 
{ 
    void * res = realloc(ptr, sz); 
    if (res == NULL) { 
     fprintf(stderr, nomem, sz); 
     exit(EXIT_FAILURE); 
    } 
    return res; 
} 

str_to_argv_err_t 
string_to_argv(char const * str, int * argc_p, char *** argv_p) 
{ 
    int  argc = 0; 
    int  act = 10; 
    char ** res = Xmalloc(sizeof(char *) * 10); 
    char ** argv = res; 
    char * scan; 
    char * dest; 
    str_to_argv_err_t err; 

    while (isspace((unsigned char)*str)) str++; 
    str = scan = strdup(str); 

    for (;;) { 
     while (isspace((unsigned char)*scan)) scan++; 
     if (*scan == NUL) 
      break; 

     if (++argc >= act) { 
      act += act/2; 
      res = Xrealloc(res, act * sizeof(char *)); 
      argv = res + (argc - 1); 
     } 

     *(argv++) = dest = scan; 

     for (;;) { 
      char ch = *(scan++); 
      switch (ch) { 
      case NUL: 
       goto done; 

      case '\\': 
       if ((*(dest++) = *(scan++)) == NUL) 
        goto done; 
       break; 

      case '\'': 
       err = copy_raw_string(&dest, &scan); 
       if (err != STR2AV_OK) 
        goto error_leave; 
       break; 

      case '"': 
       err = copy_cooked_string(&dest, &scan); 
       if (err != STR2AV_OK) 
        goto error_leave; 
       break; 

      case ' ': 
      case '\t': 
      case '\n': 
      case '\f': 
      case '\r': 
      case '\v': 
      case '\b': 
       goto token_done; 

      default: 
       *(dest++) = ch; 
      } 
     } 

    token_done: 
     *dest = NUL; 
    } 

done: 

    *argv_p = res; 
    *argc_p = argc; 
    *argv = NULL; 
    if (argc == 0) 
     free((void *)str); 

    return STR2AV_OK; 

error_leave: 

    free(res); 
    free((void *)str); 
    return err; 
} 

static str_to_argv_err_t 
copy_raw_string(char ** dest_p, char ** src_p) 
{ 
    for (;;) { 
     char ch = *((*src_p)++); 

     switch (ch) { 
     case NUL: return STR2AV_UNBALANCED_QUOTE; 
     case '\'': 
      *(*dest_p) = NUL; 
      return STR2AV_OK; 

     case '\\': 
      ch = *((*src_p)++); 
      switch (ch) { 
      case NUL: 
       return STR2AV_UNBALANCED_QUOTE; 

      default: 
       /* 
       * unknown/invalid escape. Copy escape character. 
       */ 
       *((*dest_p)++) = '\\'; 
       break; 

      case '\\': 
      case '\'': 
       break; 
      } 
      /* FALLTHROUGH */ 

     default: 
      *((*dest_p)++) = ch; 
      break; 
     } 
    } 
} 

static char 
escape_convt(char ** src_p) 
{ 
    char ch = *((*src_p)++); 

    /* 
    * Escape character is always eaten. The next character is sometimes 
    * treated specially. 
    */ 
    switch (ch) { 
    case 'a': ch = '\a'; break; 
    case 'b': ch = '\b'; break; 
    case 't': ch = '\t'; break; 
    case 'n': ch = '\n'; break; 
    case 'v': ch = '\v'; break; 
    case 'f': ch = '\f'; break; 
    case 'r': ch = '\r'; break; 
    } 

    return ch; 
} 


static str_to_argv_err_t 
copy_cooked_string(char ** dest_p, char ** src_p) 
{ 
    for (;;) { 
     char ch = *((*src_p)++); 
     switch (ch) { 
     case NUL: return STR2AV_UNBALANCED_QUOTE; 
     case '"': 
      *(*dest_p) = NUL; 
      return STR2AV_OK; 

     case '\\': 
      ch = escape_convt(src_p); 
      if (ch == NUL) 
       return STR2AV_UNBALANCED_QUOTE; 
      /* FALLTHROUGH */ 

     default: 
      *((*dest_p)++) = ch; 
      break; 
     } 
    } 
}
0

puede ser que sea demasiado tarde para responder a esta pregunta, pero se podía utilizar las funciones del standart POSIX glob o wordexp:

#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <wordexp.h> 

int 
main(int argc, char **argv) 
{ 
    wordexp_t p; 
    char *exec_path = "/bin/ls"; 

    p.we_offs = 1; 
    wordexp("-l -t /etc", &p, WRDE_DOOFFS); 
    p.we_wordv[ 0 ] = exec_path; 
    execv(exec_path, p.we_wordv); 

    /* This code is unreachable */ 
    exit(EXIT_SUCCESS); 
} 

Se prepararía 3 parámetros: -l (formato extenso listado), -t (ordenar por tiempo de modificación) y directorio /etc a la lista, y ejecutar /bin/ls. Llamar al wordexp() le da exactamente el mismo resultado que la llamada /bin/sh -c recomendada anteriormente pero el proceso spawaned tendría el proceso principal no /bin/sh.