Process 通过向stdin发送ctrl-c将SIGINT发送到进程

Process 通过向stdin发送ctrl-c将SIGINT发送到进程,process,rust,signals,pty,Process,Rust,Signals,Pty,我正在寻找一种模拟终端进行自动化测试的方法:即启动一个进程,然后通过向stdin发送数据和从stdout读取数据与之交互。例如,向stdin发送一些输入行,包括ctrl-c和ctrl-\,这将导致向进程发送信号 使用std::process::Commannd我可以将输入发送到例如cat,并且我也可以在stdout上看到它的输出,但是发送ctrl-c(as)不会导致SIGINT发送到shell。例如,该计划应在以下情况下终止: use std::process::{Command, Stdio}

我正在寻找一种模拟终端进行自动化测试的方法:即启动一个进程,然后通过向stdin发送数据和从stdout读取数据与之交互。例如,向stdin发送一些输入行,包括
ctrl-c
ctrl-\
,这将导致向进程发送信号

使用
std::process::Commannd
我可以将输入发送到例如
cat
,并且我也可以在stdout上看到它的输出,但是发送
ctrl-c
(as)不会导致
SIGINT
发送到shell。例如,该计划应在以下情况下终止:

use std::process::{Command, Stdio};
use std::io::Write;

fn main() {
    let mut child = Command::new("sh")
        .arg("-c").arg("-i").arg("cat")
        .stdin(Stdio::piped())
        .spawn().unwrap();
    let mut stdin = child.stdin.take().unwrap();
    stdin.write(&[3]).expect("cannot send ctrl-c");
    child.wait();
}
我怀疑问题在于发送
ctrl-c
需要一些tty,而通过
sh-I
它只处于“交互模式”

我是否需要完全成熟并使用,例如,或


更新:我在原始问题中混淆了shell和terminal。我现在把这事弄清楚了。我还提到了
ssh
,它应该是
sh

尝试添加-t选项两次以强制伪tty分配。即

klar (16:14) ~>echo foo | ssh user@host.ssh.com tty
not a tty
klar (16:14) ~>echo foo | ssh -t -t user@host.ssh.com tty
/dev/pts/0
当您有一个伪tty时,我认为它应该按照您的意愿将其转换为SIGINT


在您的简单示例中,您也可以在写入后关闭stdin,在这种情况下,服务器应该退出。对于这种特殊情况,它将更优雅,可能更可靠。

经过大量研究,我发现自己做pty叉子并不需要太多工作。有,但它有错误,似乎没有维护

以下代码需要crates.io上尚未出现的代码,因此
Cargo.toml
目前需要此代码:

[dependencies]
nix = {git = "https://github.com/nix-rust/nix.git"}
以下代码在tty中运行cat,然后从中写入/读取并发送Ctrl-C(
3
):


最简单的方法是直接将SIGINT信号发送到子进程。这可以使用的
signal::kill
函数轻松完成:

// add `nix = "0.15.0"` to your Cargo.toml
use std::process::{Command, Stdio};
use std::io::Write;

fn main() {
    // spawn child process
    let mut child = Command::new("cat")
        .stdin(Stdio::piped())
        .spawn().unwrap();

    // send "echo\n" to child's stdin
    let mut stdin = child.stdin.take().unwrap();
    writeln!(stdin, "echo");

    // sleep a bit so that child can process the input
    std::thread::sleep(std::time::Duration::from_millis(500));

    // send SIGINT to the child
    nix::sys::signal::kill(
        nix::unistd::Pid::from_raw(child.id() as i32), 
        nix::sys::signal::Signal::SIGINT
    ).expect("cannot send ctrl-c");

    // wait for child to terminate
    child.wait().unwrap();
}

您应该能够使用此方法发送各种信号。要实现更高级的“交互性”(例如,查询终端大小的子程序,如
vi
),您需要创建一个伪终端,如@hansaplast在其解决方案中所做的那样。

如果按Ctrl-C键,这些按键将永远无法进入应用程序。它们由终端处理,终端通过向进程发送SIGINT来响应。所以您想将SIGINT发送到进程。@sepp2k谢谢您的提问。我知道shell将ctrl-c转换为SIGINT,但不知何故忘记在问题中添加这一位。现在问题应该更清楚了。我还添加了
-I
选项,它应该在交互模式下运行sh,但它仍然没有work@hansaplast处理Ctrl-C的不是shell(怎么可能呢?一旦应用程序启动,shell就不再控制了),而是终端。因此,没有必要通过shell而不是直接调用
cat
,向应用程序发送Ctrl-C也没有任何作用。相反,您应该直接将SIGINT发送到进程。@sepp2k感谢您的解释。的确,我把终端和外壳搞混了。那么我的猜测是正确的,即
tty
丢失了。最后,我想构建一些东西,我可以通过“文档测试”来测试我的rust二进制文件,但我不想要rust代码,而是想要shell交互,包括ctrl-c,我明确地想要测试二进制文件和终端之间的交互。我目前正在研究
termion
,这看起来是目前最简单的方法,但首先我想使用
cat
(或其他一些命令),而不是
ssh
,其次我想用crust控制这个过程。在我看来,我真的需要一个tty fork,否则Ctrl-C和类似的工具将永远无法工作,请参阅下面的答案。请参阅nix::fcntl:{O_RDWR,open};| ^ ^ ^ ^没有
O_RDWR
fcntl
中,我已更新了代码,以使用当前版本的
nix
(0.15.0)。代码位于github上:
hello world
hello world
^C
cat exit code: Signaled(2906, SIGINT, false)
// add `nix = "0.15.0"` to your Cargo.toml
use std::process::{Command, Stdio};
use std::io::Write;

fn main() {
    // spawn child process
    let mut child = Command::new("cat")
        .stdin(Stdio::piped())
        .spawn().unwrap();

    // send "echo\n" to child's stdin
    let mut stdin = child.stdin.take().unwrap();
    writeln!(stdin, "echo");

    // sleep a bit so that child can process the input
    std::thread::sleep(std::time::Duration::from_millis(500));

    // send SIGINT to the child
    nix::sys::signal::kill(
        nix::unistd::Pid::from_raw(child.id() as i32), 
        nix::sys::signal::Signal::SIGINT
    ).expect("cannot send ctrl-c");

    // wait for child to terminate
    child.wait().unwrap();
}