已复制
全屏展示
复制代码

历史记录命令history与终端环境变量总结


· 13 min read

一. 终端环境变量

在 linux 终端,有几个非常重要的环境变量:PS1, PS2, PS3, PS4, PROMPT_COMMAND,这些环境变量控制了我们的终端的行为和显示。

PS1

PS1表示终端交互提示,比如[\u@\h \W]\$,它的显示结果为[work@node01 ~]$,这也是我们通常看到的命令行提示符,PS1里面可以放置任何想执行的命令。如果想查看结果,直接打开命令行,给PS1赋值即可。

# 显示当前时间
export PS1="[\u@\h \W <\$(date +%H:%M:%S)>]$ "


# 显示上一条命令的返回码
export PS1="[\u@\h \W <\$?>]$ "


# 使用函数,在函数里面定义任何想使用的命令。
$ function nginxcount() { ps aux | grep nginx | grep -v grep | wc -l; }
$ export PS1="[\u@\h \W <\$(nginxcount)>]$ "
[cs@aniu scripts <4>]$


# 提示字符颜色
#     \e[   颜色设置开始
#     x;ym  颜色代码
#     \e[m  颜色设置结束
export PS1="\e[0;31m\u@\h \w$ \e[m "
颜色代码(使用 1 替换 0 就是暗色了):
    Black  0;30
    Red    0;31
    Green  0;32
    Brown  0;33
    Blue   0;34
    Purple 0;35
    Cyan   0;36


# 提示背景颜色
#     \e[   颜色设置开始
#     xm    颜色代码
#     \e[m  颜色设置结束
export PS1="\e[44m\u@\h \w$ \e[m "
# 背景色代码常用范围
    40 黑色
    41 紅色
    42 綠色
    43 黃色
    44 藍色
    45 紫紅色
    46 青藍色
    47 白色


# 提示字符颜色、背景颜色同时设置
export PS1="\e[0;34m\e[42m\u@\h \w$ \e[m "


# 使用 tput 设置颜色
export PS1="\[$(tput setab 6)$(tput setaf 3)\]\u@\h \w$\[$(tput sgr0)\] "

说明:
    设置颜色时使用\[...\]来包裹
    tput setab:设置背景颜色
    tput setaf:设置字体颜色
    颜色结束时必须用tput sgr0关闭颜色属性

tput颜色代码:
    0 – Black 黑色
    1 – Red 红色
    2 – Green 绿色
    3 – Yellow 黄色
    4 – Blue 蓝色
    5 – Magenta 品红色
    6 – Cyan 青色
    7 - White 白色
  • 内置可用的设置
\a         - ASCII bell 字符 (07)
\d         - 日期, 格式:Weekday Month Date 格式 (比如: “Tue May 26”)
\D{format} - 自定义时间格式,比如 PS1="\D{%4Y-%m-%d %H:%M:%S}"
\e         - ASCII escape(转译)字符
\h         - 主机名第一段
\H         - 主机名
\j         - 当前 shell 管理的任务数量
\l         - 终端设备简称
\n         - 换行
\r         - 回车
\s         - 执行脚本的简称, 即 $0 的简称
\t         - 当前时间 24小时制 HH:MM:SS 格式
\T         - 当前时间 12小时制 HH:MM:SS 格式
\@         - 当前时间 12小时制 am/pm 格式
\A         - 当前时间 24小时制 HH:MM 格式
\u         - 当前用户的用户名
\v         - 当前 bash 的版本号 (例如:2.00)
\V         - 当前 bash 版本号+补丁版本号 (如:2.00.0)
\w         - 当前的工作目录, 如果是 $HOME 目录, 则显示为 ~
\W         - 当前工作目录简称, $HOME 目录展示为 ~
\!         - history 命令记录的数量
\#         - 当前命令行的行号
\$         - 好像是区分是否为 root 用户的, root 用户为 #, 其他用户为 $
\nnn       - 8进制数 nnn 对应的字符 
\\         - 反斜线
\[         - 一串非打印的字符, 可以嵌入终端控制顺序到提示中
\]         - 非打印字符的结束处
  • 我的终端PS1变量
export PS1="\e[35;1m[\e[0m\e[32;1m\u@\h \e[0m\e[34;1m\W\e[0m\e[35;1m]\e[0m\e[31;1m \e[0m"

PS2

PS2表示连续交互式提示,一个命令可以使用\结尾 链接多行的方式进行书写,而每行默认的提示符则为> , 这里我们可以修改PS2continue->进行修改

[yzy@aniu ~]$ echo 1 \
> 2 \
> 3 \
> 4 \
> 
1 2 3 4


