23

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?

6
  • 1
    This is not the right answer to the question, but try using ZSH terminal instead of the native Bash. It has auto-completion so don't execute commands straight away. I have personally tried this specific scenario and it always requires me to press enter whenever I do a edit-and-execute-command.
    – Prav
    Commented Jan 16, 2020 at 22:18
  • 1
    The shortcut which which works for me is Escape+V, but it's still going to execute the command. So as for the workaround, I would add echo in the front to execute it by printing the command to the console for review.
    – kenorb
    Commented Jan 20, 2020 at 11:36
  • 2
    can you please make an concrete example? I don't understand "apply certain filtering (using a script) and then return it to prompt".
    – Kent
    Commented Jan 20, 2020 at 20:50
  • 3
    @PraveenPremaratne: zsh is not a terminal, it's shell. Commented Jan 21, 2020 at 12:49
  • 3
    fc (c-x c-e) to enter $EDITOR and :cquit cancels edit and exit without executing. works for EDITOR=vim. Vim returns non zero exit code so shell won't execute the command - read more here vimdoc.sourceforge.net/htmldoc/quickfix.html Commented Jan 16, 2022 at 0:18

8 Answers 8

10
+50

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 love vim

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.)

  1. hit Escape to be in normal mode,
  2. hit v to enter the editor to edit the command,
  3. edit the command as I like,

(This is where you ask the question.)

  1. 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,
  2. 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.
  3. 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

  1. hit EscapeLeaderd
0
5

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.

4

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.

1

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 ....

1

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.

  1. Enable option histverify to prevent execution of modified command
  2. Use chain of modifiers to change last command
  3. 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
1

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)" [email protected]

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
    
0
1

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 '

asciicast

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

-1

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.