Bash – Can Bash Write to Its Own Input Stream?

bash

Is it possible in an interactive bash shell to enter a command that outputs some text so that it appears at the next command prompt, as if the user had typed in that text at that prompt ?

I want to be able to source a script that will generate a command-line and output it so that it appears when the prompt returns after the script ends so that the user can optionally edit it before pressing enter to execute it.

This can be achieved with xdotool but that only works when the terminal is in an X window and only if it's installed.

[me@mybox] 100 $ xdotool type "ls -l"
[me@mybox] 101 $ ls -l  <--- cursor appears here!

Can this be done using bash only?

Best Answer

With zsh, you can use print -z to place some text into the line editor buffer for the next prompt:

print -z echo test

would prime the line editor with echo test which you can edit at the next prompt.

I don't think bash has a similar feature, however on many systems, you can prime the terminal device input buffer with the TIOCSTI ioctl():

perl -e 'require "sys/ioctl.ph"; ioctl(STDIN, &TIOCSTI, $_)
  for split "", join " ", @ARGV' echo test

Would insert echo test into the terminal device input buffer, as if received from the terminal.

A more portable variation on @mike's Terminology approach and that doesn't sacrifice security would be to send the terminal emulator a fairly standard query status report escape sequence: <ESC>[5n which terminals invariably reply (so as input) as <ESC>[0n and bind that to the string you want to insert:

bind '"\e[0n": "echo test"'; printf '\e[5n'

If within GNU screen, you can also do:

screen -X stuff 'echo test'

Now, except for the TIOCSTI ioctl approach, we're asking the terminal emulator to send us some string as if typed. If that string comes before readline (bash's line editor) has disabled terminal local echo, then that string will be displayed not at the shell prompt, messing up the display slightly.

To work around that, you could either delay the sending of the request to the terminal slightly to make sure the response arrives when the echo has been disabled by readline.

bind '"\e[0n": "echo test"'; ((sleep 0.05;  printf '\e[5n') &)

(here assuming your sleep supports sub-second resolution).

Ideally you'd want to do something like:

bind '"\e[0n": "echo test"'
stty -echo
printf '\e[5n'
wait-until-the-response-arrives
stty echo

However bash (contrary to zsh) doesn't have support for such a wait-until-the-response-arrives that doesn't read the response.

However it has a has-the-response-arrived-yet feature with read -t0:

bind '"\e[0n": "echo test"'
saved_settings=$(stty -g)
stty -echo -icanon min 1 time 0
printf '\e[5n'
until read -t0; do
  sleep 0.02
done
stty "$saved_settings"

Further reading

See @starfry's answer's that expands on the two solutions given by @mikeserv and myself with a few more detailed information.