There seems to be quite a lot of information on how to edit and execute a command using your editor using "edit-and-execute-command (C-x C-e)", but what I would like to achieve is take the current shell command, apply certain filtering (using a script) and then return it to prompt for further approval/manual changes before execution. Is this possible with bash?
Latest update based on my experience
The part 0"+y$dd in the following mapping is really something that you should carefully think about and tailor it to your taste/workflow/experience.
For instance, very frequently I've found myself ending up with multiple lines in the buffer, where I only want to execute the one the cursor is on; in this case I can use 0"+y$dd:%d<CR> instead of 0"+y$dd.
And this is just one of the possible scenarios.
Final answer for those who like vim
Set vim as your EDITOR/VISUAL, so that when editing a command line, you will use vim to edit it.
Put au BufEnter /tmp/bash-fc.* nn <Leader>d 0"+y$dd:wq<CR> in your ~/.vimrc file to map Leaderd (which you will rarely use when editing a command) to the action "delete the current line into the + register without the trailing EOL".
you can use either the + or the * register in the mapping above; the ways to paste into the terminal will likely differ; you need the +clipboard option for these registers to be available.
When finished editing a command in the vim editor, hit EscapeLeaderd.
Paste the clipboard into the terminal (this is terminal-dependent).
Original answer
I often need to do the same, and I do it as follows. (I normally use the set -o vi in bash, so points 1 and 2 in the following are different if you use set -o emacs, the default; based on your question it looks like points 1 and 2 are unified in Ctrl+x followed by Ctrl+e, which is harder to type, imho.)
hit Escape to be in normal mode,
hit v to enter the editor to edit the command,
edit the command as I like,
(This is where you ask the question.)
hit Escape0"+y$dd:wq,
Note: 0"+y$, not simply "+yy, as the latter would copy the newline too, and this would result in executing the command upon pasting it in the command line,
paste the clipboard on the command line
how to do this depends on the terminal you are using, I guess; I hit Ctrl+Alt+v in URxvt.
proceed to approval/manual edit.
Clearly this is just a workaround, consisting in copying the edited command into the clipboard before deleting the whole command, so that nothing gets executed upon exiting the editor; however it's the best I can get for myself.
Update
As my EDITOR (and VISUAL) is equal to vim, when I edit the command, I edit it in vim.
In this respect, I have noticed that the buffer is named /tmp/bash-fc.random, where random is a 6-characters alphanumeric random string.
This gives space to a lot of possiblities, if you use vim as your editor, as you can define some mapping in your .vimrc to execute the whole sequence Escape0"+y$dd:wq. For instance, one command that you'd rarely use when editing a command line is Leaderd; therefore you can put the following mapping in your .vimrc file
au BufEnter /tmp/bash-fc.* nn <Leader>d 0"+y$dd:wq<CR>
so that step 4 in the above recipe becomes
hit EscapeLeaderd
It's not possible to do that in Bash/readline but it's possible in zsh
using edit-command-line command:
darkstar% autoload edit-command-line; zle -N edit-command-line
darkstar% bindkey "^X^E" edit-command-line
Now press Control-x Control-e to open your editor, edit line, leave the editor - you will see the updated command line but it will not be executed automatically.
Now that I think about it, maybe a variation of what #kenorb suggested in a comment is the best workaround (as it seems no solution exists), if we want to stick to bash.
What you can do is prepend a # (the comment character in bash) to the command, rather than echo. Then when you exit the editor, the command will be ineffective, and you will only have to press arrow up (or k, if you use set -o vi), remove the # and confirming.
Note that this strategy adds just a few keystrokes, so it can be fairly efficient, depending on your typing level.
These pieces might get you closer:
a) replace the the normal binding for newline newline (ctrl-M)
bind -x '"\C-M":date"
b) grab the current line from the history using !#
replace date with whatever script you want.
c) edit manually
d) if necessary, somehow tie in !!:p which prints the new command to the command line but does not execute it, thus letting you manually edit it.
e) using ctrl-J submit edited command rather than a newline
or they might not ....
There is an option in bash to modify command from history without executing it. I'm not sure it it's possible to use script for this, doesn't seem to be likely. Although, you can make modifications using history modifiers.
Enable option histverify to prevent execution of modified command
Use chain of modifiers to change last command
Use "!!" to put your result to command line for final edit
Here is how it looks:
$ shopt -s histverify
$ ls *.sh
script1.sh script2.sh script3.sh script-return.sh
$ !!:s/*/script1/:p
ls script1.sh
$ !!:s/1/2/:p
ls script2.sh
$ !!
$ ls script2.sh
script2.sh
I'd like to point you to the Composure framework for Bash (I'm not affiliated with it): https://github.com/erichs/composure
It provides draft and revise functions that sound like they could help with what you're trying to do. Here's a (long) quote from the project's readme file:
Composure helps by letting you quickly draft simple shell functions,
breaking down your long pipe filters and complex commands into
readable and reusable chunks.
Draft first, ask questions later
Once you've crafted your gem of a command, don't throw it away! Use
draft () and give it a good name. This stores your last command as a
function you can reuse later. Think of it like a rough draft.
$ cat servers.txt
bashful: up
doc: down
up-arrow
$ cat servers.txt | grep down
doc: down
$ draft finddown
$ finddown | mail -s "down server(s)" admin#here.com
Revise, revise, revise!
Now that you've got a minimal shell function, you may want to make it
better through refactoring and revision. Use the revise () command
to revise your shell function in your favorite editor.
generalize functions with input parameters
add or remove functionality
add supporting metadata for documentation
$ revise finddown
finddown ()
{
about finds servers marked 'down' in text file
group admin
cat $1 | grep down
}
$ finddown servers.txt
doc: down
It does not seem possible with a keyboard shortcut, at least:
$ bind -P | grep -e command -e edit
complete-command can be found on "\e!".
edit-and-execute-command can be found on "\C-x\C-e".
emacs-editing-mode is not bound to any keys
possible-command-completions can be found on "\C-x!".
vi-editing-mode is not bound to any keys
This can be done in native bash using readline specifically READLINE_LINE and READLINE_POINT variables. I use this functionality all the time though not through vim, you would need to get the value of $selected from your vim command and if not empty it takes your original line + your input and replaces your original line with the combination without executing. output as a variable
_main() {
selected="$(__coms_select__ "$#")"
origonal_text=$READLINE_LINE READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
}
bind -m emacs-standard -x '"\C-e": _main '
bind -m vi-command -x '"\C-e": _main '
bind -m vi-insert -x '"\C-e": _main '
Edit
Just remembered these two utilities that will let you do this as well.
Vipe allows you to run your editor in the middle of a unix pipeline and edit the data that is being piped between programs.
vp, up, vipe, Neomux (upgrade of nvim terminal) you can do some pretty neat throwing buffers between the terminal and split window.
and Athame (full vim on the command line)
https://github.com/ardagnir/athame
careful with that one though plugins work on the cli and it can get funky if you got tons of plugins
I am writing a bash completion program in Golang. In fact, the program is its own completion program as it looks for the COMP_LINE environment variable and if it is present, it outputs the completion options, and if not, just proceeds to run the main program.
The completion is then installed with the following:
complete -C /path/to/my-program my-program
This works well. For most of my completions, I want a space to be added after the word has been completed, however for a few flags I do not want this to occur.
When completion is defined, you can set a -o nospace option to omit the trailing space when completing a word. However then all completions that need a space have to have one added explicitly to the completion word list.
Is there any way that my program can modify the complete opts dynamically based on what completion it is returning? Is this exposed as an environment variable that a completion command could set?
I would like to avoid having to append a space to all other completions just to avoid one in the edge case for the one flag I don't want that to happen on.
My Perl framework (Perinci::CmdLine) also does the same: the scripts are their own completion, activated using complete -C SCRIPTNAME SCRIPTNAME (when the script is in PATH). Completing using an external command has its pro's and con's compared to using shell function. To solve the problem you encountered, I output a dummy answer with an extra space. Since there are more than one answer, bash no longer automatically adds a space. So instead of just returning (in JSON notation):
["-o"]
you return:
["-o","-o "]
I also use this trick when doing path completion. To allow user completing a path by "drilling down", when there is a single directory match I output:
["dirname/","dirname/ "]
so the user can Tab again to drill down inside path instead of getting a space after "dirname/ " and having to backspace and Tab again.
When I source this script, through a readline rebind of the the enter key, I pass every line entered through a custom function, which should render normal bash behaviour - except when the current line is detected to be "special":
my_eval () {
local cur_line="$READLINE_LINE"
(...) <some special checks and actions if line matches criteria >
# when crits DON'T match I want normal behaviour:
eval "$cur_line" # <- the only way?
READLINE_LINE=""
}
bind -x '"\C-M": "my_eval"'
My problem is how to get normal behaviour in such an enter handler: Is calling eval the only way or would it be possible to fall back at this point to readline's normal behaviour when enter is pressed (i.e. invoke its accept-line function somehow, which is normally bound to \C-M, according to bind -P)?
With the eval based solution I have to take care of handling multiline expressions manually, also redirections for interactive commands, history, display prompt and command before showing result...
could really not find a way to invoke readline's accept-line programmatically from a script.
So I tried to mimic the behaviour manually. Far better than eval seems to be to push into history, then use fc -s to run the last command.
That works for me... okay-ish, maybe of use to someone, so I put it on:
https://github.com/axiros/readline_proxy/
Basically I push the expressions as provided within $READLINE_LINE into the history via history -s when they are deemed complete. And then run the last statement in history via fc -s.
Here is the code.
I was wondering if it was possible to manipulate the number of characters typed as a command. The reason for this, is I am writing a custom function to complete commands (when you press TAB), but I am using the complete function only for prompting the user, and not completing. What I want to be able to do is complete a typed word as such:
User$ command param[TAB]
prefixparam aaaparambbb
User$ command param
User$ command aparam[TAB]
User$ command aaaparambbb
Since I am completing the param as the prefix and suffix (and in some cases replacing it entirely), I can't use the builtin complete functionality, and instead I am looking for a workaround (hopefully without getting too deep).
How my current function works
When you press tab, it will parse the command and determine the possible completions. Then it will type the bash prompt again ($PS1) and the part of the command that was typed already. (User$ command param)
I want it so that when there is only 1 possible option, for it to replace your partial command with the only remaining option. This is already possible since I am manually reprompting the user, but I am having problems since the shell will not allow you to backspace more than you typed originally.
User$ command aparam[TAB]
User$ command aaapa[rambbb] // Can only backspace 6 characters
I need some way to trick the shell into thinking that all 11 characters were typed and can be deleted.
=========================================================================
I have found almost what I wanted to be possible using bind for anyone interested: bash expand cd with shortcuts like zsh
You can modify the $READLINE_LINE and $READLINE_POINT variables to modify the command. However, this only works for bind commands, and binding to TAB would overwrite and break other autocomplete functions which I would prefer not to.
What I want to do is simple: add a keybinding to one of my program using readline startup file inputrc but, in addition, as my program does not produce any output, I do not want the command name to appear on stdout.
What my problem is:
.inputrc content:
"\e[1;5A":'pipe_send\n'
When I hit ctrl+uparrow, on the command line appears "pipe_send":
[ alexkag#$$$$$:: / ]
$ pipe_send
What I'd like is not having pipe_send appear on the command line, just like the commands provided by readline such as history-search-backward, history-search-forward, etc.
Do you know any way to do that? Maybe shoudn't I use readline? Note: my keybinding must only be visible in bash, not to the whole system.
As mentioned in the comments by gniourf_gniourf the solution is:
bind -x '"\e[1;5A":pipe_send'
bind -x will tell bash to execute a command whenever a certain key is pressed:
-x keyseq:shell-command
Cause shell-command to be executed whenever keyseq is entered. When shell-command is executed, the shell sets the READLINE_LINE variable to the contents of the Readline line buffer and the READLINE_POINT variable to the current location of the insertion point. If the executed command changes the value of READLINE_LINE or READLINE_POINT, those new values will be reflected in the editing state.
\e[1;5A is the terminal code sent for CtrlUp