Send SIGINT to a process by sending ctrl-c to stdin
我正在寻找一种模仿终端以进行一些自动化测试的方法:即启动一个过程,然后通过将数据发送到stdin并从stdout读取与之交互。 例如。 向stdin发送一些输入行,包括
使用
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); } |
我怀疑问题在于发送
我需要完全成熟并使用例如
更新:我在原始问题中混淆了外壳和端子。 我现在清除了。 我也提到了
最简单的方法是直接将SIGINT信号发送到子进程。使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 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\ " 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(); } |
您应该能够使用此方法发送各种信号。对于更高级的"交互性"(例如查询终端大小的
经过大量研究,我发现自己完成pty fork并不需要太多工作。有pty-rs,但是它有错误,而且似乎无法维护。
以下代码需要crates.io上尚未安装的
1 2 | [dependencies] nix = {git ="https://github.com/nix-rust/nix.git"} |
以下代码在tty中运行cat,然后从中写入/读取并发送Ctrl-C(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | extern crate nix; use std::path::Path; use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname}; use nix::fcntl::{O_RDWR, open}; use nix::sys::stat; use nix::unistd::{fork, ForkResult, setsid, dup2}; use nix::libc::{STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::io::prelude::*; use std::io::{BufReader, LineWriter}; fn run() -> std::io::Result<()> { // Open a new PTY master let master_fd = posix_openpt(O_RDWR)?; // Allow a slave to be generated for it grantpt(&master_fd)?; unlockpt(&master_fd)?; // Get the name of the slave let slave_name = ptsname(&master_fd)?; match fork() { Ok(ForkResult::Child) => { setsid()?; // create new session with child as session leader let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty())?; // assign stdin, stdout, stderr to the tty, just like a terminal does dup2(slave_fd, STDIN_FILENO)?; dup2(slave_fd, STDOUT_FILENO)?; dup2(slave_fd, STDERR_FILENO)?; std::process::Command::new("cat").status()?; } Ok(ForkResult::Parent { child: _ }) => { let f = unsafe { std::fs::File::from_raw_fd(master_fd.as_raw_fd()) }; let mut reader = BufReader::new(&f); let mut writer = LineWriter::new(&f); writer.write_all(b"hello world\ ")?; let mut s = String::new(); reader.read_line(&mut s)?; // what we just wrote in reader.read_line(&mut s)?; // what cat wrote out writer.write(&[3])?; // send ^C writer.flush()?; let mut buf = [0; 2]; // needs bytewise read as ^C has no newline reader.read(&mut buf)?; s += &String::from_utf8_lossy(&buf).to_string(); println!("{}", s); println!("cat exit code: {:?}", wait::wait()?); // make sure cat really exited } Err(_) => println!("error"), } Ok(()) } fn main() { run().expect("could not execute command"); } |
输出:
1 2 3 4 | hello world hello world ^C cat exit code: Signaled(2906, SIGINT, false) |
尝试添加-t选项TWICE强制伪伪分配。即
1 2 3 4 | klar (16:14) ~>echo foo | ssh [email protected] tty not a tty klar (16:14) ~>echo foo | ssh -t -t [email protected] tty /dev/pts/0 |
当您拥有伪tty时,我认为它应该按照您的意愿将其转换为SIGINT。
在您的简单示例中,您还可以在写入后关闭stdin,在这种情况下,服务器应退出。对于这种特殊情况,它会更优雅并且可能更可靠。