To avoid the XY-problem issue:
X problem: given an interactive TUI program, how can I wrap it into a non-interactive one (a batch script which accepts some inputs in specific format from stdin and output to stdout in specific format) in a suggested way.
Y problem: I'm trying to use expect
to wrap passphrase2pgp
into a batch script, but met some trouble when dealing with STDIN & STDOUT with pipe.
(PS: I prefer some advice for this kind of problems than a workaround of specific example, since it's not a complicated job, I think there must be some simple and direct way to achieve it, no tricks and workaround. But I'm not sure whether the implementation details of the be wrapped programs will make things very different, anyway, pointing this out -- if it is the fact -- will also be very helpful)
Suppose I want to wrap some interactive program (e.g. passphrase2pgp
, or ssh-keygen
which is more well-known) into an non-interactive script named my-keygen
, so that I can use it like
printf pwd | my-keygen | gpg --import
In this case, my-keygen
is expected to receive password from piped STDIN, and output the private key to STDOUT, which will be piped to gpg --import
.
But use passphrase2pgp
directly like printf pwd | passphrase2pgp --uid alice --repeat 0 | gpg --import
doesn't work at all, since it will still prompt for the passphrase interactivly.
This brings me to use expect
, following is my-keygen
using expect
:
(if you have nix
installed, change "expect" in shebang to "nix-shell" so you can run it directly without install passphrase2pgp
)
#!/usr/bin/env expect
#! nix-shell -p passphrase2pgp -p expect -i expect
spawn passphrase2pgp --uid alice --repeat 0 --armor
expect "passphrase:"
interact
But this script still doesn't work, when used like printf pwd | my-keygen
, it exit too early without printing anything out.
And I find it works when I answer questions from tty, it just does not work 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 finishing its job.
A minimal example to reproduce what happened 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 my-keygen
work as expected? And to be more general, what is the suggested way to such a thing?
(edited 2022/10/11)
The exit too early issue of the expect
version is fixed (thanks to many helpful answers):
just read lines in a while loop and send them one-by-one, and NOT use the interact
command of expect
at the end of the script.
But in this way, the output of the spawned process is not printed to stdout, if we need to pipe its STDOUT to another program, like gpg --import
, then we need to control the STDOUT of my-keygen
to only print the armored key, and no extra stuff. It will be better if your solution shows how to achieve that.
(I already received 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
.)