How do I read the results of a system() call in C++?
Solution 1
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()
.
Solution 2
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.
Solution 3
(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:
- You can start the process using
pipe()
,fork()
, andexec()
. This gives you a file descriptor, then you can use a loop toread()
from the file descriptor into a buffer andclose()
the file descriptor once you're done. This is the lowest level option and gives you the most control. - You can start the process using
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 usingfread()
,fgets()
, orfgetc()
, as Zarawesome's answer demonstrates, then process that buffer or append it to a C++ string. - You can start the process using
popen()
, then use the nonstandard __gnu_cxx::stdio_filebuf to wrap that, then create anstd::istream
from thestdio_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.
Solution 4
I'm not sure you can fseek/ftell pipe streams like this.
Have you checked the value of bufSize ? One reason malloc be failing is for insanely sized buffers.
Solution 5
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);
}
Bill the Lizard
Python, Java, Android, etc. developer living in Charlotte, NC. I am the author of @BountyBot, a Twitter bot that posts new and interesting bounty questions from Stack Overflow. You can view the source on GitHub. My Android apps on Google Play: Three of a Kind - A fun, fast-paced strategy card game. Match any three symbols to win. Sketchboard - Sketch drawings on your mobile device. Serpent - An Android version of the classic mobile game Snake. Math Blitz! - A fast-paced flashcard game to help students practice their arithmetic skills. Linear Interpolator - Fill in the gaps in your data using linear interpolation. Kitchen Calculator - A simple unit converter for common units used in cooking and homebrewing. If you use any of these apps, please leave me a rating and any feedback for improvement.
Updated on July 12, 2022Comments
-
Bill the Lizard almost 2 years
I'm using the following code to try to read the results of a
df
command in Linux usingpopen
.#include <iostream> // file and std I/O functions int main(int argc, char** argv) { FILE* fp; char * buffer; long bufSize; size_t ret_code; fp = popen("df", "r"); if(fp == NULL) { // head off errors reading the results std::cerr << "Could not execute command: df" << std::endl; exit(1); } // get the size of the results fseek(fp, 0, SEEK_END); bufSize = ftell(fp); rewind(fp); // allocate the memory to contain the results buffer = (char*)malloc( sizeof(char) * bufSize ); if(buffer == NULL) { std::cerr << "Memory error." << std::endl; exit(2); } // read the results into the buffer ret_code = fread(buffer, 1, sizeof(buffer), fp); if(ret_code != bufSize) { std::cerr << "Error reading output." << std::endl; exit(3); } // print the results std::cout << buffer << std::endl; // clean up pclose(fp); free(buffer); return (EXIT_SUCCESS); }
This code is giving me a "Memory error" with an exit status of '2', so I can see where it's failing, I just don't understand why.
I put this together from example code that I found on Ubuntu Forums and C++ Reference, so I'm not married to it. If anyone can suggest a better way to read the results of a system() call, I'm open to new ideas.
EDIT to the original: Okay,
bufSize
is coming up negative, and now I understand why. You can't randomly access a pipe, as I naively tried to do.I can't be the first person to try to do this. Can someone give (or point me to) an example of how to read the results of a system() call into a variable in C++?
-
flolo over 15 yearsYour are completly right, I just realized that the he did an ftell not on an open file, but on an pipe, which is not allowed.
-
Martin York over 15 yearsRewinding a pipe does not seem to make much sense!
-
Bill the Lizard over 15 yearsThat's what I get for trying to combine two foreign pieces of code. :(
-
Bill the Lizard over 15 yearsI need to parse the output of the df command and a few others. I think outputting it to a C++ string is what I need to do.
-
Bill the Lizard over 15 yearsThank you. I know most of this code is C, but I'm just trying to solve one little problem that will be a part of a much larger C++ application. I'm not restricted to pure C, so I'm interested in learning any method of doing this in C++.
-
Bill the Lizard over 15 yearsThanks, that's a good answer. Now I understand where I went wrong. I was only using df as an example, though. I really need to read and parse the results of several different system calls.
-
Bill the Lizard over 15 yearsYou're right, bufSize was -1. Amazing (to me) that you can tell that from a quick look at the code.
-
Bill the Lizard over 15 yearsAccepted because I ended up scrapping my original solution and used statvfs() for a significant portion of my problem instead. Thanks. :)
-
ahala over 10 yearsthis line ins(fileno(fp)) does not seem work under gcc 4.6.3.