Bash – How to automatically insert a string after the prompt

bashcommand lineprompt

I have a bash terminal window where I execute exclusively one command foo. Because of that I'd like every line (after the prompt of course) to begin with „foo ” so that I just have to type the functions' options and arguments, but not the recurrent function name. Of course it would be nice to be able to alter the automatically inserted string too, but that's not essential for me.

Example

When I open the terminal, without typing anything what I want to see is:

user@host:~/ $ foo 

Then I type --option argument and when I press Enter the function foo is called with the given --option and argument.

What I tried

I tried to fiddle around with $PS1 and $PROMPT_COMMAND using xdotool type "foo " and astonishingly that actually works, but unfortunately it also prints “foo ” before the prompt, which is quite ugly:

user@host:~/ $ PROMPT_COMMAND='xdotool type "foo "'
foo user@host:~/ $ foo 

I also found and tried the preexec function from Ryan Caloras' bash-preexec script, but it has exactly the same problem.

How to echo (a (executable-)string) to the prompt, so that the cursor flashes at the end of the line? is related, but the answers there don't make it possible to add something (--option argument) to the command to be executed. I didn't test zsh though – there ought to be a bash solution for such a simple thing, don't you think?

Best Answer

With zsh, it should just be a matter of:

precmd() print -z 'foo '

Or to avoid it overriding commands queued by the user with Alt+q:

zle-line-init() { [ -n "$BUFFER" ] || LBUFFER='foo '; }
zle -N zle-line-init

With bash, instead of xdotool, you could use the TIOCSTI ioctl:

insert() {
  perl -le 'require "sys/ioctl.ph";
            ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV' -- "$@"
}
PROMPT_COMMAND='insert "foo "'

It's preferable to xdotool because it's inserting those characters directly in the input buffer of the device bash is reading from. xdotool would only work if there's a X server running (wouldn't work on the console or real terminals or over ssh (without -X) for instance), that it is the one identified by $DISPLAY and that's the one you're interacting with, and that the terminal emulator bash is running in has the focus when $PROMPT_COMMAND is evaluated.

Now, like in your xdotool case, because the ioctl() is done before the prompt is displayed and the tty terminal line discipline is put out of icanon+echo mode by readline, you're likely to see the echo of that foo by the tty line discipline messing up the display.

You could work around that by instead inserting a string whose echo is invisible (like U+200B if using exclusively Unicode locales) and bind that to an action that inserts "foo ":

insert() {
  perl -le 'require "sys/ioctl.ph";
            ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV' -- "$@"
}
bind $'"\u200b":"foo "'
PROMPT_COMMAND="insert $'\u200b'"

Or you could delay the TIOCSTI ioctl enough for readline to have time to initialise:

insert_with_delay() {
  perl -le 'require "sys/ioctl.ph";
            $delay = shift @ARGV;
            unless(fork) {
              select undef, undef, undef, $delay;
              ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV;
            }' -- "$@";
}
PROMPT_COMMAND='insert_with_delay 0.05 "foo "'

If, like in the zsh approaches, you want to handle the case where the user enters text before the prompt is displayed, you could either

  • drain that by doing a tcflush(0, TCIFLUSH) in perl before the TIOCSTI ioctl (also needs a -MPOSIX option), or
  • like in the zsh approach, ensure the foo is inserted at the start of the buffer by inserting a ^A (assuming you use the emacs (default) editing mode where that moves the cursor to the beginning of the line) before foo and ^E after (to move to the end):

    insert_with_delay 0.05 $'\1foo \5'
    

    or

    bind $'"\u200b":"\1foo \5"'
    
Related Question