There are plenty of questions on here related to fork() and exec(). I have not found one that really makes the process of using them simple though, and making programmer's lives simple is the goal.
I need a C++, linux-friendly function that does the following:
string RunCommand(string command, string input){}
This function should be able to run a shell command, like grep, and "pipe" the content of input into it and read the ouptut and return it. So if I would do the following at the command line:
ps -elf | grep somequerytext
I would in code do:
string psOutput = RunCommand("ps -elf","");
string grepOutput = RunCommand("grep somequerytext", psOutput);
*edit: The question is what is the best implementation of the RunCommand function.
*edit: popen was considered as a solution for simplicity, but popen restricts you to piping data in or piping data out, but not both.
It appears that you need a function to:
- Create two pipes and fork.
- The child process then does:
- Duplicate appropriate descriptors of the pipes so that one file descriptor is standard input and one standard output
- Close the pipe descriptors
- Split up the command string into arguments
- Run the command with the arguments
- The parent (main) process then does:
- Close the appropriate pipe file descriptors
- Writes the input string to the child and closes the pipe to the child's standard input
- Reads the output string from the child
- Closes the the pipe from the child's standard output
- Waits for the child to die
- When the child is dead, the main process can continue, returning the string that it read.
The only potential problem with this outline is if the child writes output before it is finished reading its input, and it writes so much output that the pipe is full (they have a finite and usually quite small capacity). In that case, the processes will deadlock - the parent trying to write to the child, and the child trying to write to the parent, and both stuck waiting for the other to read some data. You can avoid that by having two threads in the parent, one processing the writing, the other processing the reading. Or you can use two child processes, one to run the command and one to write to the standard input, while the parent reads from the command's standard output into a string.
One of the reasons there isn't a standard function to do this is precisely the difficulty of deciding what are the appropriate semantics.
I've ignored error handling and signal handling issues; they add to the complexity of it all.
Before discussing the implementation of RunCommand
, let us consider this code fragment:
string psOutput = RunCommand("ps -elf","");
string grepOutput = RunCommand("grep somequerytext", psOutput);
In the above code fragment, the problem is that the commands are run sequentially, and does not run concurrently/in parallel. (See Programming with POSIX threads p.9 ) To give an example if ps -elf
generates huge amount of data, that will be stored in psOutput
and then passed to next command. But in actual implementation, each process in the pipe are run concurrently and data is passed with pipe
(with some buffering of course) and there is no need to wait for the execution of one process before starting the execution of other process.
I suggest you to look into the Richard Steven's Advanced Programming in the Unix Environment chapter.8 "Process Control" p.223 for an implementation of system
. Based on Richard Steven's code, a sample implementation of RunCommand
will be as follows (just skeleton code, no error checking):
int RunCommand(string command)
{
pit_t pid;
if ( ( pid = fork() ) < 0 ) return -1;
else if (pid == 0)
{
execl("/bin/sh", "sh", "-c", command.c_str(), (char*) 0);
}
else
{
/* The parent waits for the child */
wait(pid, ...);
}
}
and then one would invoke the above functions as:
string s("ps -elf | grep somequerytext");
int status = RunCommand(s);
The shell takes care of parsing its input and running the commands by setting up pipe
s in between them.
If you are interested in understanding how a shell is implemented, see "A Minishell example" in Terrence Chan Unix System Programming using C++ chap.8 "Unix Processes" (Jonathan Leffler's answer pretty much describes a shell implementation!)
Why not use popen()
? It's in the standard library, and very simple to use:
FILE* f = popen("ps -elf | grep somequerytext", "r");
char buf[2048];
buf[fread(buf, 1, 2048, f)] = '\0';
cout << buf;