前言
最近有个需求是监控linux系统的一些指标,存到文件里面,要求是每10s监控一次指标,每小时换一次文件,最多保留24小时。并且可以作为守护进程。
直接与命令打交道的那肯定是shell脚本莫属。正好之前没有写过shell脚本,于是开始研究。
shell
Shell 这个单词的原意是“外壳”,跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的对话界面。
Shell 有很多种,只要能给用户提供命令行环境的程序,都可以看作是 Shell。
历史上,主要的 Shell 有下面这些。
- Bourne Shell(sh)
- Bourne Again shell(bash)
- C Shell(csh)
- TENEX C Shell(tcsh)
- Korn shell(ksh)
- Z Shell(zsh)
- Friendly Interactive Shell(fish)
Bash 是目前最常用的 Shell, linux 中默认的shell 也是 Bash, 所以我用来编程的也是Bash
下面的命令可以查看当前设备的默认 Shell。
$ echo $SHELL
/bin/bash
一般来说,ps命令结果的倒数第二行是当前 Shell。
$ ps
PID TTY TIME CMD
4467 pts/0 00:00:00 bash
5379 pts/0 00:00:00 ps
下面的命令可以查看当前的 Linux 系统安装的所有 Shell。$ cat /etc/shells
第一个 Shell 脚本
编写
打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell)
很多ide都有相关bash的插件,例如语法检查,格式化等,具体可以根据自己使用的ide下载插件
在创建 shell 脚本文件时,必须在文件的第一行指定要使用的 shell:
#!/bin/bash
一般在 shell 脚本中,井号(#)用作注释行,除了第一行是例外。
指定了shell后,就可以编写了。如果我们只是想要执行一些简单的命令,那么其实和我们在终端直接输出命令是一样的
例如:
# 执行命令
echo "Hello World !"
运行
1. 作为可执行程序
将上面的代码保存为 test.sh,
chmod +x ./test.sh #使脚本具有执行权限
./test.sh #执行脚本
直接写成 test.sh 是不行的,因为该脚本并没有加入环境变量 PATH,
或者我们可以这么运行,直接运行解释器
/bin/sh test.sh
这种方式运行的脚本,不需要在第一行指定shell
守护进程化
守护进程化意味着进程与终端分离
一般可以使用 setid 命令。
setsid 可以创建一个新的会话(session)并在此会话中运行指定命令的命令,这个命令意味着:
- 进程不再是当前终端的一部分,因此它不受当前终端的任何键盘输入影响;
- 进程也不会接收到当前终端产生的SIGHUP信号;
忽略SIGHUP信号可以使得当前进程脱离终端
当一个终端退出时,会向该终端下的所有进程发送 SIGHUP 信号,以通知它们终端已经关闭。默认情况下,大多数进程会在收到 SIGHUP 信号后退出,这样就可以避免“孤儿进程”(父进程结束,子进程仍在运行)的出现。
另外,我们还希望进程在后台运行,即 shell不等待该脚本执行完毕后才继续接收新的命令,表示守护进程化后,我们终端可以继续输入别的命令。
总之,我们的命令如下
setsid $0 &
$0 表示当前正在执行的脚本的名称。表示当前脚本的路径和名称。
&: 这个符号将命令放入后台执行,即使终端关闭,命令也会继续执行,而不会等待命令执行完毕。
同时,我们还希望,守护进程化是可选的,例如,我们执行命令的时候 加参数 -d,则可以守护进程化,反之亦然。
执行脚本的参数可以用 $@ 符号获取,或者我们可以用 getopts
#!/bin/bash
DAEMON_MODE=false
for param in "$@"
do
case $param in
-d)
DAEMON_MODE=true
;;
*)
echo "Unsupported parameter: $param"
exit 1
;;
esac #结束标识
done
if [ "$DAEMON_MODE" = true ]; then
setsid $0 &
exit 0
while true
do
# 脚本逻辑
sleep 10
done
exit 0
至此,我们就可以使用-d参数了
输出文件
我们一般会使用一个文件来存放日志输出
代码如下:
# 定义日志文件路径
log_file="/var/log/daemon.log"
exec &>> $LOGFILE
exec 可以重定向标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)
&>> 是组合重定向操作符,它表示将标准输出(文件描述符stdout,默认编号1)和标准错误输出(文件描述符stderr,默认编号2)合并后追加到目标文件
例如,我们 echo "hello world"
, 那么这个信息就会被记录到文件里
文件管理
需求是每小时换一次日志文件,最多保留24小时,并且可以进行文件压缩
首先是每小时换一次日志文件:
LOG_DIR="/var/log/directory"
LOG_BASENAME="system_monitor"
# 计算当前日志文件名
LOG_FILE="$LOG_DIR/$LOG_BASENAME.$(date +'%Y%m%d-%H').log"
# 函数:切换日志文件
manage_log_file() {
CURRENT_HOUR=$(date +%H)
NEW_LOG_FILE="$LOG_DIR/$LOG_BASENAME.$(date +'%Y%m%d-%H').log"
# 如果当前小时对应的日志文件与当前日志文件不同,则切换
if [[ "$LOG_FILE" != "$NEW_LOG_FILE" ]]; then
exec 1>>"$NEW_LOG_FILE" 2>&1
LOG_FILE="$NEW_LOG_FILE"
fi
}
最多保留24小时:
可以使用 find命令找出指定的文件,再用-delete删除
`MAX_DAYS=1`
# 清理超过1天的日志文件
find "$LOG_DIR" -name "$LOG_BASENAME.*.log" -type f -mtime +$MAX_DAYS -delete
压缩文件:
很简单,用gzip压缩就行
gzip "$LOG_FILE"
完整代码
#!/bin/bash
DAEMON_MODE=false
LOG_DIR="/var/log/directory"
LOG_BASENAME="system_monitor"
`MAX_DAYS=1`
# 计算当前日志文件名
LOG_FILE="$LOG_DIR/$LOG_BASENAME.$(date +'%Y%m%d-%H').log"
for param in "$@"
do
case $param in
-d)
DAEMON_MODE=true
;;
*)
echo "Unsupported parameter: $param"
exit 1
;;
esac #结束标识
done
if [ "$DAEMON_MODE" = true ]; then
setsid $0 &
exit 0
# 函数:切换日志文件
manage_log_file() {
# 清理超过1天的日志文件
find "$LOG_DIR" -name "$LOG_BASENAME.*.log" -type f -mtime +$MAX_DAYS -delete
CURRENT_HOUR=$(date +%H)
NEW_LOG_FILE="$LOG_DIR/$LOG_BASENAME.$(date +'%Y%m%d-%H').log"
# 如果当前小时对应的日志文件与当前日志文件不同,则切换
if [[ "$LOG_FILE" != "$NEW_LOG_FILE" ]]; then
# 压缩文件
gzip "$LOG_FILE"
exec 1>>"$NEW_LOG_FILE" 2>&1
LOG_FILE="$NEW_LOG_FILE"
fi
}
while true
do
manage_log_file
# 脚本逻辑
sleep 10
done
exit 0
遇到的坑
一般我们都会认为 0 是 false , 1是 true。
而在shell中则比较复杂,命令语句返回0表示成功,例如我们使用 exit 0 表示正常退出
首先,bash里 条件判断是这么用:
if [ -f "file.txt" ]; then
echo "file.txt 存在"
fi
方括号[]通常用于条件测试
先看 if [ A ]的情况
1 如果 A 是个数字,则 if [ A ] 始终为真,
if [ 1 ] ; then
echo "1 is true"
else
echo "1 is false"
fi
if [ 0 ] ; then
echo "0 is true"
else
echo "0 is false"
fi
结果:
1 is true
0 is true
2 如果 A 是个表达式,则用 A 的返回值也就是状态码,来决定 if [ A ] 的真假,不过当状态码为 0 时 if [ A ] 为真,当状态码为非 0 时 if [ A ] 为假
if [ 5 -eq 5 ] ; then
echo "$? is true"
else
echo "$? is false"
fi
if [ 5 -eq 6 ] ; then
echo "$? is true"
else
echo "$? is false"
fi
结果:
0 is true
1 is false
状态码含义如下:
状态码 | 描述 |
---|---|
0 | 命令成功结束 |
1 | 通用未知错误 |
2 | 误用 Shell 命令 |
126 | 命令不可执行 |
127 | 没找到命令 |
128 | 无效退出参数 |
128+n | Linux 中信号为 n 的严重错误 |
130 | 通过 Ctrl + C 退出 |
255* | 退出状态码越界 |
参考:
https://blog.csdn.net/lyndon_li/article/details/120735191
https://www.w3cschool.cn/linux/linux-shell.html
https://blog.csdn.net/w918589859/article/details/108752592
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。