Process 通过向stdin发送ctrl-c将SIGINT发送到进程
我正在寻找一种模拟终端进行自动化测试的方法:即启动一个进程,然后通过向stdin发送数据和从stdout读取数据与之交互。例如,向stdin发送一些输入行,包括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}
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();
}