【问题标题】:Use QProcess to send EndOfText (Ctrl-C) to interactive shell使用 QProcess 将 EndOfText (Ctrl-C) 发送到交互式 shell
【发布时间】:2019-02-06 15:44:32
【问题描述】:

我使用 QProcess 打开 /bin/sh/usr/bin/bash,可以将命令写入 shell 并将输出读入我的程序。

实际问题发生在尝试向 shell 发送文本结束控制信号以中止 shell 正在运行的子进程时。

我尝试了什么:

  • shell 以-interactive 模式启动
  • 我使用 shell-builtin set -m 命令来启用作业控制
  • 出于调试目的,我读出了$- 变量,它似乎是himBHs
  • 发送任意命令通常有效(例如ls
  • 发送\x04(传输结束,Ctrl+D)有效并杀死shell。

如何在不再次打开 shell 的情况下适当地终止正在运行的进程?

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();

在 shell 中运行第一个命令会在 stdout 上返回输出,但在发送 ETX 和下一个命令后我不再收到任何内容,尽管 shell 仍在运行 (process.state() == QProcess::Running)

  1. 是否有更好的方法来发送控制信号或与子进程的子进程通信?
  2. 如何在不重新打开 shell 的情况下在 shell 中启动新程序? (我问这个的原因是因为程序可能会使用 ssh 作为 shell,我希望它避免为一个小的程序/参数更改重新启动一个全新的连接)

【问题讨论】:

    标签: c++ bash qt shell qprocess


    【解决方案1】:

    shell 永远不会看到Ctrl-C。它由(伪)终端解释,并转换为SIGINT,然后对其进行操作。

    在本地,在报告其 pid 的子 shell 中启动程序,然后使用该 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();
    }
    

    使用 ssh,因为您需要将信号转发到远程进程,因此您需要一个 remote 控制终端 (ssh -t)。对于,您发送Ctrl-C,远程终端将重新解释为正确的信号。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-22
      • 1970-01-01
      • 2011-12-18
      • 1970-01-01
      • 2015-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多