简体   繁体   中英

C Split CMD Arguments

I'm trying to do something but i can. I have a program in C in which i want to parse all the arguments. Let's be more specific

EDIT : I read the command in buffer and not when the program starts with argv etc. i read the command using fgets from STDIN

Let's say that i read a command line like this one:

ls -la

I want to save the list of the commands and arguments in n arrays in this format:

char ***command; // A list of lists of strings.

command[0][0] = "ls";
command[0][1] = "-l";
command[0][2] = "a";
command[0][3] = NULL;

I want to execute the above commands using execvp thats why i wanted in this format. Also take a look at the following example

ls -la | grep 1

The array must be the following:

command[0][0] = "ls";
command[0][1] = "-l";
command[0][2] = "a";
command[0][3] = NULL;
command[1][0] = "grep";
command[1][1] = 1;
command[1][2] = NULL;

So in a few words i want to split the commands and put them in 2D Array Based on the character | but maintain the command arguments as well.

I tried to do that using strtok (space char delimeter) and then store them in the array but i failed.

Can you help me please?

Thank you

The below answer shows how you can split up a C string just as the shell would do before passing it on to the main function as argc and argv . That is ls -la would be split into:

argv[0] == "ls"
argv[1] == "-la"

However since you want to include multiple command lines stuck together with pipe characters, you would first have to split the string at each | character. This can be done using strtok as you mention.

str = "ls -la|grep hello";
...
// Do split on |
...
strs[0] == "ls -la";
strs[1] == "grep hello";

And then further split those command lines into their own argv arrays. Then finally assemble those into the one array with NULL delimeters as you described.

Since you don't know the size of the final array when you start out. Or for that matter how many | signs there is, you could first of all count those and allocate an array holding all the argv s:

char **strs = NULL;
char **argvs = NULL;
size_t count = 0;
size_t i = 0;
while (*str) if (*str++ == '|') ++count;

strs = calloc(count, sizeof(char *));

...
// strtok on | and save tokens into strs[i]
...

Now split the command lines and append the NULL at the end:

// Code for split_commandline below.
for (i = 0; i < count; i++)
{
    argvs[i] = split_commandline(strs[i], &argc);

    // Make room for NULL at the end of the argv array.
    argvs[i] = realloc(argvs[i], (argc + 1) * sizeof(char *));
    argvs[i][argc] = NULL;
}

All a bit contrived and can of course be made to use storage, but this does it in clear steps.

NOTE : This does not split "ls -la" into "ls", "-l", "a" as the original question asked for, but rather "ls", "-la" . I'm not sure why this would be desired, but it would need hacks for individual commands since what "-la" means is specific to the ls program.

Splitting a command line

Unix/Linux You can use wordexp for this exact thing. However, not that this has some security implications you should be aware of. Namely it will expand shell variables and many if not all implementations of this results in a call to sh .

NOTE: It seems that OSX 10.9.5 leaks memory in wordexp even after wordfree is called. See Is wordexp in libc on OSX 10.9.5 known to leak? for details.

Windows I know this question was only tagged for Linux. But someone else might be interested in a multiplatform solution.

Here you can use CommandLineToArgvW . Note that this is for wchar_t * so the example code below first converts from char * to wchar_t * , does the split, and then converts back to char * to get a consistent API for both platforms.

After writing this implementation I also found __getmainargs which supports char * instead, but I have not tried using this.

Code example:

char **split_commandline(const char *cmdline, int *argc)
{
    size_t i;
    char **argv = NULL;
    assert(argc);

    if (!cmdline)
    {
        return NULL;
    }

    // Posix.
    #ifndef _WIN32
    {
        int ret;
        wordexp_t p;
        memset(&p, 0, sizeof(p));

        // Note! This expands shell variables (might be a security issue).
        if ((ret = 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;
            }
        }

        // Note that on some OSX versions this does not free all memory (10.9.5)
        wordfree(&p);

        return argv;
    fail:
        p.we_offs = 0;
        wordfree(&p);
    }
    #else // WIN32
    {
        // TODO: __getmainargs is an alternative... https://msdn.microsoft.com/en-us/library/ff770599.aspx
        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);
        free(&cmdlinew);
        return argv;

    fail:
        if (wargs) LocalFree(wargs);
        free(&cmdlinew);
    }
    #endif // WIN32

    if (argv)
    {
        for (i = 0; i < *argc; i++)
        {
            if(argv[i]) free(argv[i]);
            argv[i] = NULL;
        }

        free(argv);
    }

    return NULL;
}

*Make use of getopt ... For more information on getopt go through "man 3 getopt"*

int main (int argc, char **argv)
{
    int opt, flgl = 0, flga = 0;
    int indx;

    while ((opt = getopt (argc, argv, "la")) != ERROR) {
        switch (opt) {
            case 'l':
                    flgl = 1;           //printf ("%c", opt);
                    break;
            case 'a':
                    flga = 1;           //printf ("%c", opt);
                    break;
            default:
                    fprintf (stderr, "missing file operand\n");
                    break;
        }   
    }
    if (optind >= argc) {
        fprintf (stderr, "missing file operand\n");
        return FAILURE;
    }   
    for (indx = optind ; indx < argc ; indx++)
        call_rm (argv[indx], flgd, flgr);
    return SUCCESS;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM