I\'m using the following code to try to read the results of a df
command in Linux using popen
.
#include // file an
Pipes are not random access. They're sequential, which means that once you read a byte, the pipe is not going to send it to you again. Which means, obviously, you can't rewind it.
If you just want to output the data back to the user, you can just do something like:
// your file opening code
while (!feof(fp))
{
char c = getc(fp);
std::cout << c;
}
This will pull bytes out of the df pipe, one by one, and pump them straight into the output.
Now if you want to access the df output as a whole, you can either pipe it into a file and read that file, or concatenate the output into a construct such as a C++ String.
check your bufSize. ftell
can return -1 on error, and this can lead to nonallocation by malloc with buffer having a NULL value.
The reason for the ftell
to fail is, because of the popen. You cant search pipes.
You're making this all too hard. popen(3) returns a regular old FILE *
for a standard pipe file, which is to say, newline terminated records. You can read it with very high efficiency by using fgets(3) like so in C:
#include <stdio.h>
char bfr[BUFSIZ] ;
FILE * fp;
// ...
if((fp=popen("/bin/df", "r")) ==NULL) {
// error processing and return
}
// ...
while(fgets(bfr,BUFSIZ,fp) != NULL){
// process a line
}
In C++ it's even easier --
#include <cstdio>
#include <iostream>
#include <string>
FILE * fp ;
if((fp= popen("/bin/df","r")) == NULL) {
// error processing and exit
}
ifstream ins(fileno(fp)); // ifstream ctor using a file descriptor
string s;
while (! ins.eof()){
getline(ins,s);
// do something
}
There's some more error handling there, but that's the idea. The point is that you treat the FILE *
from popen just like any FILE *
, and read it line by line.
Why would std::malloc()
fail?
The obvious reason is "because std::ftell()
returned a negative signed number, which was then treated as a huge unsigned number".
According to the documentation, std::ftell()
returns -1 on failure. One obvious reason it would fail is that you cannot seek in a pipe or FIFO.
There is no escape; you cannot know the length of the command output without reading it, and you can only read it once. You have to read it in chunks, either growing your buffer as needed or parsing on the fly.
But, of course, you can simply avoid the whole issue by directly using the system call df
probably uses to get its information: statvfs()
.
Thanks to everyone who took the time to answer. A co-worker pointed me to the ostringstream class. Here's some example code that does essentially what I was attempting to do in the original question.
#include <iostream> // cout
#include <sstream> // ostringstream
int main(int argc, char** argv) {
FILE* stream = popen( "df", "r" );
std::ostringstream output;
while( !feof( stream ) && !ferror( stream ))
{
char buf[128];
int bytesRead = fread( buf, 1, 128, stream );
output.write( buf, bytesRead );
}
std::string result = output.str();
std::cout << "<RESULT>" << std::endl << result << "</RESULT>" << std::endl;
return (0);
}
(A note on terminology: "system call" in Unix and Linux generally refers to calling a kernel function from user-space code. Referring to it as "the results of a system()
call" or "the results of a system(3)
call" would be clearer, but it would probably be better to just say "capturing the output of a process.")
Anyway, you can read a process's output just like you can read any other file. Specifically:
pipe()
, fork()
, and exec()
. This gives you a file descriptor, then you can use a loop to read()
from the file descriptor into a buffer and close()
the file descriptor once you're done. This is the lowest level option and gives you the most control.popen()
, as you're doing. This gives you a file stream. In a loop, you can read using from the stream into a temporary variable or buffer using fread()
, fgets()
, or fgetc()
, as Zarawesome's answer demonstrates, then process that buffer or append it to a C++ string.popen()
, then use the nonstandard __gnu_cxx::stdio_filebuf to wrap that, then create an std::istream
from the stdio_filebuf
and treat it like any other C++ stream. This is the most C++-like approach. Here's part 1 and part 2 of an example of this approach.