rust-cli / rexpect

.github/workflows/ci.yml
https://docs.rs/rexpect
MIT License
328 stars 56 forks source link

rexpect interact(), giving control back to the user #30

Open chama-chomo opened 4 years ago

chama-chomo commented 4 years ago

Is there any way to return control of the terminal back to the user ? e.g. after connecting to a remote server. Thanks. In python pexpect I use interact() method to achieve this.

chama-chomo commented 4 years ago

as a Rust newbie I was able to create kind of a POC for my ssh repl, my only problem is it doesn't feel very much like a standard ssh session with command/path expansion and so. There is definitely a better way. Nevertheless, I'm going to provide my piece of code for someone like me, as inspiration (this is my specific case ofc, not too much generic)..


...
    fn connect_ssh(&self, dest: Option<&String>) -> Result<()> {
        ConnectAction::do_ssh_repl(&dest.unwrap())
            .unwrap_or_else(|e| panic!("ssh session failed with {}", e));
        Ok(())
    }

    fn do_ssh_repl(host: &String) -> Result<()> {
        let mut ssh = ConnectAction::create_ssh_session(&host)?;
        ssh.exp_regex("\r\n.*root.*:~# ")?;
        ConnectAction::interact(&mut ssh)?;
        ssh.exp_eof()?;
        Ok(())
    }

    fn create_ssh_session(host: &String) -> Result<PtyReplSession> {
        println!("Connecting to host {}", host);
        let custom_prompt = format!("root@{}'s password: ", host);
        let custom_command = format!("ssh root@{}", host);
        let mut ssh = PtyReplSession {
            echo_on: false,
            prompt: custom_prompt,
            pty_session: spawn(&*custom_command.into_boxed_str(), Some(5000))?,
            quit_command: Some("Q".to_string()),
        };
        ssh.wait_for_prompt()?;
        ssh.send_line("root")?;
        Ok(ssh)
    }

    fn interact(session: &mut PtyReplSession) -> Result<()> {
        while let Some(WaitStatus::StillAlive) = session.process.status() {
            let mut user_input = String::new();
            print!("ssh session :> ");
            io::stdout().flush().expect("couldn't flush buffer");
            io::stdin().read_line(&mut user_input).expect("error: unable to read user input");
            session.writer.write(&user_input.into_bytes()).expect("could not write");

            let re = Regex::new(r".*root@.*:~# ").unwrap();
            let (before, _) = session.reader.read_until(&rexpect::ReadUntil::Regex(re)).unwrap();

            println!("{}", before);
        };
        Ok(())
    }
BarretRen commented 1 year ago

@chama-chomo, your workaround looks good, but it's still not perfect. For example, if I start gdb task with rexpect, i want a real gdb tty after calling interact. But in this way, it can't offer me that. This issue is opened two years before, but no updates for it. I will use pexpect for my tools. Life is short, I use python.^v^

wolfv commented 1 year ago

We've implemented an interact functionality in the pixi package manager (to spawn an interactive shell where we can source some scripts after the shell has started, and then give control to the user).

You can have a look at the code here: https://github.com/prefix-dev/pixi/pull/316/files

There were some changes:

We've gone through a few iterations with this code and are reasonably happy with it now. I am not sure what the appetite is for these changes in rexpect. They are a bit deeper (especially with the ECHO part), but we could potentially make a PR to contribute the feature.