首页 > 解决方案 > 实时获取 QProcess 的响应

问题描述

我是 C++ 编程的新手,所以我需要逻辑方面的帮助(代码会很棒)。我想让 QTextEdit 像小部件一样作为终端工作。我正在尝试使用 QProcess 来实现这一点,例如:

void QPConsole::command(QString cmd){
    QProcess* process = new QProcess();
    connect(process, &QProcess::readyReadStandardOutput, [=](){ print(process->readAllStandardOutput()); });
    connect(process, &QProcess::readyReadStandardError, [=](){ print(process->readAllStandardError()); });
    connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),[=](int exitCode, QProcess::ExitStatus exitStatus){ prepareCommandLine(); return; });

    process->setProgram("/bin/bash");
    process->start();
    process->write(cmd.toStdString().c_str());
//    process->write("\n\r");

    process->closeWriteChannel();
}

下一个问题是:如果我不关闭写入通道(process->closeWriteChannel),那么我就会陷入无限循环。如果我关闭它,我就记不住状态(有点像新会话),例如,如果我执行“pwd”,我会得到结果,但如果我执行下一个“cd ..”然后“pwd”,结果与“密码”的第一个输出

所以,我的问题是是否有可能实现某种实时输出 + 使用 QProcess 记住以前的状态(如在真实终端中),或者我必须以其他方式做。在 python 中,我使用管道进行子进程调用,但我不知道 c++ 的等价物是什么。

一个代码示例会很棒。我有 QTextEdit 部分(将 QString 参数发送到函数),我需要与控制台交互的部分。

标签: c++qtc++11qt5

解决方案


这个想法很简单:保留一个实例QProcess并向其写入命令,然后\n

这是一个完整的例子,有一个QMainWindow和一个Bash类,把它放在main.cpp

#include <QtCore>
#include <QtGui>
#include <QtWidgets>

class Bash : public QObject
{
    Q_OBJECT
public:
    explicit Bash(QObject *parent = nullptr) : QObject(parent)
    {

    }

    ~Bash()
    {
        closePrivate();
    }

signals:
    void readyRead(QString);

public slots:
    bool open(int timeout = -1)
    {
        if (m_bash_process)
            return false;

        m_timeout = timeout;

        return openPrivate();
    }

    void close()
    {
        closePrivate();
    }

    bool write(const QString &cmd)
    {
        return writePrivate(cmd);
    }

    void SIGINT()
    {
        SIGINTPrivate();
    }

private:
    //Functions
    bool openPrivate()
    {
        if (m_bash_process)
            return false;

        m_bash_process = new QProcess();

        m_bash_process->setProgram("/bin/bash");

        //Merge stdout and stderr in stdout channel
        m_bash_process->setProcessChannelMode(QProcess::MergedChannels);

        connect(m_bash_process, &QProcess::readyRead, this, &Bash::readyReadPrivate);
        connect(m_bash_process, QOverload<int>::of(&QProcess::finished), this, &Bash::closePrivate);

        m_bash_process->start();

        bool started = m_bash_process->waitForStarted(m_timeout);

        if (!started)
            m_bash_process->deleteLater();

        return started;
    }

    void closePrivate()
    {
        if (!m_bash_process)
            return;

        m_bash_process->closeWriteChannel();
        m_bash_process->waitForFinished(m_timeout);
        m_bash_process->terminate();
        m_bash_process->deleteLater();
    }

    void readyReadPrivate()
    {
        if (!m_bash_process)
            return;

        while (m_bash_process->bytesAvailable() > 0)
        {
            QString str = QLatin1String(m_bash_process->readAll());
            emit readyRead(str);
        }
    }

    bool writePrivate(const QString &cmd)
    {
        if (!m_bash_process)
            return false;

        if (runningPrivate())
            return false;

        m_bash_process->write(cmd.toLatin1());
        m_bash_process->write("\n");

        return m_bash_process->waitForBytesWritten(m_timeout);
    }

    void SIGINTPrivate()
    {
        if (!m_bash_process)
            return;

        QProcess::startDetached("pkill", QStringList() << "-SIGINT" << "-P" << QString::number(m_bash_process->processId()));
    }

    bool runningPrivate()
    {
        if (!m_bash_process)
            return false;

        QProcess process;
        process.setProgram("pgrep");
        process.setArguments(QStringList() << "-P" << QString::number(m_bash_process->processId()));
        process.setProcessChannelMode(QProcess::MergedChannels);

        process.start();

        process.waitForFinished(m_timeout);

        QString pids = QLatin1String(process.readAll());

        QStringList pid_list = pids.split(QRegularExpression("\n"), QString::SkipEmptyParts);

        return (pid_list.size() > 0);
    }

    //Variables
    QPointer<QProcess> m_bash_process;
    int m_timeout;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
    {
        connect(&m_bash, &Bash::readyRead, this, &MainWindow::readyRead);

        QWidget *central_widget = new QWidget(this);

        QVBoxLayout *layout = new QVBoxLayout(central_widget);
        layout->addWidget(&m_message_board);
        layout->addWidget(&m_cmd_edit);
        layout->addWidget(&m_sigint_btn);

        m_message_board.setReadOnly(true);

        m_cmd_edit.setEnabled(false);

        m_sigint_btn.setText("Send SIGINT(CTRL+C)");

        connect(&m_cmd_edit, &QLineEdit::returnPressed, this, &MainWindow::writeToBash);

        connect(&m_sigint_btn, &QPushButton::clicked, this, &MainWindow::SIGINT);

        setCentralWidget(central_widget);

        resize(640, 480);

        QTimer::singleShot(0, this, &MainWindow::startBash);
    }

    ~MainWindow()
    {
        m_bash.close(); //Not mandatory, called by destructor
    }

private slots:
    void startBash()
    {
        //Open and give to each operation a maximum of 1 second to complete, use -1 to unlimited
        if (m_bash.open(1000))
        {
            m_cmd_edit.setEnabled(true);
            m_cmd_edit.setFocus();
        }
        else
        {
            QMessageBox::critical(this, "Error", "Failed to open bash");
        }
    }

    void writeToBash()
    {
        QString cmd = m_cmd_edit.text();

        m_cmd_edit.clear();

        if (!m_bash.write(cmd))
        {
            QMessageBox::critical(this, "Error", "Failed to write to bash");
        }
    }

    void readyRead(const QString &str)
    {
        m_message_board.appendPlainText(str);
    }

    void SIGINT()
    {
        m_bash.SIGINT();
    }

private:
    Bash m_bash;
    QPlainTextEdit m_message_board;
    QLineEdit m_cmd_edit;
    QPushButton m_sigint_btn;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

#include "main.moc"

推荐阅读