Originally Posted by
tricolaire
Hello, all.
I'm attempting to write a program in which the program executes a shell command. This shell command then outputs some value to stdout, and I would like to be able to read that output from within my program.
Based on your code, I think you'd get better results with popen():
Code:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
int main(void)
{
FILE *cmd;
char *line = NULL;
size_t size = 0;
ssize_t len;
int status;
cmd = popen("ifconfig eth0 | grep '[Ee]ther'", "r");
if (!cmd) {
fprintf(stderr, "Failed to run command.\n");
return 1;
}
while ((len = getline(&line, &size, cmd)) > (ssize_t)0) {
/* Have 'len' characters in 'line', including the newline. */
}
/* Free the input line buffer. */
free(line);
line = NULL;
size = 0;
/* I/O error? */
if (ferror(cmd) || !feof(cmd)) {
pclose(cmd);
fprintf(stderr, "Error reading the command output.\n");
return 1;
}
status = pclose(cmd);
if (WIFEXITED(status)) {
/* Normal exit. Check exit status code. */
if (WEXITSTATUS(status) != 0) {
fprintf(stderr, "Command exited with exit status %d.\n", WEXITSTATUS(status));
return 1;
}
} else {
/* Abnormal exit. */
if (WIFSIGNALED(status))
fprintf(stderr, "Command died from signal %s.\n", strsignal(WTERMSIG(status)));
else
fprintf(stderr, "Command died abnormally.\n");
return 1;
}
fprintf(stderr, "Success.\n");
return 0;
}
Note that your original code is also POSIX, although getline() is defined in POSIX.1-2008.
Your original code is utterly broken:
Originally Posted by
tricolaire
Code:
int desc[2];
char to[100];
pipe(desc);
dup2(fileno(stdout),desc[1]);
Both pipe() and dup2 can and do fail, but you don't bother to check if they succeed or not. Bad form, but not really an error.
Originally Posted by
tricolaire
Code:
execv("ifconfig en0 | grep ether",NULL);
This is just utterly wrong, see man 3 execv(). The first parameter -- the one you put a shell command in -- is the path to the executable. (If you use execvp(), you can use just the name of the executable.) The second parameter is an array of pointers, starting with the command name, followed by each command line parameter, followed by NULL.
To actually do what you thought that line does, you'd use
Code:
execl("/bin/sh", "sh", "-c", "ifconfig en0 | grep ether", NULL);
However, the exec() family of functions replace the current process with the one to be executed. It will never return! Any code you might have in your program following that line would only be run if the execl() failed.
The way to do it is to first create the communications pipe, then fork(), which creates a child process at that point. (The child process is basically a detached duplicate of the parent process. Any changes you do in the child are not visible in the parent, and vice versa, except for certain exceptions that are irrelevant to the task at hand.) Both parent and child processes continue to execute the code, in their separate ways, with initially the only difference being that the child received 0 from fork(), and the parent a nonzero process ID.
The child process duplicates the write end of the pipe to its standard output, thus redirecting its output to the pipe. (It also closes the read end of the pipe.) Then it executes the desired command. If the execl() fails, the child exit()s, since the parent is already running.
The parent process closes the write end of the pipe, and starts reading from the read end of the pipe. When you encounter an end of input (read() returns zero), the command has completed, and the parent process can close the read end of the pipe, and reap the child using waitpid().
popen() does all of the above for you, plus an extra fdopen() so you get a proper FILE stream instead of just a file descriptor.
Originally Posted by
tricolaire
Code:
read(desc[0],to,sizeof(to));
There is absolutely no guarantee, zero, zilch, nada, that the above call does what you think it does. In fact, with pipes, it frequently does not.
The man 2 read man-page explains that the read() function can return -1 with errno==EINTR in perfectly normal operation (when a signal is delivered, for example). (If the descriptor was set to nonblocking, then you could also see errno==EAGAIN || errno == EWOULDBLOCK.) It can also return any value between 1 and sizeof to, inclusive, without it being an error; this is called a short read.
When testing, it is quite possible that in your tests it always works. If you change the command, to one that writes e.g. each character separately (with flushes in between) to standard output, you may only get one character per read(). All this is perfectly normal, and if you are going to use the low-level I/O facilities, you'd better get it right or your code will be buggy -- and if you neglect to even check the return values, it will be buggy in weird and mysterious ways. It won't be fun, just annoying and frustrating. (Especially to the end user, if you simply say "it works for me, it must be something at your end", like many programmers who write this kind of code, do.)
I recommend using popen() if you only need to either supply input to the external program, or read the output from the external command.
If you want to both supply input and read the output of the external command (similar to coprocesses in Bash shell), then I can show how to do that using the low-level I/O properly. The difficulty with that is that you must be able to produce the input and consume the output asynchronously; you cannot just write one line then read one line, because the external command may need more than one line of input to produce the next line. If you do that, it is likely the two programs get deadlocked, each waiting for the other, neither one advancing. So, the actual approach I'd personally use depends on the exact use case; on the details how the program consumes and produces the data from and to the external program.