Execute current line in Bash from Vim - bash

This question is similar to Vim: execute current file?, but instead of executing the current file I want to execute only the current line.
Is this possible?
Ideally, I am looking for solutions which can have side effects in the outer shell.
For example, suppose I have the following line:
alias foo=bar
After running the command in Vim, if I start a shell with :sh, the alias foo is available, but if I quit vim using :q, then the alias is no longer available.

Sure thing, you can 'write' any content of the current file into the standard input of another program:
:.w !bash
Here . (the part before w) refers to the range of lines you are writing, and . is only the current line. Then you use !bash to write those lines to Bash.

I do this sort of thing all the time with:
:exec '!'.getline('.')
You can even create a mapping in your .vimrc:
nmap <F6> :exec '!'.getline('.')

Move the cursor to that line, and in normal mode press:
!!bash<cr>

This could be a comment if I can comment.
Concerning redirect/pipe lines of current buffer in Vim to external command, inspired by Daan Bakker's great answer, I wrote I answer here
(Save and run at the same time in Vim), on an question concerning
running a Python script (current buffer).
Beside running the whole buffer, how to run a range of line via an external command is demonstrated.
To save time, I just copy it below.
#####################
In Vim, you could simply redirect any range of your current buffer to an external command (be it 'bash', 'python', or you own Python script).
# Redirect the whole buffer to 'python'
:%w !python
Suppose your current buffer contains the two lines as below,
import numpy as np
print np.arange(12).reshape(3,4)
then :%w !python will run it, be it saved or not. And print something like below on your terminal,
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
Of course, you could make something persistent, for example, some keymaps.
nnoremap <F8> :.w !python<CR>
vnoremap <F8> :w !python<CR>
The first one runs the current line. The second one runs the visual selection, via the Python interpreter.
#!! Be careful, in Vim ':w!python' and ':.w !python' are very different, the
first writes (creates or overwrites) a file named 'python' with thew contents of
the current buffer. The second redirects the selected cmdline range (here dot .,
which mean current line) to an external command (here 'python').
For cmdline range, see
:h cmdline-ranges
Not the below one, which concerning normal command, not cmdline one.
:h command-range
This was inspired by Execute current line in Bash from Vim.

Add this mapping to your .vimrc file,
nmap <leader>E yyp:.!csh<CR>
Explanation:
yy
Yank current line
p
Paste yanked line below (and the cursor goes to this next row)
:.!csh<CR>
Execute (using csh) this newly pasted line in place. The output of this line replaces this current line, thus before executing the line was yanked and pasted below.

Consider the following command run on terminal,
seq 0 10 | xargs printf "%02x "
Expected output is,
00 01 02 03 04 05 06 07 08 09 0a
Consider you have this above command written in a file. To execute this command get this respective output back in that file, you can add this mapping in yours .vimrc,
nmap <leader>E :exec 'r!'.getline('.')<CR>
Note that, to execute above mentioned line, you need to write it with adding escape char for '%' as follows,
seq 0 10 | xargs printf "\%02x "
Go this line in your file and press <leader>E. In my case, <leader> is mapped to , hence I will press ,E

If I have a command on a line of text within vi/Vim like this"
ls -la
Position the cursor anywhere on the line and do
":.!!" and press return.
That is: colon dot bang bang return
That will execute the text in that line in the current shell from within vi/Vim and have the output inserted within the text file.
I'm thinking that is what you were asking? It's like magic.

Related

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

Jump to string in current line while using Bash in VI mode

While navigating in BASH using VI mode, I can jump back to a specific character (e.g. '-') of the current command line via the following command:
F-
How can I jump back to a specific string (e.g. '--path') in the current command line of BASH? I know navigating in VI but I did not understand how to perform regex search in current command line of BASH.
According to here, what you want doesn't seem possible. The ?word and /word bindings search in command history rather than in the current command line.
But in vi mode, you can press ESC v to open the current command line in an editor. Then you can edit & save the command and it will be executed (source).
Of course, as pointed out in nur-sh's answer, you can simply keep pressing B to get to the word.
You could use the find command which searches backwards from where you are
?word
or you could keep pressing B to get to the word
(this command goes back one Word at a time).

vim script leaves characters in stdin

