Home > Enterprise >  How can I wrap an interactive program into non-interactive which works with PIPEs?
How can I wrap an interactive program into non-interactive which works with PIPEs?

Time:10-12

Suppose I want to wrap some interactive program (e.g. 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?


(edited 2022/10/11)

There is some solution we can process the stdin, but if we need to PIPE it's stdout to another program, like:

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

(here other-program might be some programs which accepts input from stdin, e.g. gpg --import)

Then we need to control the stdout of my-keygen, since expect will print extra stuffs. It will be better if your solution shows how to achive that.

(I already recived some suggestion that expect is not a good choice for this purpose (thanks @pynexj , so feel free to post your answer if you have better solution which is not based on expect)

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 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:

Use while read construction like this:

#!/bin/bash

while read -r input; do
    opts =("$input")
done

echo "${opts[@]}"

Testing:

$ echo -e "myinfo1\nmyinfo2\nmysecret3" | ./test
myinfo1 myinfo2 mysecret3

Using with ssh-keygen:

$ ssh-keygen --help
unknown option -- -
usage: ssh-keygen [-q] [-b bits] [-C comment] [-f output_keyfile] [-m format]
                  [-t dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa]
                  [-N new_passphrase] [-O option] [-w provider]
       ssh-keygen -p [-f keyfile] [-m format] [-N new_passphrase
...

ssh-keygen -p -f "${opts[0]}" -m "${opts[1]}" -N "${opts[2]}"
  • Related