2

With $ as my bash prompt and symbolizing me hitting the enter key in the following example, how could I construct a command/alias foo so that

$ foo bar⏎

would enter/input/pre-fill/type "bar" (or any other string I pass to the command) to the command line, so that I can modify "bar" before hitting Enter? E.g.

$ bar
…
$ barbaz⏎ 

I've unsuccessfully tried echo bar > /dev/pts/123 and would like to do without xdotool. Is this possible?


EDIT: Example use case, a "greppy autocomplete":

I often need long commands with many arguments that are hard to remember. I keep examples in a file:

commands.txt:

ffmpeg -i infile.mp4 -c:v copy -c:a copy -y transcoded.mp4
sox in.wav out.wav remix 1 0

now, if I had the command as specified above, let's call it inject, I could have an alias

grepcomplete () {
  inject $(grep $0 commands.txt)
}

so when I remember that I need to remix something, but I don't remember sox and its arguments, I can type grepcomplete remix, and then have sox in.wav out.wav remix 1 0 sit on my command line, as if I typed it out, ready for me to edit and adapt, before I execute it by hitting enter. Without the need to select, copy, paste anything.

As Kamil suggests in the comments, I could use bash's history search (Ctrl-R), and provide my own "history" by doing something like history -r commands.txt in my bashrc.
Still, my approach has the benefit that I can easily hack it, e.g. by displaying all matches with syntax highlighting.

Please note that I've answered this question myself, where I provide an implementation of this inject command.

6
  • If this is to edit the output of a command or the value of a variable before you actually use it in another command, bash and zsh have keybindings that can expand all variable expansions, command substitutions, etc. That might be simpler than hacking your way around terminal input.
    – muru
    Jan 11 at 8:22
  • What is the usage case? I mean typing foo bar only to get bar in the command line clearly takes more effort than simply typing bar in the first place. Therefore I guess you want to use this foo in some other way. How exactly? Your question is remotely similar to this one, where the gain in effort is indisputable. Jan 11 at 8:29
  • @muru very true, I've played around with bash's ctrl+alt+e/C-M-e, but it wasn't quite right for my (slightly odd) workflow.
    – kubi
    Jan 11 at 8:30
  • @KamilMaciorowski of course this is just a building block & for the examples there is no gain in efficiency. The actual use case is a "completion/pre-fill with grep" for my most-used "commands that need adjustment", e.g. snippet remix will enter sox in.wav out.wav remix 1 0 # upmix mono to stereo into the prompt for me to edit and execute, without the need to select, copy, paste. Still I find this sub-problem worthwhile exploring without the context.
    – kubi
    Jan 11 at 8:37
  • An idea: a history file that holds your most-used "commands that need adjustment"; then Ctrl+R. Jan 11 at 8:58

5 Answers 5

1

Here's a small and ugly solution, inspired by a perl solution to insert a string after each prompt, using TIOCSTI adapted to python:

A bash function, e.g. for .bashrc

inject () {
(python -c "import fcntl; import termios; import sys
with open('/dev/stdout', 'w') as fd:
  for c in ' '.join(sys.argv[1:]): fcntl.ioctl(fd, termios.TIOCSTI, c)" "$@" &)
}

Usage:

inject foo bar

Notes:

  • can't handle Unicode at the moment! I suspect the for char in str is to blame?
  • The background-in-subshell construct ( foo &) prevents double-echo'ing the string before the prompt
  • It will eat whitespace between arguments: inject foo bar = inject foo bar. Use inject "foo bar".
  • It looks like it depends on timing, so race conditions might arise?
  • I had other versions with Python's threads/multiprocessing that can do without the bash subshell and might work better for piping, xargs etc. This was the cleanest and simplest solution for my use case.
1
  • 1
    Works great if you just want a Python program to be able to inject something to the outer Bash prompt.
    – Petter
    Mar 2 at 9:07
1

You can configure this using ~/.inputrc. Create the file if it doesn't exist, and add this:

Control-P: "foo "

Now, open a new terminal and press Ctrl+P and the string foo will be entered where your cursor is. You can choose a shortcut that works for you, and it should work.

6
  • Cool, thanks! But I need the string to be dynamic, hence I take it as an argument.
    – kubi
    Jan 11 at 13:30
  • Oh. In that case, @kubi, please edit your question and clarify that because it isn't mentioned anywhere. You only ask for a command to insert a specific string. How would you define this string then and if you need to define it, why not just type it out on the command line?
    – terdon
    Jan 11 at 13:37
  • Sorry! I felt like if I give bar as an argument, it would be self-explanatory that this is not a constant. (Any of the other answers were confused by this, FWIW.). I'm not sure I get your question about "defining" it, I want to be able do something like grep bar commands.txt | xargs foo, or bar=baz foo $bar... have you seen my own answer?
    – kubi
    Jan 11 at 14:10
  • @kubi I may be dense, but I still don't get it. You are writing inject foo bar which is more work than just writing foo bar directly. If you always need to write out what you are "injecting" then why not just write it out? If you want to run specific commands more easily, then you would write a function like run foo file1 that would run say grep foo file1 | head -9 | wc or whatever. But that's just a trivial little function, no need for a whole python import. Please edit your question and give a use case so we can understand what you need.
    – terdon
    Jan 11 at 14:19
  • 1
    Yes, thank you, @kubi, that makes it much clearer! I will delete this answer in a bit since it doesn't solve your issue at all. As for accepting, it is completely up to you. You can accept your own answer, yes, or another one if that works better. Just accept whichever one you end up using.
    – terdon
    Jan 11 at 15:39
0

xdotool will only work in X environment, and won't work in the TTY shell and may not work in Wayland.


I feel like you are asking about creating your own bash completion script. Then, "inject" (invoke the completion) with Tab key.

The basic idea is this complete command which says to bash what to suggest when user hits Tab key:

$ complete -W "now tomorrow never" dothis

Then, typing

$ dothis to<Tab>

fills it:

$ dothis tomorrow

For your example,

$ complete -W bar foo
$ foo<Space><Tab>

will produce

$ foo bar

Which you can edit futher.

The complete command itself could be placed into ~/.bashrc to be run during interactive shell creation.

1
  • I sort of am, yes :) and I have finished it. It's very specific to my workflow, but I still found this sub-problem interesting. And as you can see in my answer, I managed without xdotool -- your warning was exactly why I asked to avoid it. But thank you for your contribution, it will definitely help with future iterations of my setup!
    – kubi
    Jan 11 at 8:28
0

If switching to zsh is an option, then it's just:

print -rz -- "$text"

To seed zsh's line editor (zle) with the given text.

If done within a custom zle widget, you'd do:

LBUFFER+=$text

to insert the text at the cursor, or:

BUFFER+=$text

To insert at the end, or:

BUFFER[n,0]=$text

To insert it before the nth character in the editing buffer.

So

BUFFER[CURSOR+1,0]=$text

Would insert the text at the cursor without moving the cursor position, same as:

RBUFFER[1,0]=$text

With:

  • $BUFFER: full editing buffer
  • $CURSOR: current cursor position inside that buffer (0 at the start)
  • $LBUFFER/$RBUFFER: for convenience the portion of $BUFFER left and right of the cursor respectively.

See info zsh widgets for details.

1
  • Interesting, thanks. I've been comtemplating abandoning bash for years...
    – kubi
    Jan 11 at 13:33
0

In fish the equivalent is using the commandline function. For example:

commandline 'echo next command'

You must log in to answer this question.

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