[yzy@aniu ~]$ export PS2="continue-> "
[yzy@aniu ~]$ echo 1 \
continue-> 2 \
continue-> 3 \
continue-> 4 \
continue-> 
1 2 3 4

PS3

PS3表示选择命令提示符,在编写交互式代码的时候很管用,用于和用户交互,代码和执行结果如下,这里#?是默认的提示标识。

  • select.sh  默认的效果
#!bin/bash
select i in mon tue wed exit
do
    case $i in
        mon) echo "Monday";;
        tue) echo "Tuesday";;
        wed) echo "Wednesday";;
        exit) exit;;
    esac
done

[yzy@yzym ~]$ bash select.sh 
1) mon
2) tue
3) wed
4) exit
#? 2
Tuesday
#? 3
Wednesday
#? 4
  • select.sh 自定义 PS3 的效果
#!bin/bash
PS3="select a day (1-4): "
select i in mon tue wed exit
do
    case $i in
        mon) echo "Monday";;
        tue) echo "Tuesday";;
        wed) echo "Wednesday";;
        exit) exit;;
    esac
done

[yzy@yzym ~]$ bash select.sh
1) mon
2) tue
3) wed
4) exit
select a day (1-4): 2
Tuesday
select a day (1-4): 3
Wednesday
select a day (1-4): 4

PS4

PS4表示前缀跟踪输出提示,仅当 shell 脚本使用 debug 模式(set -x)时的显示提示。这里的示例有两个脚本(debugmode.sh example.sh),目的是更好的查看脚本的执行情况。

  • debugmode.sh脚本内容
#!/bin/bash
export PS4='$0[$LINENO]++ '  # 默认的PS4的值为 +
[[ "$DEBUG" = "1" ]] && set -x
cd /
cd -
bash example.sh
  • example.sh脚本内容
#!/bin/bash
[[ "$DEBUG" = "1" ]] && set -x
echo "This example.sh file"
  • 执行结果如下,在每个脚本的开头增加一行[[ "$DEBUG" = "1" ]] && set -x是个好习惯,在开启debug模式以后可以更清新的看到执行情况。
[yzy@yzym tmpy]$ ll
total 8
-rw-rw-r-- 1 yzy yzy 97 Jun 17 09:14 debugmode.sh
-rw-rw-r-- 1 yzy yzy 71 Jun 17 09:14 example.sh


[yzy@yzym tmpy]$ 
[yzy@yzym tmpy]$ bash debugmode.sh 
/home/yzy/tmpy
This example.sh file


[yzy@yzym tmpy]$ 
[yzy@yzym tmpy]$ DEBUG=1 bash debugmode.sh
debugmode.sh[4]++ cd /
debugmode.sh[5]++ cd -
/home/yzy/tmpy
debugmode.sh[6]++ bash example.sh
example.sh[3]++ echo 'This example.sh file'
This example.sh file

PROMPT_COMMAND

在命令行中,每执行完一个命令就会执行PROMPT_COMMAND环境变量的内容,它的执行时机为:敲完回车执行命令,命令执行完毕返回(正确或错误)以后,下一个命令行提示符出现以前。基于这个执行时机,可以有两方面的应用。

  • 显示系统时间
# 这样一来,每次执行完一个命令以后,都会打印当前的系统时间。
# 在命令行提示符的前一行提示时间
[yzy@yzym tmpy]$ export PROMPT_COMMAND="date '+%Y-%m-%d %H:%M:%S'"
2023-06-17 09:27:33
[yzy@yzym tmpy]$ pwd
/home/yzy/tmpy
2023-06-17 09:27:43
[yzy@yzym tmpy]$ pwd
/home/yzy/tmpy
2023-06-17 09:27:45
  • 保存历史命令,这里是简化的版本,详细的内容在后文讨论。

# 最外层的{ }表示一个匿名函数,匿名函数的输出重定向到了/tmp/command.log里面。
# 在这个匿名函数里面,有多个命令,为了能在同一行展示,每个命令的输出都不换行。
# read _ cmd表示读取history 1的标准输出作为标准输入,丢弃掉第一个字段,把第二个字段输出。
# 这个示例并不完美,后文有更完整的示例
  
PROMPT_COMMAND='{ echo -n $(date "+%Y-%m-%d %H:%M:%S"); echo -n "$(pwd) "; history 1 | { read _ cmd; echo $cmd; }; } >> /tmp/command.log'

二. history命令配置总结

环境变量

和 history 命令相关的环境变量主要有下面zhe'x

● HISTFILE
历史记录所在的文件,默认是~/.bash_history。
不想保存历史信息时,可以unset这个变量。
命令行不是实时写入该文件的,只有在 session 正常退出时才会写入,
也就是说假如我连接的终端因为网络断了,失去了连接,这时命令行是不会写入文件的


