Append command to history without removing last command - bash

I use fzf for a lot of things. I will often pipe the output of fzf and run another command with the result. However, I often want to save that command to my bash_history so I can run it again without needing to find the item with fzf again. To do so, I use the history -s command. From the output of help history:
history: history [-c] [-d offset] [n] or history -anrw [filename] or history -ps arg [arg...]
Display or manipulate the history list.
...
-s append the ARGs to the history list as a single entry
However, it appears to also delete the last item in the history (i.e. the command that contained fzf). This is supported by the man page for bash-builtins I found here.
...
history -s arg [arg ...]
...
-s Store the args in the history list as a single entry. The last command in
the history list is removed before the args are added.
Is there a way to prevent the last command from being removed? If possible, I would like to have both the fzf command and the command that was later executed appear in my history.
Additional details
Bash version: 5.0.11(1)-release
OS: macOS Mojave 10.14.6 (18G3020)

Related

How to delete a single command from history in bash

After some searching online I found that the following command should remove a single entry from the bash history:
$ history -d <number>
So let's say I would like to remove this single line from my history because I misspelled git
:
10003 gti add . && git commit -m
When I do the following command:
$ history -d 10003
It still shows up in my history, even when I restart the terminal
So any idea how I can fix this because I use autocomplete and sometimes especially when using git it can get a bit messy.
Command history in Bash is stored both in-memory, typically for the current shell session, and on disk, by default in ~/.bash_history. A default history configuration might append the in-memory session to the file on disk when the session is terminated, but there are a lot of knobs to tweak.
In any case, to permanently delete a command from history, you might have to edit the ~/.bash_history file directly.
A few references to history settings:
HISTFILE – the file where history is persisted (defaults to ~/.bash_history)
HISTFILESIZE – the number of commands stored in the history file (defaults to HISTSIZE)
HISTSIZE – the number of commands stored in the (in-memory) history list (defaults to 500)
cmdhist – shell option to save multi-line commands in a single history entry
histappend – shell option to control if the history list is appended to the history file, or if the history file is overwritten
lithist – shell option to store multi-line commands using linebreaks instead of semicolons
So turns out it's solved by deleting the command from ~/bash_history

How to view the output of history from a non-interactive shell given path to HISTFILE?

