Skip to content

rust-cli/rexpect

Folders and files

NameName
Last commit message
Last commit date
Dec 4, 2024
Nov 5, 2024
Nov 5, 2024
Apr 26, 2024
Mar 29, 2023
Oct 26, 2023
Mar 17, 2018
Nov 6, 2024
Nov 5, 2024
Dec 4, 2024
Dec 4, 2024
Mar 17, 2018
Mar 29, 2023
Mar 29, 2023
Nov 5, 2024
Nov 29, 2024
Oct 9, 2022
Mar 29, 2023
May 29, 2024
Mar 29, 2023
Sep 11, 2022

Repository files navigation

rexpect

Documentation License Crates Status

Spawn, control, and respond to expected patterns of child applications and processes, enabling the automation of interactions and testing. Components include:

  • session: start a new process and interact with it; primary module of rexpect.
  • reader: non-blocking reader, which supports waiting for strings, regex, and EOF.
  • process: spawn a process in a pty.

The goal is to offer a similar set of functionality as pexpect.

Examples

For more examples, check the examples directory.

Basic usage

Simple example for interacting via ftp:

use rexpect::spawn;
use rexpect::error::*;

fn do_ftp() -> Result<(), Error> {
    let mut p = spawn("ftp speedtest.tele2.net", Some(30_000))?;
    p.exp_regex("Name \\(.*\\):")?;
    p.send_line("anonymous")?;
    p.exp_string("Password")?;
    p.send_line("test")?;
    p.exp_string("ftp>")?;
    p.send_line("cd upload")?;
    p.exp_string("successfully changed.\r\nftp>")?;
    p.send_line("pwd")?;
    p.exp_regex("[0-9]+ \"/upload\"")?;
    p.send_line("exit")?;
    p.exp_eof()?;
    Ok(())
}

fn main() {
    do_ftp().unwrap_or_else(|e| panic!("ftp job failed with {}", e));
}

Example with bash and reading from programs

use rexpect::spawn_bash;
use rexpect::error::*;

fn do_bash() -> Result<(), Error> {
    let mut p = spawn_bash(Some(2000))?;

    // case 1: wait until program is done
    p.send_line("hostname")?;
    let hostname = p.read_line()?;
    p.wait_for_prompt()?; // go sure `hostname` is really done
    println!("Current hostname: {}", hostname);

    // case 2: wait until done, only extract a few infos
    p.send_line("wc /etc/passwd")?;
    // `exp_regex` returns both string-before-match and match itself, discard first
    let (_, lines) = p.exp_regex("[0-9]+")?;
    let (_, words) = p.exp_regex("[0-9]+")?;
    let (_, bytes) = p.exp_regex("[0-9]+")?;
    p.wait_for_prompt()?; // go sure `wc` is really done
    println!("/etc/passwd has {} lines, {} words, {} chars", lines, words, bytes);

    // case 3: read while program is still executing
    p.execute("ping 8.8.8.8", "bytes of data")?; // returns when it sees "bytes of data" in output
    for _ in 0..5 {
        // times out if one ping takes longer than 2s
        let (_, duration) = p.exp_regex("[0-9. ]+ ms")?;
        println!("Roundtrip time: {}", duration);
    }
    p.send_control('c')?;
    Ok(())
}

fn main() {
    do_bash().unwrap_or_else(|e| panic!("bash job failed with {}", e));
}

Example with bash and job control

One frequent bitfall with sending ctrl-c and friends is that you need to somehow ensure that the program has fully loaded, otherwise the ctrl-* goes into nirvana. There are two functions to ensure that:

  • execute where you need to provide a match string which is present on stdout/stderr when the program is ready
  • wait_for_prompt which waits until the prompt is shown again
use rexpect::spawn_bash;
use rexpect::error::*;

fn do_bash_jobcontrol() -> Result<(), Error> {
    let mut p = spawn_bash(Some(1000))?;
    p.execute("ping 8.8.8.8", "bytes of data")?;
    p.send_control('z')?;
    p.wait_for_prompt()?;
    // bash writes 'ping 8.8.8.8' to stdout again to state which job was put into background
    p.execute("bg", "ping 8.8.8.8")?;
    p.wait_for_prompt()?;
    p.send_line("sleep 0.5")?;
    p.wait_for_prompt()?;
    // bash writes 'ping 8.8.8.8' to stdout again to state which job was put into foreground
    p.execute("fg", "ping 8.8.8.8")?;
    p.send_control('c')?;
    p.exp_string("packet loss")?;
    Ok(())
}

fn main() {
    do_bash_jobcontrol().unwrap_or_else(|e| panic!("bash with job control failed with {}", e));
}

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.