Pipe a vim command after a shell command - bash

I'm trying to make a key mapping in vim that (a) saves current file (b) performs a git action, using shell (c) quits current vim editor.
I've tried the following methods but still can't figure it out.
Method 1 - in Vim command line
:w | !git commit -am "auto" | q
Method 2 - in .vimrc
map :W :w \| !git commit -am "auto"
map :L :Wq
Problem
The problem is that the pipe | can only be used to append shell commands. How do I do 'a Vim command' + 'a shell command' + 'a Vim command'? How to pipe a vim command after a shell command?

You need to execute those three commands separately. This is what <CR> is for:
nnoremap <key> :w<CR>:!git commit -am "auto"<CR>:qa<CR>

Have you tried vim-fugitive?
You can git commit -am using that plugin and then add your git command.
So you would write something like:
Gcommit -am "auto"
I tested it briefly. I've put this into my .vimrc:
function! CommitAndQuit()
execute "Gcommit -am 'auto'"
execute "q"
endfunction
Then type :call CommitAndQuit() and it will do what you want.

This feels related as I ended up here!
So if you want to bind this in a command you won't be able to use <cr> and pipe also won't work.
In that case the right thing to do is use a function and just call the function from that command.
In this example I had a shell script to grep some log files for errors and output the result into .errors, which then gets opened in the quickfix. The redraw is because silent messes your screen up with external commands!
function! ReadErrors()
silent !read-errors
redraw!
cfile .errors
endfunction
command! ReadErrors call ReadErrors()

There are 3 scenarios I see here:
Adding a vim command
If you want to add a new vim command, so that typing :W would write the buffer, do git commit and quit vim, you'll need to follow JonnyRaa's answer. Namely, define a function, and then define a command that executes that function:
function! WriteCommitAndQuit()
w
silent !git commit -am "auto"
q
endfunction
command! W call WriteCommitAndQuit()
Notes:
These lines can be put into ~/.vimrc, or executed directly after hitting : to go into command line mode.
I used silent to avoid getting the "Press ENTER or type command to continue" prompt.
You might want to use write and quit instead of w and q, for nicer-looking code. (Conversely, silent can be shortened to sil if you're in a hurry...)
Adding a keyboard mapping
If you want to just be able to hit a single key to run these commands, you'll need to add a mapping, and the trick for putting a shell command in the middle is to use <CR> to simulate hitting ENTER:
map <F10> :w<CR>:silent !git commit -am "auto"<CR>:q<CR>
Notes:
Once again, this can go in ~/.vimrc or execute by hitting : and typing it directly.
Instead of silent, you can just add an extra <CR> after the shell command.
Executing directly
Sometimes there's a sequence of commands you want to run repeatedly, but they're not worth bothering to persist into vimrc, since they are ad-hoc and won't be useful in the future. In this case, I just execute the line and then rely on the up arrow to bring the line again from history and execute it.
In this case, the trick is to add an escaped LF character in the middle of the command, using Ctrl-V, Ctrl-J:
:w | silent !git commit -am "auto" ^# q
Notes:
The ^# in the command is what gets shown when I hit Ctrl-V, Ctrl-J. It won't work if you hit Shift-6, Shift-2. However, it does seem to work if I hit Ctrl-Shift-2.
You can also replace the first pipe character with LF, but you don't have to.
In case you're curious, Ctrl-J is LF because LF is ASCII 10, and J is the 10th letter of the ABC. Similarly, Ctrl-I is Tab, Ctrl-M is CR, and Ctrl-] is Escape, since Escape is ASCII 27, and if you look at an ASCII table, you'll realize that ] is the 27th letter of the ABC.

i use this command to copy current file path to system clipboard.
map <silent> <leader>vv :echo expand('%:p') \| !syscopy.sh <cr>

simple why for me is:
Work
:echo expand('ls') | echomsg 'test'
Not work
:!ls | echomsg 'test'

You can't:
A '|' in {cmd} is passed to the shell, you cannot use it to append a Vim command. See |:bar|.

Related

A shortcut to fire up vim on the text typed on the command line

