Bash完整记录用户的历史命令

本篇文章所依赖的环境为:

系统为:CentOS 7.9shell为默认的bash

centos 7.9中,historybash的内置命令,用于显示和管理历史记录的命令,但是在一些特殊的条件下会导致历史命令丢失,比如:

  • 非正常退出shell
  • 在退出shell前命令未保存到历史文件中

为了避免如上情况,又想要记录用户所执行的命令,例如:作为安全审计等。

针对上述问题,可以使用2种方法来解决:

  • 修改HISTFILE环境变量内容来存储日志。
  • 使用PROMPT_COMMAND环境变量来输出历史命令进行存储日志。

history相关的环境变量

在介绍如何保存历史命令之前,需要先了解history的环境变量,在使用histroy命令中,会涉及到一系列相关的环境变量,我们一般将其写到/etc/profile或者是用户的bashrc文件中,即:开机会加载的文件以便重新定义环境变量。

HISTFILE: 历史命令记录的存储文件路径

HISTFILE环境变量保存的是 history命令历史记录 的文件存储路径,若用户为root,则默认为/root/.bash_history,可以通过设置该环境变量来改变该路径。默认情况下,当用户登录机器的时候,history会加载该文件的内容作为历史命令。

HISTFILESIZE: 历史命令的文件最大行数

HISTFILESIZE定义的是历史文件中的最大行数,centos 7系统的默认值为1000,当超过该值后,会迭代删除文件中最早的命令。

HISTSIZE: shell历史会话中的最大行数

HISTSIZE定义的是在shell会话中的最大行数,默认为1000,当超过该值后,会迭代删除会话中最早的命令。

HISTTIMEFORMAT:控制历史命令显示的时间格式

HISTTIMEFORMAT定义的是历史命令显示的时间格式,默认为空,可以设置为date命令所支持的时间变量,例如: %F %T

所展示的效果为

如何保存历史命令

简单根据用户记录历史命令

通过上述history相关环境变量,可以通过根据不同的登录用户从而设置不同的HISTFILE进行历史记录保存,例如修改/etc/profile文件内容,追加如下内容:

bash 复制代码
# tail /etc/profile

HISTTIMEFORMAT="%F %T "
USERID=$(id -u)
HISTFILE="/var/log/${USER}_${USERID}.log"
touch $HISTFILE
chmod 777 $HISTFILE

...

#

上述语句表示的含义是将历史命令显示的时间格式修改为YYYY-mm-dd HH:MM:SS的形式,并且定义历史记录的文件存储路径为/var/log/用户名_用户ID.log,并且创建文件赋777权限。

这样的话,在登录机器后,会在/var/log下产生历史文件的日志,例如:

除此之外,你还可以使用更加细分的逻辑来存储历史命令,比如:根据用户以及来访者的IP地址进行命令存储,只需要修改其环境变量即可,例如修改/etc/profile文件内容:

bash 复制代码
# tail -n 14 /etc/profile
HISTTIMEFORMAT="%F %T "
USERID=$(id -u)
if [ -z "$SSH_CONNECTION" ];then
        CLIENT="localhost"
else
        CLIENT=$(echo $SSH_CONNECTION | awk '{print $1}')
fi
HISTFILE="/var/log/${CLIENT}_${USER}_${USERID}.log"
touch $HISTFILE
chmod 777 $HISTFILE

...

#

这样存储的日志会更加详细,具体路径为:/var/log/客户端IP_用户名_用户ID.log,如果不是通过ssh登录的,就使用localhost代替客户端IP。

这样的好处有很多,可以更加细分的存储日志,查找起来也非常方便,但是也有问题,比如:下一次客户端IP变了后,再进行history查找历史命令,就无法找到此前的命令了,因为HISTFILE存储路径改变了。

使用PROMPT_COMMAND来存储历史命令

PROMPT_COMMANDbash中一个特殊的环境变量,用于定义每个命令提示符显示之前需要执行的操作,例如:

上诉命令将PROMPT_COMMAND设置为pwd,即打印当前路径,所以而后敲击的每条命令结束后,再下一个提示符显示之前都会执行该操作,可以利用此来记录历史命令。

bash中可以直接使用history n来打印最后的n条记录,比如

基于上述,可以在/etc/profile中基于PROMPT_COMMAND来调用存储信息,一般而言可以调用一个钩子,例如:

bash 复制代码
# tail -n 3 /etc/profile
HISTTIMEFORMAT="%F %T "
PROMPT_COMMAND='bash /usr/local/bin/historyTracker "$(history 1 | { read x; echo $x; })"'

#

其中/usr/local/bin/historyTracker是我们自定义的钩子,可以是shell脚本,也可以是python脚本等等,注意调用的方式不同,最重要的是后面的钩子参数是跟的是最后的命令,至于为什么是 $(history 1 | { read x; echo $x; }),这是因为直接使用在PROMPT_COMMAND的时候,调用history命令是在一个新的shell中执行的,会取到空的值,当我们使用{}命令块的时候,会创建一个子shell,这样就避免了该问题。

接下来只需要编辑钩子脚本的内容即可完成了,例如:

bash 复制代码
# cat /usr/local/bin/historyTracker
#!/bin/bash

# 记录历史记录至文件
TIMEDAY=$(date +"%F")
USERID=$(id -u)

if [ -z "$SSH_CONNECTION" ];then
        CLIENT="localhost"
else
        CLIENT=$(echo $SSH_CONNECTION | awk '{print $1}')
fi

readonly HISTORYFILENAME="/var/log/history_${CLIENT}_${USERID}_${TIMEDAY}.log"

echo $* >> $HISTORYFILENAME
#

注意钩子脚本的权限,当然你也可以直接将上诉代码写在/etc/profile中,这样的话,会显得非常凌乱。

使用PROMPT_COMMAND的方式来记录操作记录,不会去修改已有的history环境变量,不会导致用户非正常状态退出命令丢失的情况。

相关推荐
饭来_1 小时前
配置 RDP 远程桌面协议连接ubuntu服务器桌面
linux·运维·服务器
李菠菜1 小时前
Redis主从/哨兵/集群离线部署指南
linux·运维·redis
zyx没烦恼2 小时前
线程池&&单例模式
linux·开发语言·c++·单例模式
古德赖可可2 小时前
linux的例行性工作(at)
linux·运维·服务器
猪猪侠|ZZXia2 小时前
# 家庭网络IPv6地址的一些知识
linux·运维·服务器·网络·智能路由器
前进的程序员3 小时前
CentOS系统中MySQL安装步骤分享
linux·mysql·centos
小王努力学编程3 小时前
【Linux网络编程】应用层协议HTTP(实现一个简单的http服务)
linux·服务器·网络·c++·网络协议·学习·http
AI小小怪3 小时前
Linux下编译并打包MNN项目迁移至其他设备
linux·opencv·mnn·mtcnn
JhonKI4 小时前
【Linux网络】TCP服务中IOService应用与实现
linux·网络·tcp/ip
qq_447429414 小时前
Linux0.11内存管理:相关代码
linux·c语言