8

Say in C, I want to call execvp() on any string command. Command can just be:

char command[] = "ls -l";
char command[] = "rm *.txt";
char command[] = "cat makefile";

I want to put this command variable inside execvp(). So the exec() flavored function can just run with any kind of arbitrary command.

How can I do that? Thanks.

NOTE: system() is not allowed.

Aaron Dufour
  • 17,288
  • 1
  • 47
  • 69
JJ Liu
  • 1,351
  • 8
  • 20
  • 36

5 Answers5

12

If you have to call execvp(), then you will need to split up those strings into an executable name and an array of arguments (the first being the "name" of the program and the last being a NULL pointer).

That means something like:

char cmd1[] = "ls";  char *args1[] = {"ls", "-l", NULL};
char cmd1[] = "rm";  char *args1[] = {"rm", "*.txt", NULL}; // but see
                                                            // globbing below.
char cmd1[] = "cat"; char *args1[] = {"cat", "makefile", NULL};

This is a non-trivial exercise, especially if you want to allow for quoting, globbing, escaping and so forth.

Quoting means you'll have to be careful with commands like:

rm "file with spaces.txt"

in that you can't simply break on the spaces - you'll have to interpret items in the command much the same as the shell does. Simplistic breaking on spaces would give you a command with three arguments for that string above, rather than the correct one.

By globbing, I mean you'll almost certainly have problems with something like *.txt since it's typically the shell which expands these arguments. Passing that directly to execvp() will result in a single argument of literally *.txt rather than many arguments matching all the text files in your current directory.

Quoting means that you'll have to handle things like:

ls -l "file with spaces and \" quote in it"

which will further complicate your parser.

Don't get me wrong, it can be done, but it's probably a damn sight easier just using system().

If you're still thinking of going the execvp() route, you'll have to:

  • split the string into separate tokens (rather hard, since you have to handle quotes and escapes).
  • glob all the arguments, meaning that those with wildcards in them (and only ones that aren't escaped or protected by virtue of being inside quotes) are expanded into multiple arguments.
  • construct the argument array, with the command at the front and a NULL at the end.
  • call execvp() with the parameters being first element in that array and the address of the array.
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Does it mean if I implement an array of null-terminated strings `{"rm", "*foo", "*bar"}`, the `execvp` should execute every combination of matches of `"*foo"` and `"*bar"`? Say, the combinations be `rm *foo[0] .. *foo[n-1] *bar[0] .. *bar[n-1]`. – PHD Jul 18 '20 at 02:20
  • Or, instead of `execvp` inside nested loop, it is just a single command that contains expanded (globbed) arguments. Say, `{rm, afoo, bfoo, abar, ..., NULL}`. – PHD Jul 18 '20 at 02:25
5

No, the exec family of functions does not take a single string command line like system does. Instead it uses an argv-like array of strings:

char *command = "/path/to/command";
char *arguments[] = { "command", "first argument", "second argument", NULL };
execvp(command, arguments);

Note that the first entry in the arguments array is the command itself, and that the array is terminated by NULL.

Did you check the manual page?

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • yes i have, you said `char *command = "/path/to/command";` can I just do `char *command = "command";` in all the possible cases? – JJ Liu Nov 11 '11 at 04:48
3

To use any of the execve-style functions, you'll need to parse the command line yourself and build an argv vector. The functions take a char**, where the last element is null - you'll need to allocate enough memory for all this. Then your execve-style call should work.

(p.s. You haven't mentioned anything about fork...)

Robert
  • 6,412
  • 3
  • 24
  • 26
1

In order to have things like "*" ">", "<", "&" etc. to work, you need to execute "/bin/sh" and pass in your command line as arguments. So, it comes down to tokenizing your string.

Arvid
  • 10,915
  • 1
  • 32
  • 40
  • While it is convenient to use `/bin/sh`, his homework rules specifically says "no `system(3)`", which is practically the same as _no `/bin/sh`/_. – sarnold Nov 11 '11 at 03:42
  • sorry about the trouble, I will have to ask if I can use `/bin/sh`. If I were to use `/bin/sh`, I could do `char *const parmList[] ={"sh", "ls -l", NULL}; execvp("/bin/sh", parmList);` but it seems not right. syntax error? – JJ Liu Nov 11 '11 at 04:46
  • should be `char *const parmList[] ={"sh", "-c", "ls -l", NULL}; execvp("/bin/sh", parmList);` – Basile Starynkevitch Nov 11 '11 at 20:38
1

What you need to do here is make the shell parse the command. So consider this:

/bin/sh -c 'rm *.txt'

That should get you going in the right direction. :)