Parse command line arguments string into array for posix_spawn/execve

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.

1 answer

  • answered 2021-10-24 21:02 Blabbo the Verbose

    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.

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum