6

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?

2
  • 2
    askubuntu.com/questions/5363/…,
    – muru
    Sep 12, 2017 at 7:02
  • @muru I tested expect, but it doesn't enter the text on every prompt, but only at the first one. While that's wanted in the linked question, my goal is different.
    – dessert
    Sep 15, 2017 at 19:10

2 Answers 2

7

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"'
    
6
  • The insert function needs a bit of work to properly quote arguments that require quoting. Perhaps with perl ... -- "$(printf "%q " "$@")" Sep 11, 2017 at 23:10
  • Hi @glenn. Not sure what you mean. Here we want to insert foo or a U+200B character, not 'foo ' Sep 12, 2017 at 6:24
  • Perhaps I'm over-complicating matters. I was thinking of the case where the pre-populated text would be like foo "bar baz" where the 2nd argument needs to be quoted. Sep 12, 2017 at 13:34
  • @glennjackman, I'd say that has to be taken care of by the caller. insert here is meant to get shell code as argument (like eval) so you can call it with insert 'VAR="x y" cmd2 ' or insert 'cmd "${default_opt[@]}" ' for instance which you couldn't do if insert was quoting its arguments by itself. Sep 12, 2017 at 14:25
  • I used your astonishing approach in this Ask Ubuntu question, where the problem originally was raised: Main Terminal Window Displaying Commands Only
    – dessert
    Sep 15, 2017 at 19:38
4

What you're asking for doesn't sound very common so it isn't surprising that there isn't a straightforward way to do it.

With pure bash, you can use the read builtin to read the line, then eval to execute it. Simulate the prompt (I'm approximating your prompt below).

#!/bin/bash    
PROMPT=$'\e[1m'$USER@$HOSTNAME:$PWD$' $ \e[0m'
IFS= read -r -e -i "foo " -p "$PROMPT" line
eval "$line"
exit

Put this in a script and tell your terminal emulator to run that script.

Alternatively, run Screen in the terminal emulator. You can use its stuff command to simulate input.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .