Bash的基本特性之命令历史history与神奇的叹号

history 命令

使用 history 可以显示历史记录和执行过的命令。命令历史是被保存在内存中的,当退出当前的 shell 时会自动保存到历史命令文件 ~/.bash_history 当中 ,当登录 shell 时则从这个文件中读取以前的命令到命令历史。

常用的选项:

  • history n 列出最近执行过的n条命令

  • history -c 清除所有历史命令

  • histoty -w 将缓冲区命令写入历史命令文件 ~/.bash_history

  • history -d 5 删除历史记录中序号为 5 的命令

配置的定义

在内存中仅能够存储 1000 条历史命令,该数量是由环境变量 HISTSIZE 进行控制的。这个值可以在 /etc/profile 中进行修改,修改成功后在下次登录时即可生效。

1
2
3
# vim +/HISTSIZE /etc/profile 
HISTSIZE=1000
# 注意等号两边不要有空格

默认情况下 history 不显示命令的执行时间,但是系统已经记录。同样,可以在 /etc/profile 进行修改,修改成功后在下次登录时即可生效

1
2
3
export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL  # 在这一行下面添加如下两行
HISTTIMEFORMAT="%F %T | "
export HISTTIMEFORMAT

这里需要说明一下,%F %T 等同于 %Y-%m-%d %H:%M:%S,和 date 命令的用法一致,表示的是年月日和时分秒,而 | 竖线加个空格没有其他特殊意义,只是为了作为分隔符,能够更清晰地区分不同的多条命令。

注意事项

当同一用户同时登录多个 bash 时,只有最后一个退出的会写入 bash_history,其他的会被覆盖掉。

神奇的叹号 “!”

执行上一条命令

使用键盘 键可以很快再执行一次上一条命令,与之效果相同的是使用两个叹号, !! 代表了上一条命令,例如:

1
2
3
4
5
6
7
[user1@study ~]$ which head
/bin/head

[user1@study ~]$ !! # 再次执行上一条命令
which head
/bin/head
[user1@study ~]$

当要想操作一个文件,但是漏掉了操作命令,可以这样来补救:

1
2
3
4
5
6
7
8
9
[user1@study ~]$ /etc/issue
-bash: /etc/issue: Permission denied

[user1@study ~]$ cat !!
cat /etc/issue
\S
Kernel \r on an \m

[user1@study ~]$

多数情况下,还是习惯再次从头敲一次命令,但是当命令行的参数比较长(例如很长的文件路径)的时候用上面的方法会显得更高效。

调用上条命令的参数

如果要执行一条很长很长的命令,包含了很多参数,比如在进行源码包编译安装的命令,敲错了一个字母或者多了一个选项,使用退格键删除觉得又慢又很麻烦,这时候可以试试下面的方法。

调用上条命令最后一个参数

