Read cout and write to cin of a process with boost::process - c++

I'm trying to spawn a shell and read the stdout and stderr, and let the user run commands by writing to stdin. I can't seem to read from stdout or stderr, and writing to stdin also apparently doesn't do anything.
If I don't try redirecting any of stdin, stdout, or stderr, it spawns a normal shell like I'd expect. However, redirecting them just makes everything vanish. The >> just hangs mysteriously. I pull from the child's stdout and just put it right back into the actual stdout, so I'd expect at lease something like my username to show up, but like I said, it just blocks because there must be nothing in the buffer.
boost::process::child shell;
boost::process::opstream instream;
boost::process::ipstream errstream;
boost::process::ipstream outstream;
void console_out_read()
{
char ch;
do
{
outstream >> ch;
std::cout << ch;
}
while(true);
}
void console_err_read()
{
char ch;
do
{
errstream >> ch;
std::cerr << ch;
}
while(true);
}
void console_in_write()
{
int ch;
do
{
std::cin >> ch;
instream << ch;
}
while(true);
}
int main()
{
std::error_code ec;
shell = boost::process::child("bash",
boost::process::std_out > outstream,
boost::process::std_err > errstream,
boost::process::std_in < instream,
ec);
boost::thread cro(console_out_read);
boost::thread cre(console_err_read);
boost::thread cwi(console_in_write);
shell.wait();
return 0;
}

There are several issues with the code you posted.
First, in console_in_write you declared ch to be an int instead of a char. That means that std::cin will only accept things that look like numbers for it. This leads to std::cin getting into an error state if you enter anything but a number.
Second, even after changing ch to be a char, std::cin >> ch will never read a newline character because the formatted input operations discard whitespace. The easiest solution to this problem would be to use std::getline to read line-by-line instead of char-by-char and just write a newline after each line you read.
Third, boost's pipe streams are fully-buffered. That means that you need to flush the stream after you write a newline so that the data gets send to your child process. Otherwise you'll type a command and nothing will happen because the data is just sitting in the stream's buffer.
Fourth, std::threads must be joined or detached before they go out of scope. Since you don't currently do so with your I/O threads, as soon as your subprocess exits the parent will crash.
Putting these all together, I would expect a minimal example to look something like this:
boost::process::child shell;
boost::process::opstream instream;
boost::process::ipstream errstream;
boost::process::ipstream outstream;
void console_out_read()
{
std::string line;
while (std::getline(outstream, line))
{
std::cout << line << '\n';
}
}
void console_err_read()
{
std::string line;
while (std::getline(errstream, line))
{
std::cout << line << '\n';
}
}
void console_in_write()
{
std::string line;
while (std::getline(std::cin, line))
{
instream << line << std::endl;
if (line == "exit") return;
}
}
int main()
{
std::error_code ec;
shell = boost::process::child("bash",
boost::process::std_out > outstream,
boost::process::std_err > errstream,
boost::process::std_in < instream,
ec);
std::thread cro(console_out_read);
std::thread cre(console_err_read);
std::thread cwi(console_in_write);
shell.wait();
cro.join();
cre.join();
cwi.join();
}

Related

write string in file with spaces

I've a problem with writing to file. If I write something with spaces, it write each word as a single line. Why?
void backstart()
{
thread t1(backing);
thread t2(thr2);
t1.join();
t2.join();
}
void thr2()
{
string tmp;
tmp = "";
while (tmp != "/exit")
{
cin >> tmp;
if (tmp != "/exit")
{
writefile(tmp);
}
}
runing = false;
}
void writefile(string msg)
{
ofstream myfile("file.txt", ios::out | ios::app);
myfile << userna + ": " + msg + ",\n";
myfile.close();
}
Thanks
Damon
Consider writing it like this:
void thr2()
{
std::string line;
while(std::getline(cin, line)) // this check the read succeeded as well
{
if (line=="/exit") break; // stop on "/exit" command
writefile(line); // write out the line
}
running = false; // stop the thread, I guess
}
The most important line is
while(std::getline(std::cin, line))
which reads a whole line at a time into the std::string called line, and then checks the state of the stream to make sure the read succeeded. Read about std::getline here.
Edit:
Be very careful (and in fact I suggest just avoiding it) mixing reading with >> and getline. If you read, for example an int, with >> it will leave the '\n' character at the end of the line, so when you next read with getline, you get the rest of the line (which is basically nothing), rather than what you probably wanted, which is the next line.
If you have just read with >> and want to use getline, read into the whitespace eater first, like this std::cin >> std::ws.

