How do I push a string to stdin? Provide input via

2019-04-12 08:00发布

问题:

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.

回答1:

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).



回答2:

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.