I typed a very long command on the command line – only to discover that Bash doesn't like embedded single quotes. It is a bare tty and my only way to save this command for running from within a file is to open vim and start typing this very long command again...
Isn't there a shortcut to fire up vim on the text typed on the command line – directly from the command line?
Sure there is. The following command will edit the current command-line in $VISUAL:
C-x C-e
I found the answer for my case here:
First off a warning from #marlar (I made myself a function to protect myself
from this, see below):
Be very careful with this feature. If you cancel the edit, the original
command line will be immediately executed. So if you are editing rm -rf
<forward slash> and invoke the editor and realize you are into something
dangerous and thus cancel the edit, your root file system will be deleted
without further questions asked.
From the OP #Kartik:
The bash man page says:
edit-and-execute-command (C-xC-e)
Invoke an editor on the current command line, and execute the
result as shell commands. Bash attempts to invoke $VISUAL,
$EDITOR, and emacs as the editor, in that order.
And from #Mark E. Haase
If you use Bash's vi mode, the short cut is Esc, V...
I actually also find that doing just v is sufficient to launch vim with the bash
command sitting there.
From #karlicoss:
if your editor is vim, you can use :cq, it makes it exit with non-zero code,
and prevents the command from execution
Because I'm paranoid about the rm -rf command (I once deleted 2Tb of brain
data with that - 10 days into switching to linux... lucky the sysadmin had a
backup... actual quote: "I've never seen anything that stupid in my 20 years
doing this job"... but I digress) I put the following in my vimrc to
immediately comment out the command if I accidentally bring up vim and
reflexively exit:
function! CheckBashEdit()
" if the file matches this highly specific reg exp, comment the line
"(e.g. a file that looks like: /tmp/bash-fc.xxxxxx)
if match(#%, "\/tmp\/bash-fc\.......") != -1
" comment out the command
silent! execute ":%normal! I# "
write
endif
endfunction
" if we ended up in vim by pressing <ESC-v>, put a # at the beggining of
" the line to prevent accidental execution (since bash will execute no
" matter what! Imagine rm -rf <forward slash> was there...)
autocmd BufReadPost * :call CheckBashEdit()

How do I edit current shell command without executing it?

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

How to make the specified command run in vim?

I know how to make shell command run in vim,in command mode,type
:!shell_command
Now, I have a command in line 16 of a file open in Vim.
cp /home/debian/Downloads/rar/rar /usr/local/bin
How can I run this command in vim, without typing all of it?
When I hit :! to open up the ex command line and then use <C-r>", I get this:
ls /tmp^m
I have to erase ^m with the backspace key, and press the enter key, is that right?
can i not to let ^M to be shown ?
To run the command on the line your cursor is on you can yank the line into you clipboard. Type :! to open up the ex command line and then use <C-r>" to paste the command onto the command line.
So with the cursor on the line type and hit enter.
yy:!<C-r>"
Take a look at :h <c-r>.
CTRL-R {0-9a-z"%#:-=.} *c_CTRL-R* *c_<C-R>*
Insert the contents of a numbered or named register. Between
typing CTRL-R and the second character '"' will be displayed
to indicate that you are expected to enter the name of a
register.
The text is inserted as if you typed it, but mappings and
abbreviations are not used. Command-line completion through
'wildchar' is not triggered though. And characters that end
the command line are inserted literally (<Esc>, <CR>, <NL>,
<C-C>). A <BS> or CTRL-W could still end the command line
though, and remaining characters will then be interpreted in
another mode, which might not be what you intended.
Special registers
Simple answer:
:exec '!'.getline('.')

Opening Vim and using some command in normal mode from the Terminal?

I want to be able to open Vim and automatically make some commands in normal mode (in this case, open two buffers put a mark in one line on one line, fold another line, and go to the bottom of the file). The closest thing I found would be using the -c or + command line switch, however these execute commands in Ex mode.
For example, I would like to be able to write something like this:
vim -some ":e a<CR>:e b<CR>23Gma55GzfG"
To do all the commands.
Why don't you like normal command? It is not able to execute ex mode commands, but is able to use all other modes:
vim -c 'e a' -c 'e b' -c 'normal! 23Gma55GzfG'
Bang at the end of command is required if you want to be sure that it will not use any mappings.
Define this command in your _vimrc file (you can name it something other than Onstart, but it must start with a capital letter)
:command -nargs=1 Onstart <args>
From the command line you should be able to call this:
gvim c:\file.txt -c "Onstart e a<CR>:e b<CR>23Gma55GzfG"
correction
Nope, I was mistaken. You'd just have to pipe the commands:
vim -c "e a | e b | normal!23Gma55GzfG"

Going backwards in a bash prompt

I'd like to have a blank line after my bash prompt and before the output on my Mac. It should look like this would:
echo; ls
Can I add a newline to my bash prompt and then go back up one line to wait for user input? Is there something obvious I'm missing?
I know this is old but for someone like me who came across this while googling for it. This is how you do this...
It's actually pretty simple!
Check out this link --> Cursor Movement
Basically to move up N number of lines:
echo -e "\033[<N>A HELLO WORLD\n"
Just change the "< N >" to however many lines you want to go back...
For instance, to move up 5 lines it would be "/033[5A"
To my knowledge this is not possible unless you delve into more low-level stuff like full-screen emulators like curses.
This is a bit of a stab in the dark, but you may be able to use VT102 terminal codes to control the cursor without having to use Curses. The relevant VT102 commands that you'd be interested in all consist of sending ESC, then [, then the specific command parameters.
For instance, to move the cursor up one line, one needs to output:
ESC [ 1 A
0x1B 0x5B 0x31 0x41
Be warned that the VT102 documentation generally uses octal, so keep an ascii table handy if you're using hex.
All of this advice is given without having tested it -- I don't know if VT102 commands can be embedded into your bash prompt, but I thought it might be worth a shot.
Edit: Yeah -- looks like a lot of people use VT102 formatting codes in their bash prompts. To translate my above example into something Bash would recognize, putting:
\e[1A
into your prompt should move the cursor up one line.
This is very possible. If your bash has C-v set as the readline quoted-insert command, you can simply add the following to your ~/.inputrc:
RETURN: "\C-e\C-v\n\C-v\n\n"
This wil make bash (readline, actually) insert two verbatim newlines before a regular interpreted newline. By default, only one is inserted, which is what causes output to start on the line after the prompt.
You can test if C-v is set to quoted-insert by typing it in bash (that's Ctrl+V) followed by e.g. an up arrow. This should print ^[[A or something similar. If it doesn't, you can bind it in ~/.inputrc too:
C-v: quoted-insert
RETURN: "\C-e\C-v\n\C-v\n\n"
~/.inputrc can be created if it doesn't exist. The changes will not take effect in running bashes unless you issue a readline re-read-init-file command (by default on C-x C-r). Be careful though. If you do something wrong, enter will no longer issue commands, and fixing your mistake could prove to be difficult. If you should do something wrong, C-o will by default also accept the line.
Adding a newline followed by moving the cursor back to the regular prompt (like you described) is possible, but will not have the effect you intend. The newline you inserted would simply be overwritten by the application output, since you moved the cursor back in front of it.
This works:
trap echo DEBUG
It doesn't add an extra newline if you hit return at an empty prompt.
The command above will cause a newline to be output for every member of a pipeline or multi-command line such as:
$ echo foo; echo bar
\n
foo
\n
bar
To prevent that so that only one extra newline is output before all command output:
PROMPT_COMMAND='_nl=true'; trap -- '$_nl && [[ $BASH_COMMAND != $PROMPT_COMMAND ]] && echo; _nl=false' DEBUG
The DEBUG trap is performed before each command so before the first command it checks to see if the flag is true and, if so, outputs a newline. Then it sets the flag to false so each command afterwards on the line doesn't trigger an extra newline.
The contents of $PROMPT_COMMAND are executed before the prompt is output so the flag is set to true - ready for the next cycle.
Because pressing enter on an empty command line still triggers the execution of the contents of $PROMPT_COMMAND the test in the trap also checks for those contents as the current command and doesn't perform the echo if they match.
I believe (but haven't tried) if you put '\n\b' in the prompt string it would do that.
In general, if you want to find out the codes to do anything a terminal can do, read the terminfo man page.
In this case, the cursor up one line code can be determined by:
tput cuu1
If you redirect the tput output to a file, you can see what control characters are used.
Bash also supports the PROMPT_COMMAND variable, allowing you to run arbitrary commands before each prompt is issued.

Resources