I'm trying to use vim with -s option to run a script that replaces some lines in a file like this (text.txt):
test1
ab
ac
ae
test2
sd
Script file is like this (script):
:silent %s/test1\zs\_.\+\zetest2/\=substitute(submatch(0), '\n\(\w\)', '\n#\1', 'g')/g
:wq
It comments out lines between test1 and test2. Which is what I want. What I don't want though is output before and after prompt. I run it and get:
user#hostname: ~/vimtest$ vim -s script text.txt
^[[?1;2cuser#hostname: ~/vimtest$ 1;2c
So this ^[[?1;2c is bad news already but 1;2c is in the input as if I already typed it. If I hit enter it gives me a bash error. So I have to remove these symbols each time the script is used. Any ideas?
It seems like vim (or some vim startup script) is trying to figure out what type of terminal you are using. The ^[[?1;2c, with the last few characters left in the input buffer, is almost certainly part of your terminal emulator's response to a DA (Device Attributes) query. You can see this yourself by typing in bash:
printf '\033[c'
or, to see the complete return, pause a bit:
printf '\033[c'; sleep 0.1; echo
The response \033[?1;2c means "I'm a VT100 with Advanced Video Option.", which is what xterm and many other console programs respond. (The Linux console itself responds \033[?6c, which means "I'm a VT102.")
The reason that only 1;2c is left in the console input buffer, by the way, is that the initial escape code \033[? was ignored when it was read. The readline library will ignore it without echoing it, whereas normal console input will echo it and then ignore it; that's why the two shell commands above differ.
I can't reproduce this problem with my vim installation, so I don't really even know where to start looking. But you might try to see if disabling all startup files helps:
vim -u NONE -s script text.txt
If that helps, start disabling installed extensions one by one until you find the one which is causing the problem.
:%s/test1\zs\_.\+\ze\ntest2/\=substitute(submatch(0), '\n', '\n#', 'g')/g
:wq
this is tested here, it changed the input file in required way.
Some changes done based on your command:
add \n after \ze
in substitute() function we can just handle the \n, we don't need to capture the word after the \n
I noticed that you tagged the question with bash, so I thought a shell-solution should be accepted too.
awk '/test1/{p=1;print;next}/test2/{p=0;print;next}{$0=(p?"#":"")$0}7' file
this awk oneliner should do that for you. vim is very powerful editor, I love vim. But if you want to do some automatic transformation, I prefer a script or a proper text processing tool. On a linux box you can always find one. It is easier to test and debug.
Test with your input:
kent$ cat f
test1
ab
ac
ae
test2
sd
kent$ awk '/test1/{p=1;print;next}/test2/{p=0;print;next}{$0=(p?"#":"")$0}7' f
test1
#ab
#ac
#ae
test2
sd
If you want to save the text back to your file, you can :
awk '...' file > tmp.file && mv tmp.file file

How can I avoid manual scrolling through long output of a Vim script to automate execution of the script?

I want to automate the install of Conque GDB from http://www.vim.org/scripts/script.php?script_id=4582.
So I wrote these two commands.
wget "http://www.vim.org/scripts/download_script.php?src_id=22163" -O conque_gdb.vmb
vim +"so % | q" conque_gdb.vmb
The first command downloads conbque_gdb.vmb. The second command executes the two commands the author recommends in the above link, i.e. execute :so % and :q.
However, there is one problem. The output due to executing so % is huge and it causes the Vim window to display a huge output with -- More -- in the end that I must scroll through by pressing Enter, before Vim can execute the q command and quit.
"conque_gdb.vmb" 8489L, 292519C
Vimball Archive
extracted <autoload/conque_gdb.vim>: 561 lines
wrote /root/.vim/autoload/conque_gdb.vim
extracted <autoload/conque_term.vim>: 1674 lines
wrote /root/.vim/autoload/conque_term.vim
extracted <autoload/conque_gdb/conque_gdb.py>: 294 lines
wrote /root/.vim/autoload/conque_gdb/conque_gdb.py
extracted <autoload/conque_gdb/conque_gdb_gdb.py>: 17 lines
wrote /root/.vim/autoload/conque_gdb/conque_gdb_gdb.py
extracted <autoload/conque_gdb/conque_sole_gdb.py>: 82 lines
wrote /root/.vim/autoload/conque_gdb/conque_sole_gdb.py
extracted <autoload/conque_gdb/gdbinit_confirm.gdb>: 24 lines
wrote /root/.vim/autoload/conque_gdb/gdbinit_confirm.gdb
extracted <autoload/conque_gdb/gdbinit_no_confirm.gdb>: 20 lines
wrote /root/.vim/autoload/conque_gdb/gdbinit_no_confirm.gdb
extracted <autoload/conque_gdb/conque_gdb.gdb>: 12 lines
wrote /root/.vim/autoload/conque_gdb/conque_gdb.gdb
extracted <autoload/conque_term/conque.py>: 1176 lines
wrote /root/.vim/autoload/conque_term/conque.py
extracted <autoload/conque_term/conque_globals.py>: 317 lines
wrote /root/.vim/autoload/conque_term/conque_globals.py
extracted <autoload/conque_term/conque_screen.py>: 236 lines
-- More --
Can I somehow avoid this scrolling in between and install Conque GDB in a fully automated fashion?
Use the silent command to suppress output.
vim +"silent so % | q" conque_gdb.vmb
Read :h silent

BASH command prompt: add characters behind command

When editing PS1, one using escape sequences can manipulate the form of the prompt in almost anyway. Howver what holds true every time is that stdout starts right after the command input. For for simplicity's sake, here's an example of a simple prompt that adds some elements below the command entry:
PS1='aaaaaaaa\n\[\033[1B\]bbbbbbbb\n\[\033[2A\]\u#\h:\w\$ '
that looks more or less like this:
aaaaaaaa
user#hostname:~$ █
bbbbbbbb
Notice the position of the cursor once the drawing of the prompt is finished. The \033[ used in the PS1 variable manipulate the cursor (moving it up and down) to be able to draw the 'b' separator below the prompt and return to position.
If however a command is run, stdout as expected overwrites whatever is below the command:
aaaaaaaa
user#hostname:~$ echo 'hello '
hello bb
aaaaaaaa
user#hostname:~$ █
bbbbbbbb
The question is whether there is a way to manipulate the behavior of the prompt from the point of the command's last character in the following fachion:
<editable prompt><command><editable area after command?>
If for example I could instruct bash to print a newline or any escape sequence after every command that would solve the problem portrayed by the example.
This trick might work. I got the idea from https://superuser.com/questions/175799/does-bash-have-a-hook-that-is-run-before-executing-a-command
but I simplified as much as I could.
trap 'echo' DEBUG
The theory is simply to use bash's DEBUG trap to write a blank line before executing anything.

Resources