目录
Bash history 和 fc 命令详解
history 简单介绍
在 Bash 命令行上执行各种命令,特别在键入比较长的命令时,如果每次都要手工逐个字符重复输入,比较烦人。因此,尽可能利用好 Bash 的历史扩展功能非常必要,其目的是便于复用前面执行过的命令,以提高工作效率,尽可能避免错误。近日花费不少时间进行理解和整理,为了今后使用查找方便,总结如下:
Bash 默认开启交互式登录的 history 功能,在用户登录时读取默认文件 (~/.bash_history
) 中的记录,并在当前会话使用过程中,不断将新执行的命令自动添加到历史记录列表中。如果用户正常退出登录,就会把内存中的命令历史记录自动保存到默认文件中。
在我们与历史命令打交道过程中,我们总体上有几种操作:
- 一是管理历史记录集合(增删查改);
- 二是如何复用历史记录中命令(这是重点);
- 三是管理与历史记录及文件相关的几个环境变量,例如设置历史记录的数量,显示历史记录时是否带有执行命令时的时间等;
- 四是如何更好地修复命令,使用
fc
修复命令。
一、管理历史记录集合
对集合的常规操作一般是增删查改四个动作,历史记录集合也不例外。不过特定于历史记录的具体情况,还要管理保存历史记录的文件。因为我们在 shell 上操作的永远是内存中的记录列表,因此还需要有读写其它文件中的记录,或者把新增的部分记录追加到相应文件中等操作。
另外,还可以只是在历史记录列表中记录命令历史而不实际执行,及实际执行了命令而不记录到历史中。
history
:其作用是显示或者操纵命令历史列表。显示命令历史列表时行前带有行号(或编号),修改过的条目前面有 ‘*’。
history 命令的语法为:
history [n] # 显示最后 n 条历史记录,如果省略 n ,显示所有
history -c # 清除内存中的所有历史记录
history -d offset # 删除指定编号的历史记录
history -anrw [filename] # 几个与文件相关的读写操作选项
history -s arg [arg ...] # 不执行命令,而记录到历史列表中
history -p arg [arg ...] # 执行命令,但不记录其执行命令
查询
在使用 history [n]
时,默认会在每条命令条目前面加上编号,并且按照执行命令的时间倒序排列。
# 查看内存中的所有历史记录列表:
]# history
1 date
2 date +"%F %T"
3 date 121210302025.30
4 date -s "2023-10-13 10:25:30"
5 hwclock -s
......
# 查看最后 5 条历史记录
]# history 5
144 cat /etc/issue
145 cat /etc/os-release
146 ll
147 ls /
148 history 5
删除
# 想要删除历史列表中的某条记录,使用 `history -d n`,
# 如果 n 是一个正整数,就会删除对应编号的记录条目;
# 如果 n 是一个负整数,则会删除反向倒数对应的条目。
]# history -d 145
]# history 5
145 ll
146 ls /
147 history 5
148 history -d 145
149 history 5
# 想要清除所有内存中的历史记录
]# history -c
# 此时内存中的历史记录全部被清除,但是文件中的历史记录不会被删除,
# 这样就是丢失了当前会话中新执行的历史记录而已。
# 可以再次读入文件中的历史记录,或者重新登录即可自动读取文件中的记录。
增加
增加历史记录;这是在不断执行命令过程中自动进行的。当然也可以从默认或其他保存有历史记录的文件中读取。
-
当然也可以不真正执行,只是在历史记录中新增一条记录而已。
# 看看下面这条命令,是否很危险?!但是,放心,并不会真正执行 ]# history -s rm -rf / ]# history 5 147 history 5 148 history -d 145 149 history 5 150 rm -rf / 151 history 5
-
执行命令,但不记录到历史列表中
]# history 5 166 ls 167 ll /etc/issue 168 history 5 169 cat bash_history.new 170 history 5 ]# history -p ll /etc/os-release ll /etc/os-release ]# history 5 166 ls 167 ll /etc/issue 168 history 5 169 cat bash_history.new 170 history 5
这时没有看到最后执行的命令记录。
修改
修改历史记录中的命令是为了执行,没有纯粹的修改。具体实例放在复用历史记录中。
追加新的历史记录到文件中
# 查看当前 `~/.bash_history` 文件中的最后 5 条记录情况
]# tail -5 ~/.bash_history
rpm -qa bash*
help history
exit
yum provides nmcli
exit
# 把当前会话中新增的历史记录追加到文件中
]# history -a
# 再次查看历史记录文件的最后 5 条记录
]# tail -5 ~/.bash_history
history 5
tail ~/.bash_history
tail 5 ~/.bash_history
tail -5 ~/.bash_history
history -a
# 把所有内存中的历史记录都保存到其它指定的文件中
]# history -w bash_history2
]# wc -l bash_history2
158 bash_history2
从其它文件中读取历史记录到当前会话中
# 手动创建一个文件,并写入部分记录数据
]# cat bash_history.new
ls
ll /etc/issue
# 读取这个文件的记录追加到历史记录列表中
]# history -r bash_history.new
]# history 5
164 vim bash_history.new
165 history -r bash_history.new
166 ls
167 ll /etc/issue
168 history 5
如果多次执行 history -r
操作,文件的记录会多次追加到会话中的历史记录中。
而 history -n
:其作用是从文件中读取尚未读取过的行追加到当前会话历史记录中。
刚开始学习时难以理解这个命令使用场景,后来看到一篇博文才理解其用意:具体使用场景为:
我们先复制当前会话,这时这两个会话中的历史记录是一致的。然后在一个会话(简称:会话1)中执行了一些新的命令,而另一个会话(简称:会话2)也想同步这些新增的历史记录时,可以先把会话1中的新增的历史记录保存到文件中,
这时会话2就可以执行同步操作了。
会话1中执行追加新增命令记录的操作
]# history -a
# 会话2中执行同步操作,把没有读取过的记录同步到历史记录中,
# 实现了两个会话的历史记录同步
]# history -n
另外已经读取过的历史记录不会再追加到内存中的历史记录中,对比两个命令,建议使用 history -n [filename]
。
具体更详细的介绍参考:Linux history -n 与 history -r 功能区别
二、复用历史记录
复用历史记录中的命令,减少击键数量,总体思路是:在历史记录中找到想要的命令后直接执行;或者适当修改一下历史记录中的某个命令后再执行;或者复用历史命令中的各个组成部分及对这些部分进行修饰等。
不管进行何种复用操作,首先要知道如何在历史记录列表中找到相应的记录,然后,要么直接执行,要么复用历史命令中的各个组成部分,以及对这些部分进行调整。
在 Bash 的历史命令的帮助中,把命令行上用于触发从历史记录列表中搜索并扩展某条记录的动作称为“事件”,把历史记录命令中的各个组成部分(使用空格隔开的)称为“单词”,并有一些“修饰符”用于操作单词部分,例如替换,提取文件名、文件扩展名、目录等。
事件指示符
输入少量字符来查找命令历史记录,其实就是历史扩展功能。引入历史扩展的是特定的字符,这个字符默认是感叹号 !
。
想要转义这个字符使之成为字面值,可以使用斜杠(\)或者单引号。还有几个字符如果紧跟在 !
后面也会阻止其历史扩展,具体为:
空格,tab键,换行,回车和 = ,如果启用了extglob shell选项,还有小括号 ( 。
事件指示符是对历史记录列表中命令条目的引用。以下是语法:
! 开始历史替换,可以理解为,只要在命令上输入了 !,并在紧跟以下字符,
就是触发历史扩展功能。
!n 引用历史记录中第 n 条命令。
!-n 引用当前命令行倒数 n 条命令。
!! 引用前一个命令。这等价于 `!-1`。
!string
引用历史记录中以 string 开头的上一条命令。
!?string[?]
引用历史记录中包含 string 的上一条命令,
如果 string 后面紧跟换行,则尾随的 ? 可以省略。
^string1^string2^
用 string2 快速替换 string1 后重复执行前一个命令。
这相当于后面要示例的 "!!:s/string1/string2/"。
!# 引用到目前为止输入的整个命令。没有找到使用场景,后面没有示例。
为了示例,先清理内存中的历史记录,执行 history -c
,然后执行一些操作以产生一些历史记录。
]# history
1 tar cf etc.tar /etc/
2 cp /etc/passwd passwd.backup
3 ps aux | grep http
4 systemctl restart sshd
5 /usr/sbin/apachectl restart
6 history
-
使用编号
!n
搜索历史记录中的指定命令如果想要直接执行前面已经执行过的命令,不需要重新输入,只需通过使用历史记录中的命令行编号即可。
]# !4 systemctl restart sshd
想要执行倒数第 2 条命令:
]# !-2
想要执行上一条命令,可以使用以下之一:
]# !! ]# !-1
你也可以使用快捷键
ctrl+p
(如果你在默认的 emacs 模式)来得到上一条命令。
也可以使用向上的方向键。我个人认为如果不想直接执行,这两种方法更好,因为这种方法是先得到上一条命令,有进一步修改的机会,然后按回车会才执行。如果你已经使用了
set -o vi
,启用了 vi 式样的命令行编辑,则使用Esc,k
来获得相同的结果。 -
使用
!string
和!?string
来执行带关键字的命令你也可以使用关键字来执行历史记录中的命令。个人认为这是更好的选择,因为你不需要知道编号是多少。
以下的示例会搜索以
ps
开头的最近使用过的命令,并执行。4]# !ps ps aux | grep http root 29325 0.0 0.5 282924 11916 ? Ss 10:50 0:00 /usr/sbin/httpd -DFOREGROUND apache 29326 0.0 0.4 296808 8600 ? S 10:50 0:00 /usr/sbin/httpd -DFOREGROUND apache 29327 0.3 1.0 2730920 20472 ? Sl 10:50 0:02 /usr/sbin/httpd -DFOREGROUND apache 29328 0.2 0.9 2599784 18424 ? Sl 10:50 0:01 /usr/sbin/httpd -DFOREGROUND apache 29329 0.2 0.9 2534248 18424 ? Sl 10:50 0:01 /usr/sbin/httpd -DFOREGROUND root 29555 0.0 0.0 12136 1040 pts/9 S+ 11:01 0:00 grep --color=auto http
以下示例会搜索包含关键字
apache
的最近使用过的命令,并执行。]# !?apache /usr/sbin/apachectl restart # 这里使用 ? 代表逆序向上搜索,这与vim中 ? 功能一样,便于记忆。
你也可以使用快捷键
ctrl+r
逆向搜索,并且是可视的增量搜索,使用更友好:]# (reverse-i-search)`apa': /usr/sbin/apachectl restart
在搜索过程中,可以通过
ctrl+o
执行命令,并获取历史记录中当前行的下一行到命令行上等待编辑,ctrl+g
放弃执行搜索到的命令。这里还有一个小技巧:就是找到想要的命令后,点击左或右方向键就能显示整条命令等待修改,有时这样更好。
-
使用
^string1^string2
替换上一条命令在下面的示例中,首先执行
ls
命令来验证一个文件,然后意识到我们想要查看文件的内容。这时可以不需要再次键入完整路径及文件名,只需将前面命令中的ls
替换成cat
,如下所示:]# ls /etc/cron.daily/logrotate /etc/cron.daily/logrotate ]# ^ls^cat^ cat /etc/cron.daily/logrotate
该语法是下面要介绍的单词修饰符
:s
简化版。
单词指示符
在您键入一个新的命令时,单词指示符可能非常有用,它可以使用前面执行过的命令的参数(即为单词)。使用冒号(:)与事件提示符隔开。下面提供一些示例。
-
使用
^
获取命令的第一个参数在以下示例中,
!cp:^
当成一个参数传递给ls -l
命令。!cp:^
在历史记录中定位以cp
开头的先前命令,且取得该命令的第一个参数。]# cp /etc/passwd passwd.backup [root@CentOS8 ~]# ls -l !cp:^ ls -l /etc/passwd
以下示例获取上一个命令的第一个参数。
]# ls /etc/issue /etc/os-release /etc/issue /etc/os-release ]# ls -l !!:^ ls -l /etc/issue # 以下是 ls -l !!:^ 简化版 ]# ls -l !^
-
使用
$
获取命令的最后参数在以下示例中,
!cp:$
作为一个参数传递给ls -l
命令。!cp:$
先定位以cp
开头的前一个命令,且取得该命令的最后参数。]# cp /etc/passwd passwd.backup ]# ls -l !cp:$ ls -l passwd.backup
以下示例取得上一个命令的最后参数。
]# ls -l !!:$ # 更简化的格式 ]# ls -l !$
其实更好的方法是使用快捷键来取得上一个命令的最后参数。在命令行上先键入命令的前面部分,空格后采用以下两种之一的组合键,
ESC, .
或者Alt+.
。注意:后一种快捷键可能无效,原因是该快捷键被占用,或没有把 Meta 键设置成 Alt 键。
在xshell中,如果需要进行设置,方法是:
右键所有会话中的某个会话 --> 属性 --> 终端 --> 键盘 --> 选择元(Meta)键仿真下的多选按钮),顺便把 DELETE键序列选择两个 ASCII 127的单选按钮。 -
使用
:n
获取命令的第 n 个参数在以下示例中,
!tar:2
作为一个参数传递给ls -l
命令。也是先在历史记录中定位以tar
开头的上一个命令,并获得该命令的第 2 个参数。]# tar cvfz home-dir-backup.tar.gz /home ]# ls -l !tar:2 ls -l home-dir-backup.tar.gz
-
使用
:*
获取命令的所有参数在以下示例中,
!cp:*
作为一个参数传递给ls -l
命令。也是先在历史记录中定位以cp
开头的上一个命令,并获得该命令所有的参数。]# !cp cp /etc/passwd passwd.backup ]# ls -l !cp:* ls -l /etc/passwd passwd.backup
-
使用
!%
引用最近搜索到的单词正如上面解释的那样,
!?apache
将搜索历史记录中包含关键字apache
的命令,并执行它。]# /usr/sbin/apachectl restart ]# !?apach /usr/sbin/apachectl restart
!%
将引用被前个?
搜索匹配的整个单词。例如,如果前面已经进行了
?apache
搜索,那么这个!%
将会匹配整个单词/usr/sbin/apachectl
。注意,在该环境中/
作为一个单词的组成部分对待。其实是以空白分割的各个部分都是单词。因此,在这个示例中,通过执行以下命令,就可以停止 apache 服务了。
]# !% stop /usr/sbin/apachectl stop
-
使用
x-y
获取命令中的指定范围的参数在以下示例中,
!tar:3-5
作为一个参数传递给ls -l
命令。!tar:3-5
先在历史记录中定位以tar
开头的上一个命令,并取得第 3 个至第 5 个参数。]# tar cvf home-dir.tar john jason ramesh rita ]# ls -l !tar:3-5 ls -l john jason ramesh
以下命令将得到从第 2 开始的所有参数。
]# ls -l !tar:2-$ # 更简单的格式为 ]# ls -l !tar:2* # 而以下的格式等价于 `x-$-1`,即从第x个开始至倒数第2个为止,不含最后一个参数 ]# ls -l !tar:2-
特别说明:如果在没有指定事件提示符的情况下应用单词提示符,则上一个命令当成事件,即选中最近的上一个命令。
历史修饰符
修饰符用在单词指示符之后,请看下面的示例中的解释。
-
使用
:h
以删除单词中尾随的路径名在以下示例中,
!!:$:h
得到上一个命令中的最后参数,并删除尾随的路径名。在这个案例中,它删除文件名,最后得到完整的路径。]# mkdir -p /very/long/path/name/ ]# touch /very/long/path/name/file-name.txt ]# ls -l /very/long/path/name/file-name.txt ]# ls -l !!:$:h ls -l /very/long/path/name
-
使用
:t
从单词中删除所有前导路径名这与前面的示例刚好相反。
在以下示例中,
!!:$:t
获取上一个命令的最后参数,并删除所有前导路径名。在这个案例中,它只得到文件名。]# ls -l /very/long/path/name/file-name.txt ]# ls -l !!:$:t ls -l file-name.txt
-
使用
:r
删除单词中的文件名扩展名在以下示例中,
!!:$:r
获取上一个命令的最后参数,并仅删除“.后缀”(这里是文件名的扩展名)。在这个案例中,它删除.txt
。]# ls -l /very/long/path/name/file-name.txt [root@CentOS8 ~]# ls -l !!:$:r ls -l /very/long/path/name/file-name
-
使用
:s/string1/string2
像 sed 一样在 bash 历史中进行替换我们不使用前面讨论过的
^original^replacement^
这种格式,也可以在 bash 历史中使用像 sed 那样的替换,示例如下。这可能容易记住:!!
用于调用上一个命令,:s/original-string/replacement-string/
像 sed 那样语法替换字符串。]# !!:s/ls -l/cat/
您也可使用
g
标志(放在s
前面)进行全局替换,如下所示。当您拼错多个单词并想修改所有这些词,然后再次执行该命令时比较有用。在以下示例中,我错误地键入两次"password"(应该是"passwd")。
]# cp /etc/password password.bak
想要解决这个问题,只需像 sed 一样执行以下的全局历史命令替换操作。
]# !!:gs/password/passwd/ cp /etc/passwd passwd.bak
-
使用
:&
快速重复替换如果您像上面示例那样已经成功地执行了 bash 历史替换,那么,您能使用
:&
快速重复相同的替换。]# tar cvf password.tar /etc/password
现在,您可以不用重新键入该命令,或者执行
gs/password/passwd
,仅仅使用:&
,就能复用最后的替换操作。使用:g&
是为了全局复用最后的替换操作。]# !!:g& tar cvf passwd.tar /etc/passwd
-
使用
:p
仅仅打印命令而不执行当您正在执行复杂的历史替换,且在执行它前,想查看最终的命令的样子时,这非常有用。
因为使用了
:p
,因此它仅仅执行替换和显示新的命令。一旦您已经验证该 bash 历史扩展,且其结果正是您想要执行的命令,只需删除:p
,然后再次执行它。]# tar cvf home-dir.tar john jason ramesh rita ]# tar cvfz new-file.tar !tar:3-:p tar cvfz new-file.tar john jason ramesh # 以下介绍一些使用语法 !string:p # 仅打印逆序搜索到以string开头的命令,不实际执行命令 !$:p # 仅打印输出上一条命令的最后一个参数 !*:p # 仅打印输出上一条命令的所有参数
三、与 history 命令相关的环境变量
Bash 默认开启历史扩展功能,如果想关闭或者启用该功能,可采用以下设置:
# 关闭历史扩展功能
]# set +o histexpand
# 启用历史扩展功能
]# set -o histexpand
在非交互的shell中(例如,shell 脚本)没有开启历史扩展功能。
Bash 内置了 6 个环境变量,用于控制 history 的特性。如果想修改这些环境变量,可以在 ~/.bash_profile
文件进行添加或者修改,例如在该文件中添加:
HISTSIZE=2000
export HISTSIZE
-
HISTSIZE:指定可以添加到内存中历史记录的最大数量,默认是 1000 条。
-
HISTFILESIZE:指定可以保存到历史记录的文件中记录的最大数量,有默认值,各版本不尽相同。
# Ubuntu 22.04 ]$ echo $HISTSIZE; echo $HISTFILESIZE 1000 2000 # CentOS 8.5 ]# echo $HISTSIZE; echo $HISTFILESIZE 1000 1000
-
HISTFILE: 指定保存历史记录的文件,默认是:
~/.bash_history
。你可以指定不同的文件来保存历史记录,例如:
vim ~/.bash_profile HISTFILE=~/.bash_history2 export HISTFILE
-
HISTTIMEFORMAT:在显示历史记录时,默认显示带编号的记录。如果想要显示带有时间日期的记录,可以设置该变量。
]# export HISTTIMEFORMAT='%F %T ' ]# history 1 2022-10-18 10:07:15 date 2 2022-10-18 10:07:15 date +"%F %T" 3 2022-10-18 10:07:15 date 121210302025.30 4 2022-10-18 10:07:15 date -s "2023-10-13 10:25:30" 5 2022-10-18 10:07:15 hwclock -s ......
用于该变量设置的格式字符串与
date
命令的相同。 -
HISTIGNORE: 指定那些命令予以忽略,不会记录到历史列表中。
HISTIGNORE=“cmd1:cmd2*”,表示忽略cmd1,及以cmd2开头的命令,不会记录到历史中。]# export HISTIGNORE="pwd:ls:exit" ]# pwd /root # ls anaconda-ks.cfg ]# history 3 270 2022-10-18 10:34:38 man date 271 2022-10-18 10:36:39 export HISTIGNORE="pwd:ls" 272 2022-10-18 10:37:03 history 3 # pwd, ls 命令没有被记录
-
HISTCONTROL:控制历史命令的记录方式。可以取以下值:
ignoredups:这是默认值,可以忽略连续重复的命令。 ignorespace:忽略所有以空格开头的命令。 ignoreboth:相当于赋值 ignoredups, ignorespace 的两个值。 erasedups:删除历史记录中的重复命令,包括不连续的。
如果我们把该变量设置成
ignoreboth
,那么连续相同的命令只记录一次;以空格开头的命令不会记录。这样,可以使用空格加命令,以确保带有敏感信息的内容不会被记录,如密码。export HISTCONTROL=ignoreboth
四、fc 修复命令
fc 是 fix command 的缩写。这是 Bash 的内置命令,用于显示或编辑,及重新执行历史记录列表中的命令。您可以在您喜欢的编辑器中编辑指定执行过的命令并执行,无需重新键入该完整的命令。此命令有助于纠正前面键入的命令中的拼写错误,并避免重复键入冗长复杂的命令。大多数 shell 都可以使用,包括 Bash、Zsh、Ksh等。
其语法为:
fc [-e ename] [-lnr] [first] [last]
其中的选项说明:
first 和 last 可以通过数字指定范围,或者 first, last 也可以是字符串,
用以确定以该字符串开始的最近的命令。
-e ename 选择用于编辑的编辑器。默认是FCEDIT,然后 EDITOR,最后是 vi。
-l 列出部分历史记录,而不是编辑命令。
-n 在列出历史记录时,省略行号。
-r 翻转行的排列次序。
fc -s [pat=rep] [command]
这种格式就是先进行替换,即每个 pat 都替换成 rep,然后重新执行。command 与上一种格式的 first 一样。
一种有用的别名就是使用这种语法,`alias r="fc -s"` ,因此,键入 `r cc` 执行以 `cc` 开头的最后命令,
键入 `r` 重新执行上一条命令。
CentOS 8 默认编辑器是 vi, 而 Ubuntu 22.04 默认是 nano,如果想要修改为vi,可以使用
$ sudo update-alternatives --config editor
然后选择
/usr/bin/vim.basic
即可。也可以使用后面介绍的方法,及设置环境变量。
使用 fc 命令列出最近执行过的命令
在执行不带参数的 fc -l
命令时,将显示最近执行过的 16 个命令历史记录。
$ fc -l
171 history -w
172 cat .bash_history
173 man bash
174 sudo update-alternatives --config editor
175 man grep
176 export | grep -i hist
177 history 5
178 fc -l
179 export | grep -i hist
180 ls hist
181 export | grep -i hist
182 xcli
183 sudo apt install xclip
184 man xclip
185 echo hello | xclip
186 man bash
使用 -r
选项,可以翻转历史记录的次序。
$ fc -lr
使用 -n
选项,可以压制行号出现。
$ fc -ln
man grep
export | grep -i hist
history 5
fc -l
export | grep -i hist
ls hist
export | grep -i hist
xcli
sudo apt install xclip
man xclip
echo hello | xclip
man bash
fc -l
help fc
fc -lr
fc -l
现在您看不到行号了。
想要列出从指定命令开始的结果,只需使用带行号的 -l
选项。例如,要显示从第 100 行开始后所有记录,示例如下:
$ fc -l 100
100 vim .bash_history
101 man history
102 man 1 history
103 man -k history
104 history
105 history -h
106 help history
......
想要列出指定范围的记录,例如,从第 100 行至 105 行。
$ fc -l 100 105
100 vim .bash_history
101 man history
102 man 1 history
103 man -k history
104 history
105 history -h
我们也可以不使用行号,而是使用字符串。例如,想要列出从 export 命令开始至最近的记录,只需使用 export 命令的起始的几个字符,示例如下:
$ fc -l exp
181 export | grep -i hist
182 xcli
183 sudo apt install xclip
184 man xclip
185 echo hello | xclip
186 man bash
187 fc -l
188 help fc
189 fc -lr
190 fc -l
191 fc -ln
192 history
193 fc -l 2
194 fc -l 100
195 fc -l 100 105
196 history
想要查看在 export 和 man 之间的命令记录,您可以使用以下一种:
$ fc -l exp ma
181 export | grep -i hist
182 xcli
183 sudo apt install xclip
184 man xclip
185 echo hello | xclip
186 man bash
# 混合使用 行号和字符串
$ fc -l exp 186
# 都使用行号
$ fc -l 181 186
注意:使用字符串定位时,都是逆序搜索历史记录列表的,上面示例中,有两个 man 命令。
编辑并自动执行命令
在 shell 中执行命令,总是会遇到拼写错误情况,如果命令比较短,重新键入也可以,但是如果命令长而复杂,要在命令行上进行修改比较困难,这时应该是 fc
出场时候。
编辑并执行上一条命令
如果您刚刚错误拼写了一条命令。在这种情况下,您可以使用默认的编辑器轻松地修正该条命令的错误之处并执行,而无需重新输入。
想编辑上一条命令并再次执行它,只需:
fc
这时就会打开默认的编辑器并显示上一条命令。
正如您所见,最后一条命令 fc -l exp ma
出现在里面。现在您可以在这条命令里进行任意的修改,且一旦您保存退出编辑器就会自动再次执行它。
编辑并执行指定命令
先从历史记录列表中找出想要编辑的命令,然后记住其编号。在以下示例中,使用编号 214。
$ fc -l 210
210 fc -l
211 sudo apt list rang*
212 export FCEDIT=vim
213 ranger
214 echo hello fix command
215 pwd
216 cat /etc/issue
217 cat /etc/os-release
218 help fc
219 fc -l
220 cat /etc/os-release
221 pwd
222 echo hello fix command
此时只需使用以下命令就可以编辑第 214 条命令,该命令将在默认文件编辑器中打开等待编辑。
fc 214
修改完成后,保存并退出编辑器。修改后的命令自动执行。
也可以使用字符串指定最近执行过的命令,例如,要修改 export
那个命令,如下所示:
$ fc expo
后面的操作与前面一样。
编辑并执行多条命令
如果您认为 fc
一次只能编辑和执行一条前面的命令,那么您就错了。因为 fc
还允许编辑多个前面的命令!这有什么用呢?假设您要执行多条互相有关联的命令,结果在执行时次序不正确,这时使用该技巧比较适合。
- 首先您确定好要编辑的编号范围,例如 2 - 5。
$ fc -l 1 6
1 hwclock
2 sudo hwclock
3 date; sudo hwclock
4 man zdump
5 zdump HongKong
6 zdump china
- 执行以下命令
fc 2 5
-
这时会打开编辑器,您可以任意编辑这些命令,包括调整命令前后次序。
-
完成后,保存退出编辑器,就会自动执行编辑好的命令了。
重要提示:请注意,这也可能会有破坏性的。例如,如果上一条命令是类似于
rm -rf <some-path>
这种致命命令,退出编辑器后就自动执行,这样可能会丢失重要数据。因此,在使用fc
命令是要格外小心。
指定用于编辑命令的默认编辑器
fc
的另一个重要选项是 e
,用于选择编辑命令的不同编辑器。例如,我们可以使用 “nano” 编辑器来编辑上一条命令,示例如下:
$ fc -e nano
该命令会打开 nano 编辑器(而不是默认编辑器)来编辑上一条命令。
您可能会发现每次使用时指定 -e
选项很费时间。要将您喜欢的编辑器设置成默认值,只需将环境变量 FCEDIT
设置为 fc
要使用的编辑器名称即可。
例如,想要设置 “vim” 成为新的默认编辑器,只需编辑您的 ~/.profile
或者环境文件。
$ vi ~/.profile
增加以下内容:
FCEDIT=vim
export FCEDIT
保存退出文件后,使用以下命令来更新这些修改:
$ source ~/.profile
现在,您可以仅键入 fc
使用 “vim” 编辑上一条命令。
不编辑直接重新执行前面的命令
我们已经知道如果执行不带参数的 fc
,它将打开编辑器来编辑最近的命令。有时,您可能不想编辑,只是简单地执行指定的命令。
直接执行上一条命令
第一种方法:在 fc -e
末尾使用连字符(-)符号,如下所示。
$ echo hello fix command
hello fix command
$ fc -e -
echo hello fix command
hello fix command
如您所见,即使使用了-e
选项,fc
也不会编辑上一条命令。
第二种方法:这种方法更简单,即使用 fc -s
直接执行上一条命令。
$ fc -s
直接执行指定的命令
这种方法的使用需要先确定好命令的编号,其余的操作非常简单,示例如下:
$ fc -s 5
这时就会打开编辑器,并把第 5 条命令显示在其中等待编辑。同样,也可以指定范围,一起执行。
对指定的命令替换后直接执行
有时,我们想要直接替换指定的命令后再次执行,例如,先验证一个文件,然后替换命令查看其内容。可以使用以下格式:
$ ll /etc/issue
-rw-r--r-- 1 root root 26 Aug 22 10:54 /etc/issue
$ fc -s ll=cat
cat /etc/issue
在该格式中的 “command” 部分,其功能与 前一个格式的 “first” 一样。
在 zsh 中显示命令执行时间
请注意,有些选项是特定于 shell 的。它们在其他 shell 中可能无效。例如,在 zsh shell 中可以使用以下选项。而在 Bash 或 Ksh shell 中出错。
想要查看历史记录中的命令何时执行,请使用 -d
选项,示例如下:
% fc -ld
2 10:31 pwd
3 10:31 ls
4 10:32 echo hello fix command
6 10:33 fc -lf
7 10:34 fc -lE
8 10:40 fc -d
9 10:40 10:34 fc -lE
现在您看到最近执行的命令的执行时间。
我们也可以显示每个命令执行的完整时间戳,请使用 -f
选项。
% fc -fl
2 10/18/2022 10:31 pwd
3 10/18/2022 10:31 ls
4 10/18/2022 10:32 echo hello fix command
6 10/18/2022 10:33 fc -lf
7 10/18/2022 10:34 fc -lE
8 10/18/2022 10:40 fc -d
9 10/18/2022 10:40 10:34 fc -lE
10 10/18/2022 10:40 fc -ld
小结
从我理解来看,能够使用好这些历史记录确实可以在许多情况下提升工作效率。
如果只是想再次执行上一个命令,使用快捷键 ctrl+p 或 上方向键
比较好,原因是这个快捷键比较好记,p 是 previous ,同时上一个命令显示在命令行上,有修改的机会,还可以多按几次,则取得再前面的命令,当然 Ctrl+n 则作用相反。
如果前面执行的命令长而复杂,里面有拼写错误或需要修改,则在自己喜欢的文件编辑器中编辑肯定更好,那就使用 fc
命令。
如果想要执行前面执行过的命令,但是不知道编号,使用这个这种格式比较好。即 !str
。
如果想要执行历史记录中含有 string
的,使用快捷键 ctrl+r
这种实时增量搜索比较直观。
如果想要复用前面命令的指定位置的参数,只能使用 history 中的相关格式。