这个命令是干啥的
刚做运维那会儿,我有个坏习惯:执行过的命令全靠脑子记。后来次数多了,每次找之前执行过的一条命令,就疯狂按上箭头,一下、两下、十几下...有时候按过了还得再按回来。特别是一天下来敲了几百条命令的时候,想找回20分钟前执行的那个复杂sed替换,能按到手抽筋。
history 命令就是解决这个问题的。它会把你当前shell里执行过的所有命令都记录下来,还能让你通过简单的数字编号或者关键字搜索,快速找到并重新执行之前用过的命令。
说白了,这就是你的命令行时光机。
基本用法(3分钟上手)
查看历史记录
直接敲 history 就能看到所有记录:
# 查看历史命令列表,前面带编号
history
输出长这样:
1001 ls -la
1002 cd /var/log
1003 tail -f syslog
1004 vim /etc/nginx/nginx.conf
1005 systemctl restart nginx
通过编号重复执行
看到编号了吧?想重新执行第1003条,不用再敲一遍:
# 重复执行编号为1003的历史命令
!1003
执行上一条命令
# 重复执行上一条命令
!!
这个超级实用。比如你 sudo apt update 完了发现忘了加sudo:
# 先用普通用户执行,发现权限不够
apt update
# 直接用sudo重复上一条
sudo !!
使用上一条命令的最后一个参数
# 引用上一条命令的最后一个参数
!$
场景:你先 cat /etc/nginx/sites-available/default 看了配置,然后想vim编辑它:
# 先看文件内容
cat /etc/nginx/sites-available/default
# 再用vim打开同一个文件,!$ 就是上一条命令的最后一个参数
vim !$
限制显示条数
# 只看最近20条
history 20
进阶骚操作
Ctrl+R 反向搜索
这是我用最多的功能。当你记不清某条命令的具体内容,只记得里面有个关键词:
按 Ctrl + R,然后输入关键词,shell会自动匹配最近一条包含这个关键词的命令。
(reverse-i-search)`nginx': vim /etc/nginx/nginx.conf
看到匹配的命令后,按回车就直接执行。如果想换一条更早的,继续按 Ctrl + R 切换。
给历史命令加时间戳
默认的 history 只显示编号和命令,不显示时间。想看到每条命令是什么时候执行的,需要设个环境变量:
# 给历史命令加上时间戳,格式为 年-月-日 时:分:秒
export HISTTIMEFORMAT='%F %T '
然后 history 输出就变成:
1001 2024-01-15 09:23:45 ls -la
1002 2024-01-15 09:24:01 cd /var/log
如果想让这个生效持久化,把这行加到 ~/.bashrc 里。
搜索历史命令
除了 Ctrl+R,还可以用 grep 搭配 history:
# 搜索包含 docker 的所有历史命令
history | grep docker
# 搜索 git 相关的命令,忽略大小写
history | grep -i git
控制历史记录的大小
# 设置历史文件最多保留 10000 条命令
export HISTFILESIZE=10000
# 设置当前会话最多记录 5000 条命令
export HISTSIZE=5000
这两个变量不一样:
- HISTSIZE:当前会话在内存中保留多少条
- HISTFILESIZE:~/.bash_history 文件里保存多少条
不记录某些命令
有些命令我不想进历史记录,比如密码相关的:
# 以空格开头的命令不记录到历史
export HISTCONTROL=ignorespace
这样你敲 mysql -u root -p (注意前面有个空格),这条命令就不会出现在 history 里。
更狠一点,指定某些命令永远不记录:
# 不记录 exit、ls、fg、bg 这些无用命令
export HISTIGNORE='exit:ls:fg:bg:history:clear'
清除历史记录
# 清除当前会话所有记录
history -c
# 把清除同步到历史文件
history -w
避坑指南
坑1:多终端覆盖问题
踩过最惨的一个坑:开了两个终端,A终端敲了半天,退出后历史命令正常保存。然后B终端退出,把A终端的记录全覆盖了。
原因:默认情况下,shell退出时覆盖写入 ~/.bash_history,而不是追加。
解决办法,加到 ~/.bashrc:
# 让每个命令执行后立即追加到历史文件,而不是退出时才写入
PROMPT_COMMAND="history -a; $PROMPT_COMMAND"
# 退出时合并历史记录,而不是覆盖
shopt -s histappend
加了这两行之后,多终端就互不干扰了。
坑2:历史文件被撑爆
有次我在生产服务器上查问题,狂敲了几万条命令排查。结果一看 ~/.bash_history 已经几十MB了。倒不是说多大影响,但 history 命令明显卡了。
解决办法就是合理设置 HISTFILESIZE,别舍不得删。
坑3:敏感命令泄漏
直接在命令行里输密码(比如 mysql -u root -p123456),这条命令会明码保存在 ~/.bash_history。解决方法:
1. 永远不要在命令里直接写密码
2. 万一写了,赶紧 history -c 清掉
3. 或者设 HISTCONTROL=ignorespace,前面加空格
坑4:! 命令在脚本里的问题
写shell脚本时,千万别用 !! 和 !$。脚本里 ! 有特殊含义,会被转义成历史扩展,轻则报错,重则执行了意料之外的命令。
实战场景(重点!结合真实运维场景)
场景1:事故复盘
生产环境出问题了,需要排查谁在什么时间执行了什么操作。
# 先给历史命令加上时间戳
export HISTTIMEFORMAT='%F %T '
# 查看某段时间内的操作
history | grep "2024-01-15 09:2"
# 配合 sudo 日志,基本能还原事故现场
我一般还会把所有人的 .bash_history 集中搜集到日志服务器,配合 HISTTIMEFORMAT 做安全审计。
场景2:快速恢复复杂命令
比如之前调试容器网络时,花了很多时间拼出一条复杂的 iptables 规则:
# 搜索历史中的iptables命令
history | grep iptables
# 找到编号后直接执行
!888
如果这条命令在好几个会话之前执行的,甚至昨天执行的,上箭头根本翻不到。这时候 Ctrl+R 或者 history | grep 就救命了。
场景3:快速复用长路径
# 第一次敲长路径
cd /var/www/project/logs/nginx/access/
# 查看这个目录
ls -la !$
# 压缩它
tar -czf backup.tar.gz !$
场景4:批量重复执行
有次需要重启10台服务器上的某个服务,在历史里找到了重启命令:
# 找到编号
history | grep restart
# 在跳板机上循环执行
for i in server{01..10}; do
ssh $i "!123" # !123 就是那条 restart 命令
done
今日作业
小练习:执行以下几条命令后,用 history 配合 ! 操作符,在不重新输入的情况下重建一个目录结构。
mkdir -p /tmp/test-project
cd /tmp/test-project
touch README.md
touch index.html
ls -la
要求:
1. 用 !! 重复执行 ls -la
2. 用 !$ 新建一个 test.txt 文件在同一个目录
3. 用 history | grep 找到 mkdir 命令的编号,然后用 !编号 执行它
做完之后,运行 history | tail -10 截图看看你用了哪些技巧。