rexpect icon indicating copy to clipboard operation
rexpect copied to clipboard

rexpect interact(), giving control back to the user

Open chama-chomo opened this issue 5 years ago • 3 comments

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 avatar Oct 24 '20 19:10 chama-chomo

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(())
    }

chama-chomo avatar Nov 01 '20 11:11 chama-chomo

@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^

BarretRen avatar Nov 21 '22 11:11 BarretRen

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:

  • set terminal to raw mode
  • enable ECHO for the terminal (to see what user types) - this is always deactivated in rexpect
  • be able to set the window size when the SIGWINCH signal happens
  • use select on the two filestreams (stdin of the parent and stdout of the child) to forward the input / output

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.

wolfv avatar Aug 30 '23 09:08 wolfv