When I try to read bash history into vim, I get nothing.
:r !history
If I just execute the command, i.e.
:!history
instead of history I get a snapshot of my terminal as it looked before I started vim.
How can I read the output of "history" into vim? Reading the contents of .bash_history won't do as I save history with timestamps:
HISTTIMEFORMAT='%Y.%m.%d %R '
From a shell prompt:
history | vim -
The problem is that that history is only known by the bash shell that started vim. When you do :!history from within vim, you're starting a new bash shell that has its own history, which is empty, which is why you just see the screen as it looked when you started vim: it's outputting all the lines in its history, which is a grand total of zero. This actually is an oversimplification, but anyway you can't get a history of the commands that you typed just before starting vim this way.
If you want to get those lines of history without exiting vim, you can suspend vim by pressing CTRL-Z and then write the history to a file using history >history.tmp. Then type fg 1 to resume vim: this will tell bash to transfer focus back to its "job number 1", which will normally be vim. The job number is displayed after you hit CTRL-Z:
[1]+ Stopped vim
so if there's a number other than 1 in the brackets, then you should do fg for that number instead. Then (hopefully you know this) when you're back in vim just :tabedit history.tmp, for example, to open the saved history in a new tab.
You'll have timestamps in this output too, but since you're in vim you can easily filter them out with a :substitute command. Alternatively you can cut them out using HISTTIMESTAMP='' history rather than just history when writing to the file; this will still output the index of each entry. I guess you can filter that out on its way into the file too, by piping it through sed or cut or one of their crew. But it's really easy to do this from within vim (assuming you know the basics of regular expressions; if not, start with :help :substitute or maybe look for a regex tutorial).
Note that if you read in the lines from ~/.bash_history, you're only getting the history from bash shells which have completed, ie you typed exit and the terminal window closed. So any commands you typed just before starting vim won't be there. You can change the way this works but then you end up with commands from different sessions all jumbled up together in the history.
:r!echo "history" | bash -i 2>/dev/null | sed -e 's/\x1b\[.//g'
Explanation:
The history command only works on interactive shell.
The first part:
echo "history" | bash -i 2>/dev/null
Forces interactive shell (and removes lines which aren't output of history).
The second part:
sed -e 's/\x1b\[.//g'
Removes escape character the shell might output (happened on my system).
You may pre-write the history of current session:
history -w
Then in editor you can get last, say, 20 commands of history:
:r ! tail -20 ~/.bash_history
Try:
r !set -o history; HISTFILE=~/.bash_history; history -r; history 10
This will include the timestamps and will not include history that hasn't been saved to the history file.
Related
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 created a function clip that copies the output of the last command to clipboard by rerunning it first.
#copy previous output to clipboard
function clip(){
echo $(!!) | pbcopy
}
When I run the individual line contained in the function in my terminal it works perfectly. However, if I try to save it as a function in my .zshrc and execute it by calling clip, I get the following error:
zsh: command not found: !!
I can't get the automatic expansion to work properly, any help would be appreciated.
clip () {
fc -ILe- | pbcopy
}
Explanation:
History expansion with !! only works if it is typed into an interactive commandline. It will not work from within a script as !! is not handled in a special way there (leading to the "command not found" error).
Instead you can use the fc command to retrieve elements from the history.
Running fc without any parameters will retrieve the last history event and open an editor with the event in it for editing. Closing the editor will then run the edited command (not saving an edit will lead to the original command to be executed).
The parameters from the above example will modify the behavior as follows:
-I: Only retrieve internal history events, that is, exclude events loaded from $HISTFILE.
-L: Only retrieve local history events, excluding elements shared form other sessions via SHARE_HISTORY. (This may not be necessary in combination with -I, but I did not test this)
-e ename: use ename as editor instead of the default editor. if ename is set to -, then no editor is opened.
-I and -L are not strictly necessary, but they prevent you from inadvertently running which ever command was typed in last in some previous shell session. This could very well have been rm -r * or poweroff.
So in combination fc -ILe- | pbcopy will retrieve the last command entered into the current shell session and pipe its output to pbcopy.
BTW: You can just use cmd1 | cmd2 instead of echo $(cmd1) | cmd2 in order to be able to pipe the output of cmd1 to cmd2.
I am writing a Bash script that runs a command-line program (Gromacs), saves the results, modifies the input files, and then loops through the process again.
I am trying to use Vim to modify the input text files, but I have not been able to find a way to execute internal Vim commands like :1234, w, x, dd, etc. from the .sh file after opening my input files in Vim ("vim conf.gro").
Is there a practical way to execute Vim commands from the shell script?
I think vim -w/W and vim -s is what you are looking for.
The "Vim operations/key sequence" you could also record with vim -w test.keys input.file. You could write the test.keys too. For example, save this in the file:
ggwxjddZZ
This will do:
Move to the first line,
move to the next word,
delete one character,
move to the next line,
delete the line, and
save and quit.
With this test.keys file, you could do:
vim -s test.keys myInput.file
Your "myInput.file" would be processed by the above operations, and saved. You could have that line in your shell script.
VimGolf is using the same way to save the user's solution.
You can script Vim via the -c flag.
vim -c "set ff=dos" -c wq mine.mak
However that only gets you so far.
From the commands you gave it looks like you are trying to run some normal commands. You will need to use :normal. e.g. :norm dd
Writing all this on the command line is asking for trouble. I suggest you make a Vim file (e.g. commands.vim) and then :source via -S.
You probably want to get good and conformable Vim's ex commands. Take a look at :h ex-cmd-index
So you will end up with something like this. With all your Vim commands inside of commands.vim.
vim -S commands.vim mine.mak
You may also want to look into using sed and/or awk for text processing.
Alternatives
Unless you really need special Vim capabilities, you're probably better off using non-interactive tools like sed, awk, or Perl / Python / Ruby / your favorite scripting language here.
That said, you can use Vim non-interactively:
Silent Batch Mode
For very simple text processing (i.e. using Vim like an enhanced 'sed' or 'awk', maybe just benefitting from the enhanced regular expressions in a :substitute command), use Ex-mode.
REM Windows
call vim -N -u NONE -n -es -S "commands.ex" "filespec"
Note: silent batch mode (:help -s-ex) messes up the Windows console, so you may have to do a cls to clean up after the Vim run.
# Unix
vim -T dumb --noplugin -n -es -S "commands.ex" "filespec"
Attention: Vim will hang waiting for input if the "commands.ex" file doesn't exist; better check beforehand for its existence! Alternatively, Vim can read the commands from stdin. You can also fill a new buffer with text read from stdin, and read commands from stderr if you use the - argument.
Full Automation
For more advanced processing involving multiple windows, and real automation of Vim (where you might interact with the user or leave Vim running to let the user take over), use:
vim -N -u NONE -n -c "set nomore" -S "commands.vim" "filespec"
Here's a summary of the used arguments:
-T dumb Avoids errors in case the terminal detection goes wrong.
-N -u NONE Do not load vimrc and plugins, alternatively:
--noplugin Do not load plugins.
-n No swapfile.
-es Ex mode + silent batch mode -s-ex
Attention: Must be given in that order!
-S ... Source script.
-c 'set nomore' Suppress the more-prompt when the screen is filled
with messages or output to avoid blocking.
Because it seems often the history isn't saved when shutting down and not closing gnome terminal, I'd like to append to bash history before showing the prompt if the line isn't a duplicate. Although I have export HISTCONTROL=ignoreboth in .bashrc, which is supposed to imply ignoredups, it doesn't seem to work, as I still get duplicates in bash_history. So how to do this?
To offer further indications, although not solutions:
I think $PROMPT_COMMAND needs to have "$(history 1)" (if_not_duplicate) >> ~/.bash_history
But $(history 1) needs to be changed so the actual command gets output, not the history entry, which, for instance, has line numbers.
And this would remove non-adjacent duplicates, if they are already inserted.
perl -nei '$H{$_}++ or print' ~/.bash_history
You need erasedups too if you want to remove duplicates from the history.
export HISTCONTROL=ignoreboth:erasedups
does the trick for me.
I work in a place that has gazillions of tools which require tons of options, so I rely on my shell's history significantly. I even back it up every now and then just to make sure I don't lose useful, lengthy commands.
I just typed one of these commands and I want to make sure it's flushed to the history file, but I have a long-running job in the background and I can't type exec zsh. Is there something else I can do in this situation?
(Sure, I could copy and paste it into a file, but it would be more logical for there to exist a flush-history command.)
To write the shell history to the history file, do
fc -W
fc has some useful flags, see them all in man zshbuiltins.
You can also fully automate reading and writing the history file after each command (thus sharing your history file automatically with each running zsh) by saying setopt -o sharehistory. Read more history-related options in man zshoptions.
I also just found:
setopt INC_APPEND_HISTORY
From man zshoptions:
INC_APPEND_HISTORY
This options works like APPEND_HISTORY except that new history
lines are added to the $HISTFILE incrementally (as soon as they
are entered), rather than waiting until the shell exits. The
file will still be periodically re-written to trim it when the
number of lines grows 20% beyond the value specified by $SAVE-
HIST (see also the HIST_SAVE_BY_COPY option).
And use
fc -R
to read in the history (after writing it) in an existing zsh shell.
appen below line to ~/.zshrc, it will save 1000 entry we increase by changing value of HISTSIZE and SAVEHIST
HISTSIZE=1000
if (( ! EUID )); then
HISTFILE=~/.zsh_history_root
else
HISTFILE=~/.zsh_history
fi
SAVEHIST=1000