首页  /  Linux 进阶  /  正文

Shell 应用之 if 判断与服务存活检测

Linux 进阶 2026-10-02📖 18 分钟👁 —
🐚 Linux 进阶 · Shell 编程第 7 / 10 页12345678910📖 完整导航 →

一、if 的写法

Shell 的 if 和别的语言不太一样,then 是单独拎出来的一段。单分支就是条件成立才干活:

# 单分支(为真就执行代码,为假什么都不做)
if 条件
  then
    代码
fi

简化
if 条件;then
  代码
fi

写在命令行上就是一行的事,注意结尾的 fi 别忘:

ubuntu@VM-0-9-ubuntu:~$ if [ -f "caixuk.jpg" ];then echo "jinitaimei"; fi

嵌套也是可以的:

if <>
  then
    echo ...
  if <>
    then
      ...
  fi
fi


if <>
  then
    echo ...
else
  echo...
fi # 记得加fi

多分支用 elif,注意 if 后面要写 then,elif 不用写;嵌套层数尽量别超过三层,套太深自己都看不懂了:

if <>
  then # if 写then,elif 不需要
    代码1
# elif 嵌套不超3层
elif <>
  then
    代码2
elif <>
  then
    代码3
else
  代码4
fi # 写 fi

条件测试本身有三种写法,效果一模一样,中括号、双中括号和 test 都行:

中括号、双中括号和 test 三种条件写法
if [ -f /etc/hosts ]
  then
    echo yes
fi


if [[ -f /etc/hosts ]]
  then
    echo yes
fi


if test -f /etc/hosts
  then
    echo yes
fi

二、内存检测脚本

先看需求:

  1. 检测 linux 剩余可用内存,比如小于 100M,给运维发邮件
  2. 编写crontab,每三分钟检查一次内存

思路就四步:先拿到内存情况,再配邮件告警把剩余内存告诉出去,然后用 if 判断是不是小于阈值,最后交给 crontab 定时跑。

free -m 以 MB 为单位看内存:

ubuntu@VM-0-9-ubuntu:~$ free -m
#              总内存        使用        剩余              缓冲/缓存     可用剩余
               total        used        free      shared  buff/cache   available
Mem:            7623         624        5694           2        1569        6998
Swap:           1987           0        1987
free -m | awk 'NR==2 {print $NF}'
# NF代代表最后一列

awk 的作用就是单独取出一列,而且分得干干净净,取第几列就写第几列:

# 取 access.log 里的 IP 列(第1列)
awk '{print $1}' /var/log/nginx/access.log

# 取前10行
awk '{print $1}' access.log | head -10

# /etc/passwd 是用 : 分隔的,取用户名
awk -F: '{print $1}' /etc/passwd

# CSV 文件,取第2列
awk -F, '{print $2}' file.csv

# 取第 12 行
awk 'NR==12' 文件名

# 取第 1 行(标题行)
awk 'NR==1' 文件名

脚本长这样。需求里写的是 100M,我这份演示机器的内存大,阈值就随手写成了 7000:

#!/bin/bash

# 定义内存和获取提示内存的变量
FreeMem=`free -m | awk 'NR==2 {print $NF}'`
CHARS="现在内存是$FreeMem"

if [ "$FreeMem" -lt "7000" ]
  then
    echo $CHARS | tee /tmp/messages.txt
    # 发邮件需要mail服务
    # mail -s 主题 收件人 < 发送信息
    # mail -s "`date +%F-%Y`$CHARS" zzz_usr@163.com < /tmp/messages.txt
    # 没有配置这个服务,所以先注释掉,改为echo 输出
    echo "内存不足7000M"
fi

定时任务 crontab

crontab 的任务必须写进文件里才会执行,写完用 crontab 文件名 加载(文件后缀随便写,用 .cron 一眼能看出是定时任务文件):

分 时 日 月 周 命令
 │ │  │  │  │
 │ │  │  │  └── 星期几(0-7,0和7都代表周日)
 │ │  │  └──── 月份(1-12)
 │ │  └────── 日(1-31)
 │ └──────── 小时(0-23)
 └────────── 分钟(0-59)

ubuntu@VM-0-9-ubuntu:~/shell$ vim mem.cron
ubuntu@VM-0-9-ubuntu:~/shell$ cat mem.cron
*/3 * * * * /home/ubuntu/shell/mem.sh &>/dev/null