在命令行使用 !$!:$ 可以很方便地引用上一条命令的最后一个参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[user1@study ~]$ sudo ifconfig 192.168.127.127 eth1
eth1: Unknown host
ifconfig: `--help gives usage information.

[user1@study ~]$ sudo ifconfig !$ 192.168.127.127
sudo ifconfig eth1 192.168.127.127
[user1@study ~]$

[user1@study ~]$ ls /etc/redhat-release
/etc/redhat-release

[user1@study ~]$ ls -al !$
ls -al /etc/redhat-release
lrwxrwxrwx. 1 root root 14 Jul 2 2015 /etc/redhat-release -> centos-release
[user1@study ~]$

!$ 有着相同功能的还有一个快捷键:Esc + .,当执行一次 Esc + . 的时候,跟 !$ 功能一样,但是在多次重复执行 Esc + . 的时候,则会倒序切换历史命令的最后一个参数。

1
2
3
4
5
6
7
[user1@study ~]$ echo 192.168.1.1
192.168.1.1
[user1@study ~]$ echo 192.168.1.2
192.168.1.2
[user1@study ~]$ echo 192.168.1.3
192.168.1.3
[user1@study ~]$ echo # 在这里重复使用 Esc + . 看看效果吧

使用 !$:p 或者 !:$:p 则只会打印输出上一条命令的最后参数,但不执行。

1
2
[user1@study ~]$ !$:p
192.168.1.3

调用上条命令但剔除最后一个参数

有时候命令行会从别处粘贴一个字符串来操作,但是由于失误多复制了一部分并且执行时出错,此时 !:- 就派上用场了:

1
2
3
4
5
6
7
8
[user1@study ~]$ ip route show match 192.168.127.127 dev
Command line is not complete. Try option "help"

[user1@study ~]$ !:-
ip route show match 192.168.127.127
default via 192.168.127.2 dev eth0
192.168.127.0/24 dev eth0 proto kernel scope link src 192.168.127.159
[user1@study ~]$

使用 !:-:p 则只会打印输出,但不执行。

1
2
3
4
5
6
[user1@study ~]$ ip route show match 192.168.127.127 dev
Command line is not complete. Try option "help"

[user1@study ~]$ !:-:p
ip route show match 192.168.127.127
[user1@study ~]$

调用上条命令第一个参数

如果想引用上一条命令的第一个参数,使用 !^!:^!:1 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[user1@study ~]$ cp /etc/sysconfig/network-scripts/ifcfg-eth0 /tmp/
[user1@study ~]$ cat !:^
cat /etc/sysconfig/network-scripts/ifcfg-eth0
# Generated by parse-kickstart
DEVICE="eth0"
IPV6INIT="yes"
BOOTPROTO="none"
UUID="fec3a443-a913-4a91-afbe-8c3e8b859230"
ONBOOT="yes"
IPADDR=192.168.127.159
NETMASK=255.255.255.0
PREFIX=24
BROADCAST=192.168.127.255
NETWORK=192.168.127.0
GATEWAY=192.168.127.2

[user1@study ~]$ wc -l !:1
wc -l /etc/sysconfig/network-scripts/ifcfg-eth0
12 /etc/sysconfig/network-scripts/ifcfg-eth0
[user1@study ~]$

使用 !^:p!:^:p!:1:p 则只会打印输出,但不执行。

1
2
3
4
5
[user1@study ~]$ echo 192.168.1.3
192.168.1.3
[user1@study ~]$ wc !:^:p
wc 192.168.1.3
[user1@study ~]$

调用上条命令中指定的参数

如果只想用上条命令中某个参数呢,则按照 ![命令名]:[参数号] 的规则即可,并且命令名可以省略。例如:

1
2
3
4
5
6
7
[user1@study ~]$ sudo ip route add 114.114.114.114 via 192.168.127.2 dev eth0

[user1@study ~]$ ip route show match !:4
ip route show match 114.114.114.114
default via 192.168.127.2 dev eth0
114.114.114.114 via 192.168.127.2 dev eth0
[user1@study ~]$

对于 sudo 命令来说 114.114.114.114 是他是它的第 4 个参数。

使用 ![命令名]:[参数号]:p 则只会打印输出,但不执行。

1
2
[user1@study ~]$ ip route show match !:4:p
ip route show match 114.114.114.114

调用上条命令所有参数

如果一条操作命令后面有很多选项和参数,但是我们把命令敲错了,这时候该怎么办呢?此时我们再重新敲对操作命令,使用 !* 或者 !:* 将上一条命令的所有参数引用下来即可:

1
2
3
4
5
6
7
8
9
10
11
[user1@study ~]$ ips route add 192.168.127.128/32 dev 192.168.127.127 dev eth1
-bash: ips: command not found

[user1@study ~]$ ip !*
ip route add 192.168.127.128/32 dev 192.168.127.127 dev eth1
RTNETLINK answers: Operation not permitted

[user1@study ~]$ echo ip !:*
echo ip route add 192.168.127.128/32 dev 192.168.127.127 dev eth1
ip route add 192.168.127.128/32 dev 192.168.127.127 dev eth1
[user1@study ~]$

第一条命令执行失败是因为命令敲错,第二条命令执行失败是因为当前用户是普通用户,权限不允许,但是命令以及参数是没错的。

使用 !*:p 或者 !:*:p 则只会显示出上一条命令的所有参数,但不执行。

1
2
3
4
5
[user1@study ~]$ ips route add 192.168.127.128/32 dev 192.168.127.127 dev eth1
-bash: ips: command not found

[user1@study ~]$ ip !:*:p
ip route add 192.168.127.128/32 dev 192.168.127.127 dev eth1

调用上条以关键字开头的命令

使用 !string 则会执行命令历史中最近一个以指定字符串(string)开头的命令,例如:

1
2
3
4
[user1@study ~]$ !wc
wc -l /etc/sysconfig/network-scripts/ifcfg-eth0
12 /etc/sysconfig/network-scripts/ifcfg-eth0
[user1@study ~]$

使用 !string:p 则只打印输出符合条件的命令,但不执行。

1
2
[user1@study ~]$ !wc:p
wc -l /etc/sysconfig/network-scripts/ifcfg-eth0

替换上条命令的参数

如果上一条命令很长,而且有个参数不小心敲错了该怎么办?使用 ^no^yes 就可以将上条命令中错误的参数 no 改成 yes 并再次执行一次。比如:

1
2
3
4
5
[user1@study ~]$ grp -i 163 /etc/yum.repos.d/CentOS-Base.repo
-bash: grp: command not found
[user1@study ~]$ ^grp^grep
grep -i 163 /etc/yum.repos.d/CentOS-Base.repo
[user1@study ~]$

但是这种方法只能替换上条命令中第一次被匹配到的参数:

1
2
3
4
5
6
[user1@study ~]$ ip route show match 192.168.10.10
default via 192.168.127.2 dev eth0
[user1@study ~]$ ^10^123
ip route show match 192.168.123.10
default via 192.168.127.2 dev eth0
[user1@study ~]$

如果想把上条命令中所有匹配到的参数全部替换可以这样做:

1
2
3
4
5
6
7
[user1@study ~]$ ip route show match 192.168.10.10 
default via 192.168.127.2 dev eth0

[user1@study ~]$ !!:gs/10/123
ip route show match 192.168.123.123
default via 192.168.127.2 dev eth0
[user1@study ~]$

对上条以关键字开头的命令也是可以做替换的,例如将上一条 scp 命令中的所有 10 替换为 123

1
!scp:gs/10/123

逻辑非

列出除了 cfg 结尾以外的所有文件:

1
2
3
4
5
6
7
8
9
10
[user1@study ~]$ ls
test10.cfg test2.cfg test4.cfg test6.cfg test8.cfg
test10.ini test2.ini test4.ini test6.ini test8.ini
test1.cfg test3.cfg test5.cfg test7.cfg test9.cfg
test1.ini test3.ini test5.ini test7.ini test9.ini

[user1@study ~]$ ls !(*.cfg)
test10.ini test2.ini test4.ini test6.ini test8.ini
test1.ini test3.ini test5.ini test7.ini test9.ini
[user1@study ~]$

其他

  • !n:执行历史命令中第 n 条命令。

  • !-n:执行历史命令中倒数第 n 条命令。

  • !?str?:最近一条包含 str 的命令。

  • Ctrl + p:引用上一条命令。

  • Ctrl + n:引用下一条命令。

  • Ctrl + r:按下这个快捷键 ,然后输入关键字搜索历史命令,按 Enter 执行命令。

有钱任性,请我吃包辣条
0%