Home > Mobile >  Parse command line arguments string into array for posix_spawn/execve
Parse command line arguments string into array for posix_spawn/execve

Time:10-26

Given single string cmd representing program command line arguments, how to get array of strings argv, that can be passed to posix_spawn or execve.

Various forms of quoting (and escaping quotes) should be processed appropriately (resulting invocation should be same as in POSIX-compatible shell). Support for other escape characters would be desirable. Examples: #1, #2, #3.

CodePudding user response:

As Shawn commented, in Linux and other POSIXy systems, you can use wordexp(), which is provided as part of the standard C library on such systems. For example, run.h:

#ifdef __cplusplus
extern "C" {
#endif

/* Execute binary 'bin' with arguments from string 'args';
   'args' must not be NULL or empty.
   Command substitution (`...` or $(...)$) is NOT performed.
   If 'bin' is NULL or empty, the first token in 'args' is used.
   Only returns if fails.  Return value:
     -1: error in execv()/execvp(); see errno.
     -2: out of memory. errno==ENOMEM.
     -3: NULL or empty args.
     -4: args contains a command substitution. errno==EINVAL.
     -5: args has an illegal newline or | & ; < > ( ) { }. errno==EINVAL.
     -6: shell syntax error. errno==EINVAL.
   In all cases, you can use strerror(errno) for a descriptive string.
*/
int run(const char *bin, const char *args);

#ifdef __cplusplus
}
#endif

and compile the following C source to an object file you link into your C or C program or library:

#define  _XOPEN_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <wordexp.h>
#include <string.h>
#include <errno.h>

int run(const char *bin, const char *args)
{
    /* Empty or NULL args is an invalid parameter. */
    if (!args || !*args) {
        errno = EINVAL;
        return -3;
    }

    wordexp_t  w;

    switch (wordexp(args, &w, WRDE_NOCMD)) {
    case 0: break;  /* No error */
    case WRDE_NOSPACE: errno = ENOMEM; return -2; 
    case WRDE_CMDSUB:  errno = EINVAL; return -4;
    case WRDE_BADCHAR: errno = EINVAL; return -5;
    default:           errno = EINVAL; return -6;
    }

    if (w.we_wordc < 1) {
        errno = EINVAL;
        return -3;
    }

    if (!bin || !*bin)
        bin = w.we_wordv[0];

    if (!bin || !*bin) {
        errno = ENOENT;
        return -1;
    }

    /* Note: w.ve_wordv[w.we_wordc] == NULL, per POSIX. */

    if (strchr(bin, '/'))
        execv(bin, w.we_wordv);
    else
        execvp(bin, w.we_wordv);

    return -1;
}

For example, run(NULL, "ls -laF $HOME"); will list the contents of the current user's home directory. Environment variables will be expanded.

run("bash", "sh -c 'date && echo'"); executes bash, with argv[0]=="sh", argv[1]=="-c", and argv[2]=="date && echo". This lets you control what binary will be executed.

  • Related