这是 Bash One-Liners Explained 系列的第四篇文章。在这一篇里,我会给大家介绍 Bash 命令行历史功相关的内容。我会选择用最合适的 Bash 方法,各种常见的语法和技巧,向各位阐明如何用 Bash 内置的命令和 Bash 编程语言来完成各式各样的任务。

1. 清除命令行历史

$ rm ~/.bash_history

Bash 将历史执行的命令都保存在文件.bash_history中,该文件位于你的家目录下。为了清除命令行历史,只要把这个文件删除即可。

注意,当你执行完退出后,最后一个rm ~/.bash_history命令依然会被记录下来。如果你想隐藏清除的操作命令,请看下一条。

2. 当前会话下停止记录命令行历史

$ unset HISTFILE

环境变量HISTFILE指向命令行执行历史保存的目标文件路径,如果你重置了该变量,Bash 就不会保存历史。

另外一种方法是将它指向/dev/null

$ HISTFILE=/dev/null

3. 不要记录当前执行的命令

很简单,只要在命令之前加空格就行:

$  command

注意,以上正常工作的前提是,HISTIGNORE变量被正确的设置,它的值是冒号分隔的匹配表达式列表,如果一个命令匹配其中的任意一个表达式则不会被保存到记录中。

例如,忽略空格开头的命令:

HISTIGNORE="[ \t]*"

我(原文作者)的配置如下所示:

HISTIGNORE="&:[ \t]*"

这里的&符号是有特殊含义的,它表示上一次执行的命令。所以,这里除了忽略空格开头的命令之外,重复执行的命令也只会被记录一次。

4. 更改保存命令行历史的目标文件

$ HISTFILE=~/docs/shell_history.txt

之后执行的命令会被记录到文件~/docs/shell_history.txt中。

5. 命令行历史记录中增加时间戳

$ HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S"

如果你将环境变量HISTTIMEFORMAT设置成一个合法的日期格式(具体请参考man 3 strftime),Bash 会在历史记录中同时保存命令执行的时间,而且在你执行history命令时,它也会将时间显示出来。

6. 显示历史

$ history

history命令可以按行显示执行的历史命令,如果设置了HISTTIMEFORMAT,在结果中还会显示命令执行的时间。

7. 显示最近执行的50个命令

$ history 50

如果你在执行history命令时,指定一个数字参数,例如 50,那么它只会显示最近50个命令。

8. 显示执行最多的10个命令

$ history |
    sed 's/^ \+//;s/  / /' |
    cut -d' ' -f2- |
    awk '{ count[$0]++ } END { for (i in count) print count[i], i }' |
    sort -rn |
    head -10

这一行命令中结合了sed, cut, awk, sorthead等多个命令。让我们来回顾下整个过程,理解发生了什么。假设history命令的输出是这样的:

$ history
    1  rm .bash_history 
    2  dmesg
    3  su -
    4  man cryptsetup
    5  dmesg

首先,我们通过sed命令删除开头的空格,同时将行号后的连续两个空格替换成一个:

$ history | sed 's/^ \+//;s/  / /'
1 rm .bash_history 
2 dmesg
3 su -
4 man cryptsetup
5 dmesg

接下来,我们使用cut命令将第一列删除:

$ history |
    sed 's/^ \+//;s/  / /' |
    cut -d' ' -f2-

rm .bash_history 
dmesg
su -
man cryptsetup
dmesg

然后,我们再用awk命令统计命令在历史记录中出现的次数:

$ 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

紧接着,使用sort命令逆序排列结果:

$ 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

最后,我们截取最开始的10行,即10个最频繁实用的命令。

$ history |
    sed 's/^ \+//;s/  / /' |
    cut -d' ' -f2- |
    awk '{ count[$0]++ } END { for (i in count) print count[i], i }' |
    sort -rn |
    head -10

以下是我的使用最多的10个命令:

2172 ls
1610 gs
252 cd ..
215 gp
213 ls -las
197 cd projects
155 gpu
151 cd
119 gl
119 cd tests/

9. 快速执行上一个命令

$ !!

输入两个感叹号,第一个感叹号表示开始历史命令替换,而第二个感叹号表示上一次执行的命令。例如:

$ echo foo
foo
$ !!
foo

这里echo foo被重复执行了一次。

这个用法在你忘记通过sudo执行命令时尤其有用,例如:

$ rm /var/log/something
rm: cannot remove `/var/log/something': Permission denied
$
$ sudo !!   # executes `sudo rm /var/log/something

10. 快速执行最近一个以特定字符串开头的命令

$ !foo

上一个命令中,第一个感叹号表示开始历史命令替换,后面的内容表示最近一次执行的以foo开头的命令。例如:

$ 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

11. 使用文本编辑器打开上一次执行的命令

$ fc

fc命令执行后,会用文本编辑器打开上一个命令。当你想要编辑一个很长并且复杂的命令式,这个功能会帮你省下不少功夫。

例如,你输入了下面一行错误的命令:

$ for wav in wav/*; do mp3=$(sed 's/\.wav/\.mp3/' <<< "$wav"); ffmpeg -i "$wav" "$m3p"; done

当你输完命令后,因为内容过长,你找不出错误的地方。这中情况下,你可以使用fc命令加载该命令到文本编辑器中,然后错误的地方(最后的 mp3 单词拼错)就一目了然了。