Blake
Blake

Reputation: 75

Reading terminal response to bash commands into c++ variable

Does anyone know of a way to read in terminal output from bash commands into c++? For example, entering "ls" into the terminal returns the file names in the current directory, is there any way to perhaps import these names into a string or char array or something? I have been looking at fork(), exec(), and pipe(). I was attempting to open a pipe communication between child (system commands entered here) and parent (output read in here) but have been thoroughly unsuccessful. Ideas?

Upvotes: 0

Views: 1641

Answers (2)

enthusiasticgeek
enthusiasticgeek

Reputation: 2736

If one uses glibmm c++ bindings one can execute the following

#include <glibmm.h>
#include <iostream>

#include <unistd.h>

void finished_process(int pid, int ret, Glib::RefPtr<Glib::MainLoop>& loop)
{
    loop->quit();
    std::cout << "Process finished: " << pid << "|" << ret <<
    std::endl;
}

int main()
{
    int stdout_fd, stdin_fd, stderr_fd, pid;
    Glib::ustring line;
    std::vector<std::string> command;

    Glib::init();
    command.push_back("ls");
    command.push_back("-l");
    command.push_back("|");
    command.push_back("grep");
    command.push_back("-i");
    command.push_back("my_file");

    try{
    Glib::spawn_async_with_pipes(".", command,
        Glib::SPAWN_SEARCH_PATH | Glib::SPAWN_DO_NOT_REAP_CHILD, sigc::slot<void>(),
        &pid, &stdin_fd, &stdout_fd, &stderr_fd);
    } catch(Glib::SpawnError &e){
    std::cout << "Error: " << e.what() << std::endl;
    return 0;
    }

    Glib::RefPtr<Glib::MainLoop> loop(Glib::MainLoop::create());
    Glib::RefPtr<Glib::MainContext> context = loop->get_context();
    context->signal_child_watch().connect(sigc::bind(sigc::ptr_fun(finished_process), loop),
                    pid);

    Glib::RefPtr<Glib::IOChannel> io = Glib::IOChannel::create_from_fd(stdout_fd);
    loop->run();

    while (io->read_line(line) != Glib::IO_STATUS_EOF){
    std::cout << line;
    }
    io->close();
    close(stdout_fd);
    std::cout << "Exit" << std::endl;
}

To compile the program g++ -g -std=c++0x signal_child_watch_example.cc -o signal_child_watch_example `pkg-config --libs --cflags giomm-2.4`

On Ubuntu/Debian one can install glibmm libraries as follows sudo apt-get install libglibmm-2.4-dev

Note In future this 2.4 version number may change. Make changes to the above command accordingly.

source: http://gtk.10911.n7.nabble.com/Glib-MainContext-signal-child-watch-is-not-working-td43517.html

Upvotes: 0

Blake
Blake

Reputation: 75

Thanks Kerrek SB, I had seen people using popen() in various ways but wasn't sure if that was the route to pursue or not. With the help of the documentation and putting together parts of how other people were using it, I came up with this. This does what the question I wrote asked about specifically: importing the terminal response from the command "ls" into a vector of file names. However, each file name contains a newline character that I implicitly remove pushing back all but the last element of the string file into the vector.

void currentDirFiles( vector<string> & filesAddress )
{ 
    FILE * pipe = popen( "ls", "r" );
    char buffer[1000];
    string file;
    vector<string> files;
    while ( fgets( buffer, sizeof( buffer ), pipe ) != NULL )
    {
        file = buffer;
        files.push_back( file.substr( 0, file.size() - 1 ) );
    }
    pclose( pipe );
    filesAddress.swap( files );
}

I am trying to adapt the wikipedia RAII example to fit my needs as per the recommendations of Mankarse, and this is what I have so far. Does it look like I am on the right track? Any obvious mistakes or misunderstandings I have on this?

 #include <cstdio>

// exceptions
class file_error { } ;
class open_error : public file_error { } ;
class close_error : public file_error { } ;
class read_error : public file_error { } ;

class TerminalPipe
{
   public:
       TerminalPipe():
           pipeHandle(std::popen("ls", "r"))
           {
               if( pipeHandle == NULL )
                   throw open_error() ;
           } 
    ~TerminalPipe()
    {
        std::pclose(pipeHandle) ;
    } 
    void currentDirFiles( vector<string> & filesAddress )
    {
       char buffer[1000];
       string file;
       vector<string> files;
       while ( fgets( buffer, sizeof( buffer ), pipeHandle ) != NULL )
       {
           if( std::ferror( pipeHandle ) ) 
               throw read_error();
           else
           {
               file = buffer;
               files.push_back( file.substr( 0, file.size() - 1 ) );
           }
       }
    }
    private:
        std::FILE* pipeHandle ;
        // copy and assignment not implemented; prevent their use by
        // declaring private.
        TerminalPipe( const file & ) ;
        TerminalPipe & operator=( const file & ) ;
};

Then call it with this:

vector<string> dirFiles;
TerminalPipe pipe;    
pipe.currentDirFiles( dirFiles );

Upvotes: 2

Related Questions