Is there a way to "push" a string to the stdin stream of a program when calling it?
So that we would have the effect of
echo "something" | ./my_program
but instead of reading EOF after "something"
, my_program
would read its further input from the original stdin (e.g., the keyboard).
Example: Say we want to start a bash shell, but the first thing we would like to do inside it is to call date
. echo date | bash
would not do the job, as the shell would terminate after running date
.
This might work:
(echo "something"; cat -) | ./my_program
It creates a sub-shell where the first line of output comes from echo
and the rest comes from the standard input to cat
, which is the terminal (or the script's standard input, at any rate). I use the -
to emphasize that cat
is required to read from standard input — it is not simply that I've forgotten to specify "$@"
or something after the cat
command. Omitting the -
doesn't make an operational difference; it might make a comprehensibility difference.
Be aware that the input to my_program
is no longer a terminal but a pipe connected to the terminal, which can affect the behaviour of the program. The cat
process introduces delays, too.
Expect
If that doesn't do the job, then you probably need to use expect
instead. This is a general purpose tool for scripting interactions with other programs, and it uses pseudo-ttys (ptys) to make it appear as if a user at a terminal is communicating with the other program.
As anishsane notes, expect
has an interact
command that can be used to leave you with the user typing to the program after some fixed preamble.
Beware
Adapting this to your second scenario was not a comfortable experience:
(echo date; cat -) | bash -i
The -i
tells Bash it is an interactive shell. The date
worked, and I got a prompt after it, but I didn't get any more commands executed. Interrupt got me more prompts; pretty much everything else seemed to be ignored. That may be cat
doing too much buffering of its output.
I ended up killing that shell from another terminal window. I had better luck with:
(echo date; cat -) | bash
but there were no prompts from Bash. Be careful; make sure you know how to get out of trouble.
I/O Redirection Stunts
rici also pointed out that in this particular case, you could use:
{ { echo date; echo 'exec 0<&3-';} | bash -i; } 3<&0
That's rather clever because the trailing 3<&0
makes a copy of the original standard input (file descriptor 0) on descriptor 3, and then runs bash -i
with its input coming from two echo
statements. The first requests the date. The second re-redirects things so that the standard input now comes from file descriptor 3 (that's the 0<&3
part) — which is the original standard input, aka 'the terminal' — and closes file descriptor 3 too (that's the trailing -
; it is a Bash extension over POSIX shell I/O redirection).
To complement Jonathan Leffler's helpful answer with a potentially simpler, but limited alternative:
IF
./my_program
is bash
, as in the OP's example
- AND it's acceptable that side effects produced by the startup command are:
- EITHER: NOT visible AT ALL to the
bash
instance that stays open
- OR: limited to inheriting all modified variables as exported variables
you can try the following:
bash -c "$(<command that produces Bash commands>); exec bash -i"
Using the example of the date
command as the startup command:
bash -c "$(echo 'date'); exec bash -i"
This executes date
and then replaces the running bash
instance with a new instance, which then stays open (due to neither receiving stdin input nor having a command passed to it via -c
).
To illustrate that any changes made to the startup shell's environment are NOT propagated to the one that ultimately stays open, consider this:
bash -c "$(echo 'date; foo=bar; echo $foo'); exec bash -i"
This prints the date and assigns and prints variable $foo
, but if you then execute echo $foo
again, it will NOT be defined, because the shell instance that ultimately stays open does NOT inherit its state from the one that executed the startup commands.
You can partially work around this limitation by passing -a
to bash
, which then causes all variables modified or created by the startup commands to be automatically exported:
bash -ac "$(echo 'date; foo=bar; echo $foo'); exec bash -i"
Now, if you execute echo $foo
again in the stay-open instance, it WILL print bar
.
Note:
- Such variables effectively become environment variables, which means that any child processes created by the stay-open shell also see them.