In emacs I would like to use shell-command-to-string to essentially retrieve something like history | tail -n5. Issue is that history is a bash built in (in interactive mode only?) so that does not work. As I am merely interested in a read-only snap of the decorated output, is there a way to parse the content of the history file (suppose it is ~/.bash_history) and output it with the line number and date as the history bash command does it?
In other words, is there a way to do something like:
(shell-command-to-string "history --pretty-dump-to-stdout-with-date-time-linenum --filename ~/.bash_history")
? (Or, perhaps, a different workaround?)
Make history command output something
I expect set -o history to fail as it will use the history file $HISTFILE which most likely is unset for non-interactive shells. Assuming the history is stored in ~/.bash_history you could execute ...
HISTFILE=~/.bash_history; set -o history; history | tail -n5
However, this would modify your history by adding history | tail -n5. A better approach is to read the history file without enabling the history:
history -r ~/.bash_history; history | tail -n5
Commands from your current session
Assume you are inside a interactive terminal session and execute ...
$ myCommand
$ emacs # and inside emacs the shell command `history` as shown above
... then you most likely won't see myCommand in the history. This is because bash only writes the history to your history file when closing the session. You can manually update the history file using history -a, thus the following should work:
$ myCommand
$ history -a
$ emacs # and inside emacs the shell command `history` as shown above
Since most people usually would forget the history -a it would be best to automate this step in your .bashrc, either at each prompt (PROMPT_COMMAND="history -a), or only for emacs (emacs() { history -a; command emacs "$#"; }).
Pretty Printing
is there a way to do something like [...] --pretty-dump-to-stdout-with-date-time-linenum
The history format of the history command is controlled by HISTTIMEFORMAT. The format has to be set and exported before calling history inside emacs.

Is it possible to search all bash histories

If you have 10 xterms open, say, is it possible to search all the bash histories combined in some way?
I can do
history|grep something
on each one separately but I would sometimes like to avoid that.
I am going to assume these xterm windows are all for the same userid, and are all on the same host. If they are not all the same user, then you must change their history file locations to use the same history file. If you need to do this, see this thread: https://superuser.com/questions/760830/how-do-i-change-the-bash-history-file-location
But the main problem you will have is that bash only writes its history to .bash_history when the shell is closed. However, you can edit your .bashrc to write the most recent command to .bash_history immeditately, like so:
# When the shell exits, append to the history file instead of overwriting i
shopt -s histappend
# After each command, append to the history file and reread it
export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"
See this link for more info: https://unix.stackexchange.com/questions/1288/preserve-bash-history-in-multiple-terminal-windows
Bash has in-memory history and a history file ($HISTFILE, ~/.bash_history by default). By default, all your bash sessions share one history file which gets by the in-memory history of the last exiting bash shell whenever a bash shell exits.
You can change the behavior from overwriting to appending with:
shopt -s histappend
And you can manually invoke this dumping with
history -a #-a stands for append
What I do is I have history -a as part of my PROMPT_COMMAND so that each new prompt line adds a new record in the global history file.
Then, whenever I need the history from other sessions, I just load the global history file into the in-memory history with:
history -r
-r read the history file and append the contents to the history
The manual of the history command can be read with help history and more details regarding history are in man 1 bash.
Apart from setting shopt -s histappend and adding history -a to your PROMPT_COMMAND, it's also useful to set sizes (HISTFILESIZE, HISTSIZE) to some large values and HISTCONTROL, e.g. to "ignoreboth:erasedups" to eliminate duplicates.
Also, I find Ctrl-R to be a much more comfortable way of searching through history.

fish: command substitution issue with interactive command

I'm trying to set "fzf - Fuzzy finder for your shell" for the fish shell. The problem is that interactive commands don't work when I use it in command substitution. Example:
This command works: (echoes all the files in the current dir, and I can interactively select one by fuzzy-finder)
ls | fzf
But this one doesn't work:
echo (ls | fzf)
It just immediately returns empty string.
It doesn't work for any interactive command, so if you haven't fzf, you can test it with, say, off the top of my head, chsh:
This command works: (asks for password)
chsh
but this one doesn't: (immediately returns empty string)
echo (chsh)
More, when I try to exit fish, it says that "there are stopped jobs", i.e. interactive command starts and immediately stops.
How to make it work?
This is probably just a bug. See https://github.com/fish-shell/fish-shell/issues/1362 (as discussed on the mailing list)

How do I prevent commands from showing up in Bash history?

Sometimes, when I run commands like rm -rf XYZ, I don't want this to be recorded in Bash history, because I might accidentally run the same command again by reverse-i-search. Is there a good way to prevent this from happening?
If you've set the HISTCONTROL environment variable to ignoreboth (which is usually set by default), commands with a leading space character will not be stored in the history (as well as duplicates).
For example:
$ HISTCONTROL=ignoreboth
$ echo test1
$ echo test2
$ history | tail -n2
1015 echo test1
1016 history | tail -n2
Here is what man bash says:
HISTCONTROL
A colon-separated list of values controlling how commands are saved on the history list. If the list of values includes ignorespace, lines which begin with a space character are not saved in the history list. A value of ignoredups causes lines matching the previous history entry to not be saved. A value of ignoreboth is shorthand for ignorespace and ignoredups. A value of erasedups causes all previous lines matching the current line to be removed from the history list before that line is saved. Any value not in the above list is ignored. If HISTCONTROL is unset, or does not include a valid value, all lines read by the shell parser are saved on the history list, subject to the value of HISTIGNORE. The second and subsequent lines of a multi-line compound command are not tested, and are added to the history regardless of the value of HISTCONTROL.
See also:
Why is bash not storing commands that start with spaces? at unix SE
Why does bash have a HISTCONTROL=ignorespace option? at unix SE
In your .bashrc/.bash_profile/wherever you want, put export HISTIGNORE=' *'. Then just begin any command you want to ignore with one space.
$ ls # goes in history
$ ls # does not
Even better use HISTIGNORE. This allows you to specify a set of patterns to be ignored (such as rm). It is better (I think) than just piping all history to /dev/null.
kill -9 $$
I know that is not as best as the previous answers, but this will kill the current Bash shell without saving anything, useful when HISTCONTROL is not set by default, you forgot to set it, or pure and simple you forgot to put a leading space and you just typed in some passwords and don't want them to remain permanently in history.
This is the quick way, and something like erasing the history file is not as good because you need to do it outside a history saving shell (log in as different user and use su/sudo, creating a background job, etc.)
You can do one of two things:
export HISTFILE=/dev/null
Or, begin the command with a space.
Or
unset HISTFILE
(similar to the previous answer only shorter: export HISTFILE=/dev/null)
I added an "Incognito" functionality to my .bashrc for when I want to run some commands without being saved without having to add spaces before each one.
Do note though that the in-memory history of the current terminal session will still be saved, but when I open a new terminal the commands issued in a past terminal's incognito session will never be seen because they were never written to the HISTFILE.
To your .bashrc:
ignoreHistory="false"
DEFAULT_HISTFILE=~/.bash_history
HISTFILE="$DEFAULT_HISTFILE"
# Toggle incognito mode
incognito() {
if [[ "$ignoreHistory" == "true" ]]; then
echo -e "\e[33mExited incognito mode\e[39m"
ignoreHistory="false"
HISTFILE="$DEFAULT_HISTFILE"
else
echo -e "\e[33mEntered incognito mode\e[39m"
ignoreHistory="true"
HISTFILE=/dev/null
fi
}
Nice little utility I think some people may find use in, you can even change the prompt to reflect whether you're in incognito mode or not.
At shell startup, I explicitly cleanup the history from the entries that I don't want to be there. For example, I don't want any rm -rf in the history (it's trauma after removing a directory full of results processed overnight, just with a single Arrow-Up + Enter :)
I put the following snippet in my init file (works with .zshrc, should also work with .bashrc)
# ...
HISTFILE=~/.zshhistory
# ...
# remove dangerous entries from the shell history
temp_histfile="/tmp/$$.temp_histfile"
grep -v -P '^rm .*-rf' $HISTFILE > $temp_histfile
mv $temp_histfile $HISTFILE

Resources