2009-10-02 10 views
7

Ho una stringa std :: contenente un comando da eseguire con execv, qual è il miglior modo "C++" per convertirlo in "char * argv []" richiesto dal secondo parametro di execv()?converte stringa in argv in C++

per chiarire:

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

risposta

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

Naturalmente, questo non funzionerà con commans che utilizzano citando come rm "a file.mp3". Puoi considerare la funzione POSIX wordexp a cui importa questo e molto altro.

+0

I comandi con le virgolette sono esattamente il motivo per cui execv accetta un array - per evitare di prendere la decisione quali dovrebbero essere le regole di quoting. A meno che l'interrogante non dica le regole di quotazione che desidera, non possiamo rispondere alla domanda e, quando le ha specificate correttamente, la risposta sarà "generare un parser per la grammatica che hai appena specificato" ;-) –

+0

@onebyone true probabilmente a un certo punto del programma, aveva già gli argomenti in un array - non lo sappiamo. Ma se ha solo una stringa, e deve fare qualcosa di simile a dividerlo per qualche scopo come il logging o il controllo, è sempre utile sapere di 'wordexp 'ecc. Sono d'accordo che in generale, la semplice pressione su' sh' è un buona idea. –

0

È possibile utilizzare la funzione c_str() di std :: string per convertire a char *. La funzione strtok divide la stringa usando il delimitatore ''.

+0

È necessario dividere la stringa per spazi prima di passare a 'execv'. Richiede argomenti da passare come elementi separati nella matrice. –

+0

Sì, è vero. Non avevo l'esempio di codice quando scrivevo la risposta. –

2

Una combinazione del metodo stringa c_str() e strtok() per dividerlo per spazi dovrebbe ottenere l'array di stringhe che è necessario passare a exec() e alle relative funzioni.

+1

Questo potrebbe andar bene se non si intende sovvertire il quoting per consentire a un singolo argomento di contenere spazi. –

+0

Beh, sì, devi stare attento. Citando è una lattina completamente nuova di worm. –

1

Forse split_winmain da Boost.ProgramOptions. Boost è una buona scelta nella maggior parte dei casi. http://www.boost.org/doc/libs/1_40_0/doc/html/program_options/howto.html#id1396212

Se si è interessati solo in Windows (altri kernel generalmente non conoscono righe di comando nel senso di Windows), è possibile utilizzare la funzione API CommandLineToArgvW che utilizza le stesse convenzioni come il runtime MS C.

In generale, dipende dallo stile di quotatura della piattaforma e/o della shell. Microsoft C Runtime utilizza uno stile completamente diverso rispetto ad es. bash!

10

Risposte molto non unificate qui. Cosa c'è di sbagliato:

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

perché preoccuparsi scrivere un parser riga di comando quando c'è una perfetta buona già nel sistema?

(Nota: una buona ragione è perché non ti fidi della stringa che stai per eseguire. Si spera che questo sia già vero, ma la shell farà "di più" con quella stringa di un ingenuo spazio bianco- splitter e quindi aprire più buchi di sicurezza, se non si sta attenti.)

+0

Cosa succede se non ce n'è uno sul sistema? –

+0

Un sistema con la chiamata di sistema execve() ma nessuna shell? Mai sentito parlare di una tale bestia. –

+0

quando eseguo un programma di test con solo le due righe sopra, e cmd = "echo hello world", la chiamata execl non ha esito positivo, restituisce. L'errore è 2. quando cambio il primo argomento da "sh" a "/ bin/sh", mi dà l'errore "-c: echo ciao mondo: nessun file o directory" – aaronstacy

0

LIBTINYC di Matt Peitrek ha un modulo chiamato argcargv.cpp che accetta una stringa e la analizza nell'array degli argomenti prendendo in considerazione gli argomenti citati. Nota che è specifico per Windows, ma è piuttosto semplice, quindi dovrebbe essere facile spostarsi su qualsiasi piattaforma tu voglia.

Se lo si fa, anche cambiarlo per prendere come parametri lo loaction per mettere il conteggio e il puntatore alla matrice argv invece di usare externs (solo il mio piccolo consiglio). Matt non ne aveva bisogno perché LIBTINYC era il runtime.

In alternativa, è possibile cercare nell'origine di runtime del compilatore (quasi tutti lo forniscono) per vedere che cosa fanno per analizzare la riga di comando e chiamarlo direttamente (se ciò risulta essere praticabile) o prendere in prestito le idee da quel bit di codice.

1

Questa è una variazione della risposta di litb, ma senza l'allocazione manuale della memoria. Non gestirà ancora citazioni.

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

mi sento tipo di sporco sulla const_cast <>, ma i programmi non dovrebbe essere di modificare il contenuto delle stringhe argv.

+0

Se 'v1' cresce, tutti i puntatori in' v2' possono essere invalidati. –

+0

@BenJackson grazie, cambiato in una lista. –

1

OK, sono stato io a inciampare su questo me stesso abbastanza volte. Questa è una "C" diritta, quindi può essere inserita in C o C++. Tratta le stringhe di virgolette singole e doppie in modo diverso. Il chiamante è responsabile della deallocazione di argv [0] (se non NULL) e 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

Forse è troppo tardi per rispondere a questa domanda, ma è possibile utilizzare le funzioni di 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); 
} 

Sarebbe preparare 3 parametri: -l (formato lungo messa in vendita), -t (ordina per tempo di modifica) e la directory /etc per elencare ed eseguire /bin/ls. Chiama wordexp() ti dà esattamente lo stesso risultato della chiamata /bin/sh -c consigliato in precedenza ma il processo spawaned avrebbe il processo padre non /bin/sh.