简体   繁体   English

使用 QProcess 将 EndOfText (Ctrl-C) 发送到交互式 shell

[英]Use QProcess to send EndOfText (Ctrl-C) to interactive shell

I use a QProcess to open /bin/sh or /usr/bin/bash and it's possible to write commands to the shell and read the output into my program.我使用QProcess打开/bin/sh/usr/bin/bash并且可以将命令写入 shell 并将输出读入我的程序。

The actual problem occurs when trying to send a end-of-text control signal to the shell to abort the running child process of the shell.当试图向 shell 发送文本结束控制信号以中止 shell 正在运行的子进程时,会出现实际问题。

What I tried:我试过的:

  • The shell gets started in -i nteractive mode shell 以-i交互模式启动
  • I use the shell-builtin set -m command to enable job control我使用 shell-builtin set -m命令启用作业控制
  • For debugging purposes I read out the $- variable, it seems to be himBHs出于调试目的,我读出了$-变量,它似乎是himBHs
  • Sending arbitrary commands usually works (eg ls )发送任意命令通常有效(例如ls
  • Sending \\x04 (end-of-transmission, Ctrl+D) works and kills the shell.发送\\x04 (传输结束,Ctrl+D)会起作用并杀死外壳。

How could I appropriately kill the running process, without opening the shell again?如何在不再次打开外壳的情况下适当地终止正在运行的进程?

QProcess process;
process.start("/bin/sh", QStringList() << "-i");
process.write("set -m\necho $-\n");                 // returns himBHs
process.waitForBytesWritten();

// start a running program here (E.g. tail -f logfile)
process.write("tail -f logfile\n");

process.write("\x03");
process.write("newcommand\n");
process.waitForBytesWritten();

Running the first command inside the shell returns output on stdout, but I don' t receive anything anymore after sending the ETX and the next command, although the shell is still running ( process.state() == QProcess::Running )在 shell 中运行第一个命令会在 stdout 上返回输出,但是在发送 ETX 和下一个命令后我没有再收到任何东西,尽管 shell 仍在运行( process.state() == QProcess::Running

  1. Is there a better way to send control signals or to communicate with the child's child process?有没有更好的方法来发送控制信号或与孩子的子进程通信?
  2. What could I possibly do to start a new program inside the shell without reopening the shell again?在不重新打开 shell 的情况下,我可以做些什么来在 shell 中启动一个新程序? (The reason I'm asking this is because the program is probably going to use ssh as the shell and I want it to avoid re-initiating a entirely new connection for a minor program/argument change) (我问这个的原因是因为该程序可能会使用 ssh 作为 shell,我希望它避免为小程序/参数更改重新启动一个全新的连接)

A shell never sees Ctrl-C . 外壳永远不会看到Ctrl-C It is interpreted by the (pseudo)-terminal, and converted to SIGINT , that is then acted on. 它由(伪)终端解释,并转换为SIGINT ,然后对其起作用。

Locally, start the program in a sub-shell that reports its pid, and then use that PID to kill it directly. 在本地,在报告其pid的子外壳中启动程序,然后使用该PID直接将其杀死。

#include <QtCore>
#include <signal.h>
#include <cstdio>

int getPID(const QByteArray &line) {
   int pid = 0;
   char c1, c2;
   if (sscanf(line.data(), "@@@%d@@%c%c", &pid, &c1, &c2) == 3)
      if (c1 == '@' && (c2 == '\r' || c2 == '\n')) return pid;
   return 0;
}

int main(int argc, char *argv[]) {
   auto input = QByteArray(
                    "echo _kill_me_now_ > log\n"
                    "/bin/sh -c 'echo @@@$$@@@>&2; exec tail -f log'\n"
                    "echo done\n"
                    "exit\n")
                    .split('\n');
   // tail -f will block

   QCoreApplication app(argc, argv);
   QProcess process;
   int pid = 0;

   auto const writeInputLine = [&] {
      if (input.isEmpty()) return;
      auto const line = input.takeFirst();
      puts(line.data());
      fflush(stdout);
      process.write(line);
      process.write("\n");
   };

   process.setProcessChannelMode(QProcess::SeparateChannels);
   QObject::connect(&process, &QProcess::stateChanged, [](auto state) {
      auto static const meta = QMetaEnum::fromType<QProcess::ProcessState>();
      fprintf(stderr, "State=%s\n", meta.key(state));
      fflush(stderr);
      if (state == QProcess::NotRunning) QCoreApplication::quit();
   });
   QObject::connect(&process, &QProcess::readyReadStandardError, [&] {
      auto const data = process.readAllStandardError();
      if (auto p = getPID(data)) pid = p; // we could suppress pid output here
      fputs(data.data(), stdout);
      fflush(stdout);
      if (data.endsWith("$ ")) writeInputLine();
   });
   QObject::connect(&process, &QProcess::readyReadStandardOutput, [&] {
      while (process.canReadLine()) {
         auto const line = process.readLine();
         fputs(line.data(), stdout);
         if (line.startsWith("_kill_me_now_") && pid) {
            kill(pid, SIGTERM);
            pid = 0;
         }
      }
      fflush(stdout);
   });

   process.start("/bin/sh", {"--noediting", "-i"});
   return app.exec();
}

With ssh, since you need to forward a signal to the remote process, and thus you need a remote controlling terminal ( ssh -t ). 使用ssh,由于您需要将信号转发到远程进程,因此需要一个远程控制终端( ssh -t )。 And for that you will be sending a Ctrl-C , that the remote terminal will reinterpret as a proper signal. 为此发送Ctrl-C ,远程终端将重新解释为适当的信号。

have you tried to play with the Signal and Slots for QProcess ? 您是否尝试过将信号和插槽用于QProcess Here is a simple example: 这是一个简单的示例:

test::test(QObject* p)
    : QObject (p),
      process{}
{

    connect(&process, SIGNAL(finished(int, QProcess::ExitStatus) ),
                    this, SLOT(hFinish()) );

    process.start("/bin/sh", QStringList() << "-i");
    process.write("set -m\necho $-\n");                 // returns himBHs
    process.waitForBytesWritten();

    // start a running program here (E.g. tail -f logfile)
    process.write("tail -f logfile\n");

    process.write("\x03");
    process.write("newcommand\n");
    process.waitForBytesWritten();

}

void test::hFinish()
{
    this->process.kill();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM