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:
- How to send standard input through a pipe
- Can expect scripts read data from stdin?
- How can I pipe initial input into process which will then be interactive?
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:
create a file named
test
with following content#!/usr/bin/env expect spawn bash interact
run
chmod x ./test
to make it executablerun
echo 'echo hello' | ./test
and confirm thathello
is not printed as expectedrun
echo 'echo hello' | bash
and confirm thathello
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:
- Use
echo -e
echo -e "myinfo1\nmyinfo2\nmysecret3" | my-keygen
echo "myinfo1"$'\n'"myinfo2"$'\n'"mysecret3" | my-keygen
- Using actual newlines
echo "myinfo1
myinfo2
mysecret3" | my-keygen
- 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]}"