Is there an elegant way to determine if an ifstream is attached to stdin?

My program is a common shell that I am trying to write in C++. In addition to taking commands from the command line it has to be able to read commands in a file - the file name being passed as an optional arg, not by redirection.
If the arg is present I open the passed filename otherwise I open "/dev/stdin". I'm not thrilled about opening the dev file and it isn't my main question but if someone has a better method I would love to hear it.
Eventually I have to read commands given to the shell but first I must present the prompt if it I am reading from stdin or skip the prompt if the input is coming from a file. My question is: is there a better way to determine in getCommand if the input stream is stdin than declaring a global or passing a bool or similar hacks?
It occurs to me that if I can somehow use std::cin rather than opening the /dev file I can pass the stream as an istream. Would that make it easier to distinguish between the two? E.G. if (source == cin) ?
Thanks for any and all suggestions.
bool getCommand(ifstream source, std::string command)
{
if (source == stdin)
//print prompt to stdout
// do the rest of stuff
return true;
}
int main(int argc, char *argv[])
{
std::ifstream input;
std::string command;
if (argc == 2)
{
input.open(argv[1], std::ifstream::in);
if (! input)
{
perror("input command file stream open");
exit(EXIT_FAILURE);
}
}
else
{
input.open("/dev/stdin", std::ifstream::in);
if (! input)
{
perror("input stdin stream open");
exit(EXIT_FAILURE);
}
}
//.......
if (getCommand(input, command))
//.......
}
If you use std::istream& as the type of your variable then you can use std::cin instead of opening /dev/stdin:
std::ifstream fileinput;
if (argc == 2)
{
fileinput.open(argv[1], std::ifstream::in);
if (! fileinput)
{
perror("input command file stream open");
exit(EXIT_FAILURE);
}
}
std::istream &input = (argc == 2) ? fileinput : std::cin;
The remainder of your question should be about identifying a tty (Detect if stdin is a terminal or pipe?). But failing that, once you've made the above change you could compare with the address of std::cin:
if (&input == &std::cin) {
std::cout << "prompt:";
}
Obviously that won't identify the case where the command line argument is /dev/stdin, so it's a bit of a bodge. But I'd probably argue that's actually an improvement given the funny requirement, because someone can at least write yourprogram /dev/stdin < input.txt, or sort input.txt | yourprogram /dev/stdin in order to work around your restriction and avoid the prompt :-)
First off, if you want to read either from std::cin or from a std::ifstream, I would implement the work in terms of an std::istream and certainly avoid opening /dev/stdin:
std::istream in(std::cin.rdbuf());
std::ifstream fin;
if (argc == 2) {
fin.open(av[1]);
// check that the file is actually open or produce an error
in.rdbuf(fin.rdbuf());
}
else {
// determine if you need to create a prompt
}
if (getCommand(in, command)) {
...
}
Now, to determine if you actually need to write a prompt it isn't sufficient to determine if you are reading from standard input but you also need to determine if the standard input is connected to something with a screen and a keyboard. There is no portable way to do so, however. On UNIXes there are functions like isatty() which can be used to determine if file descriptor 0 is connected to a tty. I would use this information to set up an iword() which can then be used to check if the stream need a prompt:
static int const needs_prompt = std::ios_base::xalloc();
...
in.iword(needs_prompt) = isatty(0);
...
if (in.iword(needs_prompt)) {
std::cout << "pompt>";
}
If you want to get fancy you can create a custom std::streambuf which is hooked up with an std::ostream which is tie()ed to the input stream stream and writes a prompt upon sync(): Every time a read operation on an input stream is done the tie()ed std::ostream is flushed. However, this requires that you only read once for each prompt being produced, e.g. using std::getline(in, line). Obviously, if you don't need a prompt, you don't tie() an std::ostream. Below is a simple program demonstrating the auto-prompt approach (without doing any of the other business):
#include <iostream>
#include <streambuf>
struct prompt
: std::streambuf
{
std::string d_prompt;
std::ostream& d_out;
prompt(std::string const& p, std::ostream& out)
: d_prompt(p)
, d_out(out)
{
}
int sync() {
this->d_out << this->d_prompt << std::flush;
return 0;
}
};
int main()
{
prompt p("prompt>", std::cout);
std::ostream helper(&p);
std::cin.tie(&helper);
for (std::string line; std::getline(std::cin, line); ) {
std::cout << "read '" << line << "'\n";
}
}