● HISTFILESIZE
设置 HISTFILE 文件里可以保存多少条记录。


● HISTSIZE
设置一个 shell session 可以存多少条历史记录,超过该值以后,会把旧的命令删除
在session正常退出时会把这些命令写入 HISTFILE 指定的文件中


● HISTIGNORE
保存历史时忽略某些命令,
例如export HISTIGNORE="ls*:[ ]*:exit",Pattern之间用:分隔
  ○ ls*: 忽略ls开头命令
  ○ [ ]*: 忽略以空格开头的历史记录
  ○ exit: 忽略exit命令。


● HISTCONTROL
和HISTIGNORE类似,也是用来忽略某些历史记录。多个值用:分隔HISTCONTROL=ignoredups:erasedups
  ○ ignoredups:忽略连续重复的命令。
  ○ ignorespace:忽略以空白字符开头的命令。
  ○ ignoreboth:同时忽略重复和空白开头的命令。
  ○ erasedups:忽略所有历史命令中的重复的命令。


● HISTCMD
下一个命令在历史记录中的index号。


● HISTTIMEFORMAT
可以在 history 的输出中显示历史记录的时间戳,注意:history的时间戳只有当设置了HISTTIMEFORMAT环境变量了以后,新加的命令才有正确的时间戳。
比如
export HISTTIMEFORMAT="%F %T "
export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "

bash选项

用于配置是否保留历史记录

# 打开这个选项:会保存历史记录
set -o history

# 关闭这个选项,关闭记录以后,不会保存到当前session里面
# 也不会保存任何命令 到 HISTFILE,
# 但是可以通过 BASH_COMMAND 强制保存命令,后面会有详细说明
set +o history

history命令

history         # 列出所有历史记录
history 3       # 列出最近3条历史记录

history -c      # 清除当前shell session的历史记录,不会影响HISTFILE
history -d 3    # 删除编号为3的记录

history -w      # 用当前session的记录来覆盖HISTFILE
history -a      # 把当前session启动后产生新产生的命令append到HISTFILE里

history -r      # 读取HISTFILE中的历史记录,把它们append到当前shell的记录中


# 把"cmd arg1"保存到当前shell的历史中;不执行命令,就可以保留历史记录
history -s "cmd arg1"


# 在多个shell直接共享历史命令,缺点是会误操作(基本不会这么用)
export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"

事件提示符(Event Designators)

事件提示符用于执行历史命令。

!!        # 执行上一个命令,和!-1等效
!-1       # 执行上一个命令
!-2       # 执行上上个命令
!3        # 执行编号为3的历史记录
!xyz      # 执行以xyz开头的最近执行过的命令
!?xyz?    # 执行含有xyz的最近执行过的命令


# 默认情况下,输入!!回车后会立即执行,假如我想对命令做些修改,则使用 histverify
# histverify 用作执行命令时回显到命令行,等待确认命令无误以后或者修改以后执行。
shopt histverify     # 查看是否开启 histverify,默认是关闭的
shopt -s histverify  # 开启histverify,开启后指定命令行会回显到命令行,并不执行
shopt -u histverify  # 关闭histverify

单词提示符(Word Designators)

Word designators跟在一个event designators后面,它们之间以:分隔,主要用于提取某条命令的某个参数,在编写脚本的时候很有用。


!!:2   # 提取上一个命令的第2个参数展示在命令行,并不回车执行它。
!6:p   # 提取编号为6的命令,但不执行只会打印,然后按上键编辑命令行,然后执行
!!:0   # 提取上一个命令的名字
!!:3-5 # 提取第3,4,5个参数,参数编号从0开始。
!!:$   # 提取最后一个参数
!!:^   # 提取第一个参数
!!:*   # 提取所有参数,和!!:1-$效果一样
!!:3*  # 等效于3-$
!!:3-  # 提取第3个到倒数第二个

vim编辑上一条命令

你有没有遇到这样的情况,输入了很长的一串命令行,在不断的调整参数、不断的运行,在命令行调整参数非常尴尬,左右键调整很麻烦,这时可以借助 fc 命令使用vim来编辑命令并执行了

# 使用vim编辑上一条命令,编辑完成以后保存退出即刻执行
fc

# 或者显示指定编辑器
fc -e vim


# 假如你编辑了上一条命令,但是不想执行了,默认情况下只要退出 vim 编辑器就会执行的
# 解决办法: 
# 	Ctrl+z 让这个编辑进入后台
# 	然后 fg 把这个后台的vim编辑任务放到前台