ubuntu@VM-0-9-ubuntu:~/shell$ crontab -l
no crontab for ubuntu
ubuntu@VM-0-9-ubuntu:~/shell$ crontab mem.cron
ubuntu@VM-0-9-ubuntu:~/shell$ crontab -l
*/3 * * * * /home/ubuntu/shell/mem.sh &>/dev/null

ubuntu@VM-0-9-ubuntu:~/shell$ 

五个星号代表“每”,常用的几种:

* * * * * /path/to/script.sh              # 每分钟一次
5 * * * * /path/to/script.sh              # 每小时的第 5 分钟
0 2 * * * /path/to/script.sh              # 每天凌晨 2 点
0 3 * * 1 /path/to/script.sh              # 每周一凌晨 3 点
0 4 1 * * /path/to/script.sh              # 每月 1 号凌晨 4 点
*/5 * * * * /path/to/script.sh            # 每 5 分钟
0 8-18/2 * * * /path/to/script.sh         # 上午 8 点到下午 6 点每隔 2 小时
0 2 * * * /path/to/script.sh >> /var/log/script.log 2>&1   # 顺手把输出记进日志

三个容易踩的坑:


三、怎么判断服务还活着

先拿两个数字练下手,脚本接收传进来的两个参数,用 -lt、-eq、-gt 比大小:

#!/bin/bash

# 接收传入的两个参数
a=$1
b=$2

if [ "$a" -lt "$b" ] 
  then
    echo " $a < $b "
    exit 0
fi

if [ "$a" -eq "$b" ] 
  then
    echo " $a = $b "
    exit 0
fi

if [ "$a" -gt "$b" ] 
  then
    echo " $a > $b "
    exit 0
fi

ubuntu@VM-0-9-ubuntu:~/shell$ bash ab.sh 3 4
 3 < 4 

同样的意思写成 if-elif-else 更清爽:

#!/bin/bash

# 接收传入的两个参数
a=$1
b=$2

if [ "$a" -lt "$b" ]
  then
    echo "$a less than $b"
elif [ "$a" -eq "$b" ]
  then
    echo "$a equal to $b"
else
    echo "$a greater than $b"
fi

ubuntu@VM-0-9-ubuntu:~/shell$ bash ab.sh 3 4
3 less than 4

判断服务状态其实分好几层,从本机看到远端探、从端口看到进程看到业务,当时梳理的角度大概是这些:

端口监控、进程数、客户端模拟访问、登录数据库四种判断角度

本机看端口常用 netstat、ss、lsof;从远端探本机端口用 telnet、nmap、nc;看进程数就 ps -ef | grep 配合 wc -l;再往上一层就是真的去登录数据库或者访问接口。

netstat 的参数:

参数含义
-tTCP 连接
-uUDP 连接
-n显示 IP 和端口号(不解析域名)
-l只显示正在监听的端口
-p显示占用端口的进程名/PID

先把 mysql 装上,设置好 root 密码:

sudo apt install mysql-server -y
# 设置密码
# 进入 mysql 环境
mysql
# 设置密码(用的是root的密码)
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码';
FLUSH PRIVILEGES;
# exit 退出

# 测试连接 
# 参数 -p 和密码是无空格的,后面不写密码就是交互式输入密码,更安全
mysql -u root -p密码
# 查看端口
root@VM-0-9-ubuntu:~# netstat -tunlp|grep mysql
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      125623/mysqld       
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      125623/mysqld
# 但我们要用端口存活表示mysql还在,这一坨需要优化
root@VM-0-9-ubuntu:~# netstat -tunlp|grep mysql| wc -l
2
# 如果输出2,代表mysql存活

# 使用 ss 命令
root@VM-0-9-ubuntu:~# ss -tunlp|grep mysql| wc -l
2

# 使用 lsof -i tcp:3306 查看3306被谁占用了
root@VM-0-9-ubuntu:~# lsof -i tcp:3306
COMMAND    PID  USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
mysqld  125623 mysql   23u  IPv4 21041127      0t0  TCP localhost:mysql (LISTEN)

# 上面的命令,只要输出不为 0,mysql 服务就存活

netstat 和 ss 其实是一家人,现在的新系统更推荐 ss,netstat 要装 net-tools 才有。

# 安装 nmap 端口扫描软件
# centos的
yum install -y telnet nmap nc
# ubuntu的
apt install -y telnet nmap netcat-openbsd

