Reputation: 2396
I have 2 bash scripts
Problem:
When creating a QProcess
, I am unable to differentiate if the QProcess
is finished or is hanging for user input.
I was hoping for some function e.g. QProcess::atEnd
that returns true if the script is finished, but it returns true even if the QProcess
is hanging for user input (from script)
More Info:
Script requiring user input : name-please.sh
Script not requiring input : hello.sh
name-please.sh
#!/bin/bash
echo "I am Mr Robot"
echo "what is your name:"
read n
if [[ ! -z "$n" ]];
then
echo "please to meet you"
exit 0;
else
echo "ok, bye..."
exit 1;
fi
hello.sh
#!/bin/bash
echo "I am Mr Robot"
using mReadyReadStandardOutput()
, I read the script output. The issue exists around the fact that I need to read the output, determine if the output contains a line that requests user info (line from the script) and respond to that.
see code with pointer to line
Code:
#include <QCoreApplication>
#include <QLoggingCategory>
#include <QTextStream>
#include <QProcess>
#include <QString>
#include <QVariant>
#include <QDebug>
#include <QObject>
#include <QEventLoop>
class m_Proc : public QProcess
{
Q_OBJECT
public:
int exitCode = -1;
QString _out, _input;
m_Proc(QString input = QString(), QObject *parent = 0);
~m_Proc() {}
public slots:
void myReadyReadStandardOutput();
void myFinished(int i);
};
m_Proc::m_Proc(QString input, QObject *parent)
{
connect(this,SIGNAL(readyReadStandardOutput()),
this,SLOT(myReadyReadStandardOutput()));
connect(this, SIGNAL(finished(int)),
this, SLOT(myFinished(int)));
}
void m_Proc::myReadyReadStandardOutput() {
// Note we need to add \n (it's like pressing enter key)
_out = this->readAllStandardOutput();
if (!_input.isEmpty()) {
/*
* check if line matches that of the script,
* where the user is required to enter their name
*/
if (_out.contains("what is your name")) { <<< ---- LINE WITH ISSUE
this->write(QString(_input + QString("\n")).toLatin1());
_out = this->readAllStandardOutput();
}
}
else
qDebug() << "Input Emptry (should not be reached!) !";
}
void m_Proc::myFinished(int i){
exitCode = i;
qDebug() << "exit code = " << QString::number(exitCode);
qDebug() << _out;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
m_Proc *myProcess1 = new m_Proc(QString("text"));
// QString program = "/home/dev/script";
myProcess1->start("/home/dev/script", QStringList());// << "one" << "two");
// myProcess1->start("/bin/sh", QStringList() << "-c" << "ls" << "-la");
a.exec();
}
#include "main.moc"
Any advice to bypass this issue?
Upvotes: 0
Views: 1173
Reputation: 6430
Three things.
According to the documentation for QProcess::atEnd()
, this returns true only if the process is not running (QProcess::NotRunning
). So...are you sure the process isn't truly done when you call atEnd()
? I don't see it anywhere in the code you've posted, so I can't tell.
Your myReadyReadStandardOutput()
signal is overwriting the m_Proc::_out
member variable each time new data is available. Depending on how writes to standard output are buffered on your system (and by Qt), you may be reading partial lines into _out
. Thus the _out.contains()
call may return false, because you've got something like at is your
in the line, rather than the full what is your name:
.
You write the member string _input
to the process's standard input, and then immediately try to read again from the standard output. This will most likely not return any data that you didn't already have in _out
. Qt only really does actual I/O with the underlying device when control returns to the main event loop. From the documentation for QIODevice
:
Certain subclasses of QIODevice, such as QTcpSocket and QProcess, are asynchronous. This means that I/O functions such as write() or read() always return immediately, while communication with the device itself may happen when control goes back to the event loop.
My suggestion for solving your issue would be to modify the myReadyReadStandardOutput
signal. For asynchronous devices (such as sockets and sub-processes), data may arrive in arbitrary-sized chunks. This is point 2 I made above. The usual method for dealing with this is to put something like the following at the top of the signal:
if (!canReadLine())
return;
So you bail out of the signal and return to the main event loop if a full line of data is not available. If that returns true, you can do something like:
_out = readLine();
/* Process an entire line, e.g., check if it matches the request for name */
So the signal is designed to operate only on full lines of data. This is useful for terminals or network protocols that are line-oriented. In this case, you can call the QString::contains()
method to check if this is the line requesting a name, and, if so, write it. But you must then return to the event loop in order for the data to be written, so you'd process the next line (please to meet you
) in the next call to the signal in which a full line is available.
Upvotes: 1