三. history命令静默保存

假如你想把在服务器的任何命令都保存下来,并且不允许任何人修改(root除外),有两种方法。

3.1 方法一 使用 PROMPT_COMMAND

可以使用前文提到的 PROMPT_COMMAND 环境变量来实现,新建shell_log.sh脚本,用于定义保存的命令行格式,配置过程使用 root 用户配置。

这种方式的缺点是:因为它依赖history命令,所以一旦运行 set +o history 关闭历史命令,这种方法将会失效,但是依然会看到 set +o history 这条命令。

  • /etc/shell_log.sh 定义如何把命令行格式化
#!/bin/bash

HISTTIMEFORMAT=""
HISTIGNORE=""
HISTCONTROL=""
readonly HISTTIMEFORMAT
readonly HISTIGNORE
readonly HISTCONTROL

before_last_num="-1"
check_command(){
    code=$?
    filepath=/var/log/shell/shell.log
    last_cmd="$(echo $(history 1) | { read _ cmd; echo $cmd; })"
    last_num="$(echo $(history 1) | { read num _; echo $num; })"
    if [ ! -z "$last_cmd" -a "$before_last_num" != "-1" -a "$last_num" != "$before_last_num" ];then
        datetime="$(date '+%Y-%m-%d.%H:%M:%S')"
        remoteip="$(who -u am i | tr -s ' ' | tr -d '()' | awk '{print $7}')"
        localip="$(echo $(hostname -I) | sed -e 's/ /|/g')"
        tty="$(who -u am i | tr -s ' ' | tr -d '()' | awk '{print $2}')"
        process="$(who -u am i | tr -s ' ' | tr -d '()' | awk '{print $6}')"
        username="$(who -u am i | tr -s ' ' | tr -d '()' | awk '{print $1}')"
        [ -z "$remoteip" ] && remoteip="null"
        [ -z "$localip" ] && localip="null"
        [ -z "$tty" ] && tty="null"
        [ -z "$process" ] && process="null"
        [ -z "$username" ] && username="null"
        echo "$datetime $remoteip $(hostname) $localip $tty $process $username $(pwd) $code $last_cmd" >> $filepath
    fi
    before_last_num="$last_num"
}
export PROMPT_COMMAND=check_command
readonly PROMPT_COMMAND

  • 配置文件权限,让登录用户自动执行,这样一来,任何用户只要登录服务器,所有的命令都会被记录下来,并且普通用户无法修改文件属性。
# 不允许修改
chattr +i /etc/shell_log.sh

# 编辑 /ect/profile 把下面命令放在里面
source /etc/shell_log.sh

# 创建保存命令的文件
mkdir /var/log/shell
touch /var/log/shell/shell.log

# 所有人都有写权限、只允许追加
chmod a+w /var/log/shell/shell.log
chattr +a /var/log/shell/shell.log

3.2 方法二 使用BASH_COMMAND变量

如果用户使用了 set +o history ,方法一就失效了,为了避免用户使用这种方法,可以使用 BASH_COMMAND

  • /etc/shell_log.sh 定义命令行格式化,只不过不依赖 PROMPT_COMMAND 了
#!/bin/bash

check_command(){
    filepath=/var/log/shell/shell.log
    datetime="$(date '+%Y-%m-%d.%H:%M:%S')"
    remoteip="$(who -u am i | tr -s ' ' | tr -d '()' | awk '{print $7}')"
    localip="$(echo $(hostname -I) | sed -e 's/ /|/g')"
    tty="$(who -u am i | tr -s ' ' | tr -d '()' | awk '{print $2}')"
    process="$(who -u am i | tr -s ' ' | tr -d '()' | awk '{print $6}')"
    username="$(who -u am i | tr -s ' ' | tr -d '()' | awk '{print $1}')"
    [ -z "$remoteip" ] && remoteip="null"
    [ -z "$localip" ] && localip="null"
    [ -z "$tty" ] && tty="null"
    [ -z "$process" ] && process="null"
    [ -z "$username" ] && username="null"
    echo "$datetime $remoteip $(hostname) $localip $tty $process $username $(pwd) $BASH_COMMAND" >> $filepath
}

# when execute a cmd this func will be executed
trap 'check_command' DEBUG

  • 配置文件权限,同上
# 不允许修改
chattr +i /etc/shell_log.sh

# 编辑 /ect/profile 把下面命令放在里面
source /etc/shell_log.sh

# 创建保存命令的文件
mkdir /var/log/shell
touch /var/log/shell/shell.log

# 所有人都有写权限、只允许追加
chmod a+w /var/log/shell/shell.log
chattr +a /var/log/shell/shell.log

文章推荐