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. I am attempting to achieve this via pipes, and have written the following code:

Code:

int desc[2];
char to[100];

pipe(desc);
dup2(fileno(stdout),desc[1]);

execv("ifconfig en0 | grep ether",NULL);
read(desc[0],to,sizeof(to));

close(desc[0]);
close(desc[1]);

As best as I can figure, this should result in that command line output being stored in "to".

The code compiles, runs, and I end up with the variable "to" containing a single '\0' character, rather than grep's output to stdout. Pipe's are a new area for me, and so I'm pretty sure the problem lies in my creation of the file descriptors.

Any help is greatly appreciated.

12-07-2012

Adak

It's easy to pipe stdout to a file, and then read the file normally. I've not seen stdout piped to stdin, but that sounds like what you want to do, if possible.

> execv("ifconfig en0 | grep ether",NULL);
exec is NOT a shell.
It does not interpret pipe characters as joining the stdout of one process to the stdin of another process.
If you want it to search your PATH, then you need to use execlp or execvp

12-07-2012

Nominal Animal

Quote:

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.

Note that your original code is also POSIX, although getline() is defined in POSIX.1-2008.

Your original code is utterly broken:

Quote:

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.

Quote:

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.

Quote:

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.

12-08-2012

tricolaire

Color me sufficiently chastised.

I read the man pages for execv prior to using it and for some reason it did not sink in that this would happen. I replaced the execv with just a call to system() and now the code works. I will, of course, be adding error checking, which was my intent down the road.