Follow me on Twitter for my latest adventures!
This is the fourth part of the Bash One-Liners Explained article series. In this part I'll teach you how to work with bash history. I'll use only the best bash practices, various bash idioms and tricks. I want to illustrate how to get various tasks done with just bash built-in commands and bash programming language constructs.
I'll break this part into several sub-parts as it's very tiring to write long articles, and I'd rather publish many short articles and make quick progress.
See the first part of the series for introduction. After I'm done with the series I'll release an ebook (similar to my ebooks on awk, sed, and perl), and also bash1line.txt (similar to my perl1line.txt).
Also see my other articles about working fast in bash from 2007 and 2008:
- Working Productively in Bash's Emacs Command Line Editing Mode (comes with a cheat sheet)
- Working Productively in Bash's Vi Command Line Editing Mode (comes with a cheat sheet)
Parts of this post are based on my earlier post Definitive Guide to Bash Command Line History. Check it out, too!
Let's start.
Part IV: Working with history
1. Erase all shell history
$ rm ~/.bash_history
Bash keeps the shell history in a hidden file called .bash_history. This file is located in your home directory. To get rid of the history, just delete it.
Note that if you logout after erasing the shell history, this last rm ~/.bash_history command will be logged. If you want to hide that you erased shell history, see the next one-liner.
2. Stop logging history for this session
$ unset HISTFILE
The HISTFILE special bash variable points to the file where the shell history should be saved. If you unset it, bash won't save the history.
Alternatively you can point it to /dev/null,
$ HISTFILE=/dev/null
3. Don't log the current command to history
Just start the command with an extra space:
$ command
If the command starts with an extra space, it's not logged to history.
Note that this only works if the HISTIGNORE variable is properly configured. This variable contains : separated values of command prefixes that shouldn't be logged.
For example to ignore spaces set it to this:
HISTIGNORE="[ ]*"
My HISTIGNORE looks like this:
HISTIGNORE="&:[ ]*"
The ampersand has a special meaning - don't log repeated commands.
4. Change the file where bash logs command history
$ HISTFILE=~/docs/shell_history.txt
Here we simply change the HISTFILE special bash variable and point it to ~/docs/shell_history.txt. From now on bash will save the command history in that file.
5. Add timestamps to history log
$ HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S"
If you set the HISTTIMEFORMAT special bash variable to a valid date format (see man 3 strftime) then bash will log the timestamps to the history log. It will also display them when you call the history command (see the next one-liner).
6. Show the history
$ history
The history command displays the history list with line numbers. If HISTTIMEFORMAT is set, it also displays the timestamps.
7. Show the last 50 commands from the history
$ history 50
If you specify a numeric argument, such as 50, to history, it prints the last 50 commands from the history.
7. Show the top 10 most used commands from the bash history
$ history |
sed 's/^ \+//;s/ / /' |
cut -d' ' -f2- |
awk '{ count[$0]++ } END { for (i in count) print count[i], i }' |
sort -rn |
head -10
This one-liner combines bash with sed, cut, awk, sort and head. The perfect combination. Let's walk through this to understand what happens. Let's say the output of history is:
$ history
1 rm .bash_history
2 dmesg
3 su -
4 man cryptsetup
5 dmesg
First we use the sed command to remove the leading spaces and convert the double space after the history command number to a single space:
$ history | sed 's/^ \+//;s/ / /' 1 rm .bash_history 2 dmesg 3 su - 4 man cryptsetup 5 dmesg
Next we use cut to remove the first column (the history numbers):
$ history |
sed 's/^ \+//;s/ / /' |
cut -d' ' -f2-
rm .bash_history
dmesg
su -
man cryptsetup
dmesg
Next we use awk to record how many times each command has been seen:
$ history |
sed 's/^ \+//;s/ / /' |
cut -d' ' -f2- |
awk '{ count[$0]++ } END { for (i in count) print count[i], i }'
1 rm .bash_history
2 dmesg
1 su -
1 man cryptsetup
Then we sort the output numerically and reverse it:
$ history |
sed 's/^ \+//;s/ / /' |
cut -d' ' -f2- |
awk '{ count[$0]++ } END { for (i in count) print count[i], i }' |
sort -rn
2 dmesg
1 rm .bash_history
1 su -
1 man cryptsetup
Finally we take the first 10 lines that correspond to 10 most frequently used commands:
$ history |
sed 's/^ \+//;s/ / /' |
cut -d' ' -f2- |
awk '{ count[$0]++ } END { for (i in count) print count[i], i }' |
sort -rn |
head -10
Here is what my 10 most frequently used commands look like:
2172 ls 1610 gs 252 cd .. 215 gp 213 ls -las 197 cd projects 155 gpu 151 cd 119 gl 119 cd tests/
Here I've gs that's an alias for git status, gp is git push, gpu is git pull and gl is git log.
8. Execute the previous command quickly
$ !!
That's right. Type two bangs. The first bang starts history substitution, and the second one refers to the last command. Here is an example:
$ echo foo foo $ !! foo
Here the echo foo command was repeated.
It's especially useful if you wanted to execute a command through sudo but forgot. Then all you've to do is run:
$ rm /var/log/something rm: cannot remove `/var/log/something': Permission denied $ $ sudo !! # executes `sudo rm /var/log/something`
9. Execute the most recent command starting with the given string
$ !foo
The first bang starts history substitution, and the second one refers to the most recent command starting with foo.
For example,
$ echo foo
foo
$ ls /
/bin /boot /home /dev /proc /root /tmp
$ awk -F: '{print $2}' /etc/passwd
...
$ !ls
/bin /boot /home /dev /proc /root /tmp
Here we executed commands echo, ls, awk, and then used !ls to refer to the ls / command.
10. Open the previous command you executed in a text editor
$ fc
Fc opens the previous command in a text editor. It's useful if you've a longer, more complex command and want to edit it.
For example, let's say you've written a one-liner that has an error, such as:
$ for wav in wav/*; do mp3=$(sed 's/\.wav/\.mp3/' <<< "$wav"); ffmpeg -i "$wav" "$m3p"; done
And you can't see what's going on because you've to scroll around, then you can simply type fc to load it in your text editor, and then quickly find that you mistyped mp3 at the end.
Enjoy!
Enjoy the article and let me know in the comments what you think about it. In the next sub-part I'll continue discussing history keyboard shortcuts, history shell options and more.


Facebook
Plurk
more
GitHub
LinkedIn
FriendFeed
Google Plus
Amazon wish list
Comments
hi,
all the commands are nice and useful specially to find top 10 most used commands :)
but the third one is not working in my system even i am firing commands with space but still it is showing in history
I forgot that what gets ignored is configured through the
HISTIGNOREvariable. It contains a:separated values of command prefixes that should be ignored.It looks like your distribution doesn't have a default value for this variable.
It should contain this:
Meaning - don't log anything to history that starts with a space.
I've this in my
.bashrc:Here
&has a special meaning - ignore repeated commands.hmm. it was hashed in my .bashrc now i have corrected it and it is working :)
1. It looks weird, that you are taking the second column of history output with cut, instead of doing that inside of awk block. (in your "top 10" example)
2. I think that awk is an overkill for the common task of counting the number of entries. The same can be done a la "| sort | uniq -c | sort -nr". I think that this code snippet is much easier to remember than the line of awk code, even though it performs worse.
1. I love when each command does just one thing, and I love to pipe things together.
I could have written it as a Perl one-liner:
history | perl -lne 's/\d+//;s/^\s+//;s/\s\s/ /; $count{$_}++; END { print "$count{$_} $_" for (sort { $count{$a} <= $count{$b} } keys %count )[0..10] }'2. Good example of combining commands together.
Also, it would be an overkill to use
history | sort | uniq -c | sort -rn
because, as you stated in the article, history is printing .bash_history to the screen. For most users it would be better to run:
$ sort .bash_history | uniq -c | sort -rn
because this will exclude one pipe.
3. does not work here either (Debian Squeeze).
10. can be done easier by using regular expressions after the bang, in case of your mistyped "for wav in..." you could type:
!f:s/m3p/mp3
(other example here http://linux-training.be/files/books/html/fun/ch13s09.html )
cheers,
paul
3. I forgot that what gets ignored is configured through the
HISTIGNOREvariable. It contains a:separated values of command prefixes that should be ignored.It looks like your distribution doesn't have a default value for this variable.
It should contain this:
10. Good one. Alternatively you could've written
^m3p^mp3. I'll cover these in the next part.By default commands started with space will be recorded to history.
In order to change it you have to configure it by:
export HISTCONTROL="ignorespace"
Nice tip.
You can also do the same by doing:
I use CTRL+R a lot to quicksearch & reuse my history, might be a nice addition to your post.
Definitely. I'll talk about it in the next post.
I actually wrote about these keyboard shortcuts in my Definitive Guide to Bash History a long time ago.
I personally prefer using HISTCONTROL=erasedups. It's the inverse of ignoredups, in that it removes all previous instances of the command from your history, so that only the most recent one is kept.
Checkout Bash "inputrc", which controls the behavior of readline. You can do some really cool things (capture command in a visual editor, modify it, return it to the command line for execution). As I was reading the above, I kept echoing one variable at a time. I used parameter expansion:
for p in "${!HIST@}"; do echo $p=${!p}; done
I thought I'd replace sed/awk with bash using associative arrays and the redirection in your earlier one-liner. My history is prefixed with the date, time, as well as the number, thus the 3 _'s.
declare -A aA; while read -r _ _ _ cmd; do ((aA["${cmd}"]++)); done < <(history)
for cmd in "${!aA[@]}"; do if (( ${aA[$cmd]} > 1 )); then printf -- '%3d %-.50s\n' ${aA[$cmd]} "${cmd}"; fi; done | sort -rn
shave embraced the new technologies and the CMS platforms, I think the new tools only make the web designs better. I am glad that new technologies are coming out in web design that make things easier, improved, and better looking for design
Leave a new comment