2017-05-04 73 views
0

我在寻找一种方法来模拟天生一些自动化测试的端子:即启动一个进程,然后通过将数据发送到stdin和从标准输出读取与它进行交互。例如。向stdin发送一些输入行,包括ctrl-cctrl-\,这应该导致发送信号给进程。发送SIGINT到过程通过发送CTRL-C至标准输入

使用std::process::Commannd我能够将输入发送给例如cat,我也看到了在标准输出上输出,但发送ctrl-c(如I understand that is 3)不会导致SIGINT发送到外壳。例如。这个计划应当终止:

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这只是在“交互模式”。

我是否需要全力以赴并使用termionncurses

更新:我在原来的问题混淆外壳和终端。我现在清除了这个。我还提到ssh应该是sh

+1

如果您按下Ctrl-C键,那么这些按键从不会输入到应用程序中。它们由终端处理,终端通过发送SIGINT来响应该终端。所以你想发送SIGINT到进程。 – sepp2k

+0

https://stackoverflow.com/questions/6108953/how-does-ctrl-c-terminate-a-child-process –

+0

@ sepp2k感谢您的问题。我意识到shell将ctrl-c转换为SIGINT,但不知何故忘了在问题中加入这一点。这个问题现在应该更清楚了。我还添加了'-i'选项,它应该在交互模式下运行sh,但它仍然不起作用 – hansaplast

回答

0

了大量的研究后,我想通了,这不是太多的工作要做PTY叉自己。有pty-rs,但它有错误,似乎没有维护。

下面的代码需要pty module of nix这是尚未在crates.io,所以Cargo.toml需要这个现在:

[dependencies] 
nix = {git = "https://github.com/nix-rust/nix.git"} 

下面的代码运行在一个tty猫,然后写从中/读取和发送按Ctrl -C(3):

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\n")?; 
      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"); 
} 

输出:

hello world 
hello world 
^C 
cat exit code: Signaled(2906, SIGINT, false) 
0

尝试添加-t选项TWICE来强制伪tty分配。即

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,如你所愿。

在您简单的例子,你也可以写,经过短短接近标准输入在这种情况下,服务器应该退出。对于这种特殊情况,它会更优雅,可能更可靠。

+0

但首先我想使用'cat'(或其他命令)而不是'ssh',然后我想用生锈来控制过程。国际海事组织我真的需要一个tty分支,否则Ctrl-C和类似的将永远不会工作,请参阅下面的答案 – hansaplast