# 扫
root@VM-0-9-ubuntu:~# nmap 127.0.0.1 -p 3306
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-07-24 21:04 CST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000047s latency).
# 下面是内容
PORT     STATE SERVICE
3306/tcp open  mysql

Nmap done: 1 IP address (1 host up) scanned in 0.04 seconds

# 也可以主动过滤开放的端口
root@VM-0-9-ubuntu:~# nmap 127.0.0.1 -p 3306 | grep open
3306/tcp open  mysql

# 这个命令配合wc输出不为 0 就是存活,否则挂掉
# 用 echo 模拟一次回车输入,然后通过管道传给 telnet
# 让 telnet 自动连接本机的 3306 端口,并立即退出
root@VM-0-9-ubuntu:~# echo -e "\n" | telnet 127.0.0.1 3306
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
[
8.0.46-0ubuntu0.24.04.3
                       WTcV#ÿirTAfB]+caching_sha2_passwordConnection closed by foreign host.

# 判断connected信息,有信息就是连上了(这里默认mysql是3306)
# 注意Connected是大写的 C,linux严格匹配大小写
root@VM-0-9-ubuntu:~# echo -e "\n" | telnet 127.0.0.1 3306 | grep Connect | wc -l
Connection closed by foreign host.
1
root@VM-0-9-ubuntu:~# ps -ef | grep mysql
mysql     125623       1  0 20:49 ?        00:00:03 /usr/sbin/mysqld
root      132892  124675  0 21:18 pts/3    00:00:00 grep --color=auto mysql
# 这里有一个 grep 本身产生的进程,可以去掉
# -v 表示反向匹配(invert-match)
ps -ef | grep mysql | grep -v grep
root@VM-0-9-ubuntu:~# ps -ef | grep mysql | grep -v grep | wc -l
1

四、用 php 和 python 连一下 mysql

端口活着不代表数据库能用,最直接的办法是拿业务代码去连一下,能读到数据就说明没问题。这里准备了 php 和 py 两个程序。

php 连 mysql

先装 mysql,再准备 php 环境的依赖:

centos yum remove -y php-mysql、yum install -y php-mysqlnd php

ubuntu apt remove -y php-mysql、apt install -y php-mysql php

然后写连接代码:

<?php
  $mysql_id=mysql_connect("localhost","root","密码")
    # mysql_connect() 如果连接成功,会返回一个 MySQL 连接标识符(资源类型)
    # 这个值在条件判断中会被视为 true;如果连接失败,会返回 false
    if ($mysql_id) {
      echo "mysql服务运行中"
        }else {
      # 打印报错函数信息
      echo mysql_error();
    }

这个脚本在 PHP 7.0+ 环境下会直接报错,因为 mysql_connect() 和 mysql_error() 已经被彻底移除了,所以真要用得换成 mysqli 的写法,脚本存成 mysql_server.php:

<?php
  $mysql_id = mysqli_connect("localhost", "root", "密码");

if ($mysql_id) {
  echo "mysql服务运行中";
} else {
  echo mysqli_connect_error();
}

跑一下:

root@VM-0-9-ubuntu:~/shell# php mysql_server.php 
  mysql服务运行中root@VM-0-9-ubuntu:~/shell# 

python 连 mysql

先装 python3 的环境依赖:

centos yum install -y python3-devel python3-pip

ubuntu apt install -y python3-dev python3-pip

再用包管理工具装连接 mysql 的模块:

pip3 install pymysql
# Ubuntu 24.04+ 的新保护机制,禁止用系统 pip 装包,怕搞乱系统环境
error: externally-managed-environment
# 但是 pip3 已经是系统自带的了
root@VM-0-9-ubuntu:~/shell# which pip3
/usr/bin/pip3

py 代码数量要严格控制,就这么几行:

import pymysql
# 连接数据库
db = pymysql.connect(
    host="localhost",
    user="root",
    password="密码",
    db="mysql",
    charset="utf8" # 注意这里不是utf-8没有段横杠
)
# 操作数据库
cursor=db.cursor()
cursor.execute("select version()")

data=cursor.fetchone()

print(f"数据库已连接,版本:{data[0]}")
# 或
print("数据库已连接,版本:%s" % data[0])
db.close()

实际跑的时候先报了个缺模块的错,加 --break-system-packages 装上 pymysql 就好了:

root@VM-0-9-ubuntu:~/shell# cat py3_mysql.py 
import pymysql
# 连接数据库
db = pymysql.connect(
    host="localhost",
    user="root",
    password="xxxxx",
    db="mysql",
    charset="utf8"
)
# 操作数据库
cursor=db.cursor()
cursor.execute("select version()")

data=cursor.fetchone()

print(f"数据库已连接,版本:{data[0]}")
db.close()
# 发现报错,少了安装环境
root@VM-0-9-ubuntu:~/shell# python3 py3_mysql.py 
Traceback (most recent call last):
  File "/root/shell/py3_mysql.py", line 1, in <module>
    import pymysql
ModuleNotFoundError: No module named 'pymysql'
root@VM-0-9-ubuntu:~/shell# pip3 list | grep pymysql
root@VM-0-9-ubuntu:~/shell# pip3 install --break-system-packages pymysql

root@VM-0-9-ubuntu:~/shell# python3 py3_mysql.py 
数据库已连接,版本:8.0.46-0ubuntu0.24.04.3

把服务停掉,立马就报错了:

root@VM-0-9-ubuntu:~/shell# systemctl stop mysql
root@VM-0-9-ubuntu:~/shell# python3 py3_mysql.py 
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/dist-packages/pymysql/connections.py", line 682, in connect
    sock = socket.create_connection(
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/socket.py", line 852, in create_connection
    raise exceptions[0]
  File "/usr/lib/python3.12/socket.py", line 837, in create_connection
    sock.connect(sa)
ConnectionRefusedError: [Errno 111] Connection refused

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/root/shell/py3_mysql.py", line 3, in <module>
    db = pymysql.connect(
         ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/pymysql/connections.py", line 372, in __init__
    self.connect()
  File "/usr/local/lib/python3.12/dist-packages/pymysql/connections.py", line 744, in connect
    raise exc
pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on 'localhost' ([Errno 111] Connection refused)")

pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on 'localhost' ([Errno 111] Connection refused)") 这句就是连不上数据库地址的意思。


五、把几种判断凑成一个脚本

前面那些命令一条条敲太累,写进 test_mysql.sh 里,五种办法轮流上一遍:

root@VM-0-9-ubuntu:~/shell# cat test_mysql.sh
#! /bin/bash

echo "---method 1---"
# 返回结果不为0,只能是大于0
if [ `netstat -tunlp | grep mysql |wc -l` -gt "0" ] 
	then
		echo "mysql is running"
else
	echo "mysql is stopped..."
	systemctl enable --now mysql
fi

echo "---method 2---"
if [ `ss -tunlp | grep mysql |wc -l` -gt "0" ]
        then
                echo "mysql is running"
else
        echo "mysql is stopped..."
        systemctl enable --now mysql
fi

echo "---method 3---"
if [ `lsof -i tcp:3306 | grep mysql |wc -l` -gt "0" ]
        then
                echo "mysql is running"
else
        echo "mysql is stopped..."
        systemctl enable --now mysql
fi
# 使用php脚本
# 执行脚本根据返回值判断是否存活
echo "---method 4---"
php /root/shell/mysql_server.php
if [ $? -eq 0 ]
        then
                echo "mysql is running"
else
        echo "mysql is stopped..."
        systemctl enable --now mysql
fi

# 使用py脚本
# 现在 Method 4 和 5 的写法对了,但 Method 5 有一个隐患
# $? 取的是 python3 的返回值
# 但中间隔了一个 echo "---method 5---",导致 $? 被覆盖
# 先保存返回值即可(推荐),把 echo 放到 if 里面也可以
python3 /root/shell/py3_mysql.py
PYTHON_EXIT=$?

echo "---method 5---"
if [ $PYTHON_EXIT -eq 0 ]; then
    echo "mysql is running"
else
    echo "mysql is stopped..."
    systemctl enable --now mysql
fi
# 修改文件太多,不想删除可以使用 EOF 定界符
cat > /root/shell/test_mysql.sh <<'EOF'
内容
EOF

五种办法都通过:

# 输出结果
root@VM-0-9-ubuntu:~/shell# bash test_mysql.sh 
---method 1---
mysql is running
---method 2---
mysql is running
---method 3---
mysql is running
---method 4---
mysql服务运行中mysql is running
---method 5---
数据库已连接,版本:8.0.46-0ubuntu0.24.04.3
mysql is running