Rewind ifSteam in C++

I'm aware this has been asked a few time before and I read the threads related to it but the solutions there didn't work for me.
ifstream myFile;
myFile.open("largefile.txt");
if (myFile.is_open())
{
while (!myFile.eof( )) //step through each line until end of file
{
myFile>> str;
if(str.size() < 5){
amount++;
}
}
}
myFile.seekg(0, ios::beg);
if (myFile.is_open())
{
for(int i=0; i != random_integer; i++) //step through each line until random line reached
{
myFile>> str;
if(i == random_integer-1){
cout << "\n";
cout << str;
cout << "\n";
cout << str.size();
}
}
}
myFile.close();
I read that using EOF in the while statement was a bad idea, whats the alternative? And how can I rewind for the second loop as seekg isn't working out, I don't want to have to close the file and open again, and the file is to large to read into an array really?
TIA, I'm sure this is an easy fix I'm just very new to C++.
Instead of testing eof() (which doesn't work correctly), you should
just verify that each input has worked, by using the stream in a boolean
context:
while ( myFile >> str ) {
// ...
}
Once it has failed, of course, the stream is in a failed state, which
must be reset before any further operations are allowed:
myFile.clear();
myFile.seekg( 0, std::ios_base::beg );
myFile >> str is not guaranteed to work just because you checked for end of file, because that is a flag only set when EOF has been read, not when it is about to be read.
Instead you should do:
while( myFile >> str )
You also need to clear your stream before you use it again (with seekg). One of the unfortunate things about streams is that they hold state as flags and they can trip you up if you are not careful.
myFile.clear();
then continue.

C++ file handling, is_open returning bad

If I include the if test in my code the error message is returned and I'm not sure why.
and when it's not used, my program get's stuck in a loop where it never reaches the end of the file. I don't understand what's going wrong.
int countlines()
{
fstream myfile;
myfile.open("questions.txt", ios::in);
string contents;
int linenumber = 0;
//if (myfile.is_open())
// {
while (!myfile.eof())
{
getline( myfile, contents );
if (contents != "")
{
linenumber++;
}
}
cout << "there are " << linenumber << " lines.\n";
//}else {cout<<"Unable to get file.\n";}
myfile.close();
return(linenumber);
}
What's going on is that your file is not being opened. That's why is_open fails.
Then, when you comment out the check, you're breaking your loop because you're iterating incorrectly (see my comment) and not detecting stream failures (.eof() will never be true on that stream).
Make sure that the file is in the right place, and that it is accessible.
The correct idiom for reading a file line-by-line in C++ is using a loop like this:
for (std::string line; std::getline(file,line);)
{
// process line.
}
Inserting this in your example (+fixing indentation and variable names) gives something like this:
int countlines(const std::string& path)
{
// Open the file.
std::ifstream file(path.c_str());
if (!file.is_open()) {
return -1; // or better, throw exception.
}
// Count the lines.
int count = 0;
for (std::string line; std::getline(file,line);)
{
if (!line.empty()) {
++count;
}
}
return count;
}
Note that if you don't intend to process the line contents, you can actually skip processing them using std::streambuf_iterator, which can make your code look like:
int countlines(const std::string& path)
{
// Open the file.
std::ifstream file(path.c_str());
if (!file.is_open()) {
return -1; // or better, throw exception.
}
// Refer to the beginning and end of the file with
// iterators that process the file character by character.
std::istreambuf_iterator<char> current(file);
const std::istreambuf_iterator<char> end;
// Count the number of newline characters.
return std::count(current, end, '\n');
}
The second version will completely bypass copying the file contents and avoid allocating large chunks of memory for long lines.
When using std::istream and std::ostream (whose std::fstream implements), the recommended usage is to directly use the stream in a bool context instead of calling eof() function because it only return true when you managed to read until the last byte of the file. If there was any error before that, the function will still return true.
So, you should have written your code as:
int countlines() {
ifstream myfile;
int linenumber = 0;
string linecontent;
myfile.open("question.txt", ios::in);
while (getline(myfile, linecontent)) {
if (!linecontent.empty()) {
++linenumber;
}
}
return linenumber;
}
Try the following code. It will also (hopefully) give you an idea why the file open is failing...
int countlines()
{
ifstream myfile;
myfile.open("questions.txt");
string contents;
int linenumber = 0;
if (myfile.is_open())
{
while (getline(myfile, contents))
{
if (contents != "")
linenumber++;
}
cout << "there are " << linenumber << " lines." << endl;
myfile.close();
}
else
cout << "Unable to get file (reason: " << strerror(errno) << ")." << endl;
return linenumber;
}

While Loop contines forever to get end of input string

How do I run the while loop until the end of line or null character reached.
Here is my code
#include<iostream>
using namespace std;
main()
{
char input[20];
cout<<"Enter a line: ";
cin>>input;
while(input!='\0')
{
cout<<"This is a text";
}
system("pause");
}
If you want to read until either a newline or a NUL, read one character at a time inside the loop.
#include<iostream>
int main()
{
char input;
std::cout << "Enter a line: " << std::flush;
while(std::cin >> input && input != '\n' && input != 0) {
std::cout << "This is a test\n";
}
}
Notes:
main requires a return type
Never, ever, say "using namespace std;"
Don't forget to flush if you want cout to appear immediately.
Notice the compound test in the while condition:
First, did the read succeed?
Next, is it not '\n' (one of your conditions).
Next, is it not NUL (the other of your conditions).
The body of the loop will be executed once per input character -- is that what you wanted?
But, consider if you have correctly specified your requirement. It is an unusual requirement -- why would there be a NUL in a text file, and why would you want to process each character individually?
In idiomatic C++, you can read the input file in a line at a time using std::getline:
std::string myString;
while(std::getline(std::cin, myString)) {
// process myString
}
If you just want to read in a single line:
std::string myString;
if(std::getline(std::cin, myString)) {
// process myString
}
Finally, if you want to read a line, and ignore its contents:
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
try something like:
i = 0;
while ((input[i] != '\0') && i < 20)
{
cout<<"This is a text";
i++;
}
Like this:
std::string line;
if (std::getline(std::cin, line))
{
std::cout << "Thank you, you said, \"" << line << "\".\n";
}
else
{
// error, e.g. EOF
}
If you want to read multiple lines, use a while loop instead:
while (std::getline(std::cin, line))
{
std::cout << "Thank you, you said, \"" << line << "\".\n";
}
The issue is that you're reading an entire chunk of text at once and then printing it until the input is '\0'. However, you're never actually updating this inside the loop. You can either use cin inside the loop to get the input, OR if you're trying to output each character, you can index the char array.