Home > Software design >  How can I wrap an interactive program into non-interactive which accepts piped stdin?
How can I wrap an interactive program into non-interactive which accepts piped stdin?

Time:10-11

Suppose I want to wrap ssh-keygen into an non-interactive script named my-keygen, so that I can use it like

echo "myinfo1\nmyinfo2\nmysecret3" | my-keygen

This brings me to use expect:

spawn ssh-keygen

expect "Enter file in which to save the key..."

set line [gets stdin]

send "$line\n"

...repeat for more questions...

interact

And I find it works when I answer questions from tty, but it not works if the stdin is from pipe.

I searched a lot and found something useful:

Which let me know that there is an EOF in the piped content which will cause the interactive program exit without finish it's job.

A minimal example to reproduce what happend is:

  1. create a file named test with following content

    #!/usr/bin/env expect
    
    spawn bash
    
    interact
    
  2. run chmod x ./test to make it executable

  3. run echo 'echo hello' | ./test and confirm that hello is not printed as expected

  4. run echo 'echo hello' | bash and confirm that hello is printed as expected this way

So is there any solution to make ./test works as expect?

CodePudding user response:

The way you're echoing your string, the newlines are being sent as literal characters and not being interpreted as newlines so there is only one line of stdin being passed to expect that gets completely consumed the first time you read stdin. There are a couple ways to fix this:

  1. Use echo -e
echo -e "myinfo1\nmyinfo2\nmysecret3" | my-keygen
  1. ANSI-C Quoting
echo "myinfo1"$'\n'"myinfo2"$'\n'"mysecret3" | my-keygen
  1. Using actual newlines
echo "myinfo1
myinfo2
mysecret3" | my-keygen
  1. A here-document
my-keygen <<EOF
myinfo1
myinfo2
mysecret3
EOF

CodePudding user response:

(Not a direct answer. Just show the idea how to do it.)

Expect's interact does not read from the pipe. You need to read data from the pipe by yourself and then forward the data to Expect.

The following is a quick example using my sexpect. You can easily write the Expect script accordingly.

The script foo.sh:

export SEXPECT_SOCKFILE=/tmp/sexpect-$$.sock

ps1re='bash-[0-9.] [$#] $'

sexpect spawn bash --norc
sexpect expect -re "$ps1re"

while read -r cmd; do
    sexpect send -cr "$cmd"
    sexpect expect -re "$ps1re"
done

sexpect send -cr "exit"
sexpect wait

Let's try it:

$ printf '%s\n' tty 'echo hello world' | bash foo.sh
bash-5.2$ tty
/dev/ttys013
bash-5.2$ echo hello world
hello world
bash-5.2$ exit
exit
$

CodePudding user response:

The Expect version of @sexpect's answer:

$ cat foo.exp
proc expect_prompt {} {
    upvar spawn_id spawn_id
    expect -re {bash-[.0-9] [#$] $}
}

spawn -noecho bash --norc
expect_prompt

while { [gets stdin cmd] >= 0 } {
    send "$cmd\r"
    expect_prompt
}

send "exit\r"
expect eof
$ printf '%s\n' tty 'echo hello world' | expect foo.exp
bash-5.1$ tty
/dev/pts/13
bash-5.1$ echo hello world
hello world
bash-5.1$ exit
exit
  • Related