查看历史命令

使用shell内置的history命令可以查看我们在交互式shell中执行的命令。history输出的结果可能是成百上千行,在history后加上整数可以将输出限制在最新几行:

1
history 3
1
2
3
34  ls never_delete_wrong_file_1
35 history
36 history 3

history向stdout中输出,因此我们可以用管道处理。

在当前shell中清楚(删除)历史,可以用-c选项:

1
history -c

从历史中重新召唤命令

三种从shell历史记录中回忆命令的节省时间的方法:

  1. 光标移动

    极其容易学习,但在实践中往往速度较慢

  2. 历史展开

    学习起来较难(坦白说,它很晦涩难懂),但可以非常快

  3. 增量搜索

    既简单又快速

光标移动

按上/下箭头选择历史命令。

历史展开

历史展开是一个通过特殊表达式获取历史命令的shell特性。表达式以感叹号(读作"bang")开头。例如,一行中的两个感叹号("bang bang")执行紧接着的前一个命令。

1
ignoreboth
1
!!
1
2
echo $HISTCONTROL
ignoreboth

要获得以特定字符开头的最近的命令,在该字符串前放置一个感叹号:

1
!grep
1
2
grep python animals.txt
python Programming Python 2010 Lutz, Mark

要获得以特定字符在某处的最近的命令,在该字符串前放置一个感叹号,并把该字符串用问号包围:

1
!?grep?
1
2
3
4
5
6
7
8
9
10
11
12
13
history | grep -w cd
2 cd efficientlinux
5 cd ch03
7 cd never_delete_wrong_file_1
8 cd ..
9 cd ch03
10 cd ..
11 cd ch01
13 cd ../ch02
15 cd ../ch01
17 cd command_4_grep
20 history | grep -w cd
22 history | grep -w cd

也可以用历史ID来执行某一特定历史命令(在ID前加感叹号):

1
!28
1
2
3
4
python3
Python 3.12.3 (main, Sep 11 2024, 14:17:37) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

感叹号后的负数用于以相对位置执行历史命令,例如!-3意味着“你执行的前第3条命令”。

历史展开高效而快速,但有些神秘。然而,它也可能很危险,如果我们执行了错误的代码。为了避免致命的错误,我们应该使用:p选项来打印而不执行命令:

1
!-5:p
1
python3

shell将未执行的命令添加到历史中,因此我们只需

1
!!

就可以快速执行了。

关于历史命令的常见问题

命令历史中储存了多少行命令?

历史中储存了HISTSIZE行命令。

1
echo $HISTSIZE
1
1000
1
HISTSIZE=10000

计算机记忆体是十分廉价的(10000行历史命令大约仅占用200K记忆体)。大胆的话,我们可以将HISTSIZE的值设置为-1以保存所有命令。

加入命令历史的是什么样的文本?

Shell会原封不动地追加你输入的内容,不进行求值。如果你运行ls $HOME,历史记录中将包含ls $HOME,而不是ls /home/smith

需要注意的是输入!!!-3这样的文本时,命令会先执行再被添加到历史中。

重复的命令会被追加到历史中吗?

答案取决于变 HISTCONTROL的值。默认情况下,如果这个变量未设置,那么每条命令都会被追加。如果其值为ignoredups,那么如果重复的命令是连续的,则不会被追加(其他值可参见man bash):

1
$HISTCONTROL=ignoredups

每个shell是否都有一个独立的历史记录,还是所有shell共享一个单一的历史记录?

每个交互式shell都有一个独立的历史记录。

永远不要再次删除错误的文件

使用alias

我们有时候使用模式匹配删除文件时会错误地输入而删除了的文件,例如我们要使用模式匹配*.txt,而错误地多打了一个空格:

1
rm * .txt

避免这种问题最常见的方法就是将rm -i的别名设置为rm,这样的话再删除文件前shell会询问:

1
2
alias rm='rm -i'
rm *.txt
1
2
3
rm: remove regular file '1.txt'? y
rm: remove regular file '2.txt'? y
rm: remove regular file '3.txt'? y

如此一来,多打的空格就不会致命了:

1
rm * .txt
1
rm: remove regular file '123'? ^C

如果发现错误,立即终止命令(Control + C)。

先确认再删除

先使用ls列举所选文件:

1
ls *.txt
1
1.txt  2.txt  3.txt

确认无误后再删除:

1
rm !$
1
rm *.txt

历史展开式!$意思是前一个命令的最后一个词。

命令历史的增量搜索

增量搜索(incremental search)跟搜索引擎提供的交互式建议很像。在大多数情况下,增量搜索是从历史中重新召回命令的最快捷、最简单的方法。

  • 在命令提示符处按下CTRL+R(R代表reverse incremental search)
  • 输入原来命令的任意一部分(开头、中间、结尾……)
  • 每当输入一个字符时,shell都会展示最近的与刚刚输入的字符匹配的命令
  • 当看到需要的命令时,按下ENTER

按下CTRL+R后:

1
(reverse-i-search)`':

但如果我们又一个命令完全包含了另一个命令呢?

可以再次按下CTRL+R,shell会自动跳转到历史中上一个匹配的命令。

命令行编辑

我们有如下几种命令行编辑的方法:

  • 光标移动
  • 插入符号表示法
  • Emacs或Vim风格的按键操作

在命令中光标移动

有如下操作:

操作 功能(效果)
左箭头 向左移动一个字符
右箭头 向右移动一个字符
CTRL+左箭头 向左移动一个单词
CTRL+右箭头 向右移动一个单词
Home 前往命令开头
End 前往命令结尾

插入符号的命令展开

假设下列命令中我们错误的将jpg打为jg

1
md5sum *.jg | cut -c1-32 | sort | uniq -c | sort -nr

我们不需要将命令重新打一遍,只需(即caret syntax):

1
^jg^jpg

就可显示命令已替换成功

1
md5sum *.jpg | cut -c1-32 | sort | uniq -c | sort -nr

[!NOTE]

使用这种方式只能替换命令中出现的第一个源字符串(jg)。

Emacs或Vim风格的按键操作

使用

1
set -o vi

1
set -o emacs

现在,我们就可以使用我们熟悉的编辑风格编辑命令行了!

剩下的就是练习、练习、再练习!