历史记录命令history与终端环境变量总结
一. 终端环境变量
在 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
表示连续交互式提示,一个命令可以使用\
结尾 链接多行的方式进行书写,而每行默认的提示符则为>
, 这里我们可以修改PS2
为 continue->
进行修改
[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