Shell和自动化

为什么用Shell ?

很简单,你不能保证随时都在吧。DBA 使用 Shell 主要是为了 自动化 + 批量化 + 监控化,能够减少手工操作,避免出错,定时执行重复任务,批量管理多个数据库实例,快速处理系统日志与资源分析,以及作为更大自动化系统(如 Ansible、Cron、CI/CD)的底层脚本。

主要应用场景:

  • 数据库日常维护
  • 数据库监控与报警
  • 性能分析与资源管理
  • 批量操作与集群管理
  • 与 SQL 脚本结合执行

Shell基础

Shell的运行

这里所说的Shell通常都是指 shell 脚本,这里使用的是bash
扩展名并不影响脚本执行:

1
2
#!/bin/bash 
echo "Hello World !"

#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。

运行 Shell 脚本有两种方法:

  1. 作为可执行程序
    将上面的代码保存为 test.sh,并 cd 到相应目录:

    1
    2
    chmod +x ./test.sh  #使脚本具有执行权限
    ./test.sh #执行脚本
  2. 作为解释器参数
    这种运行方式是,直接运行解释器,不需要具有可执行权限,其参数就是 shell 脚本的文件名,如:

    1
    2
    3
    /bin/sh test.sh

    bash test.sh

    这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

Shell 变量

变量命名

等号两侧避免使用空格:

1
variable_name=value

除了显式地直接赋值,还可以用语句给变量赋值,如:

1
2
3
4
# 将 /etc 下目录的文件名循环出来。
for file in `ls /etc`

for file in $(ls /etc)

变量使用

使用一个定义过的变量,只要在变量名前面加美元符号即可,如:

1
2
3
4
5
6
7
your_name="qinjx"
echo $your_name
# 或
echo ${your_name}
# 花括号作用是识别边界
skill="use"
echo "I am good at ${skill}Script"

只读变量

只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

1
2
3
4
#!/bin/bash

myUrl="https://www.google.com"
readonly myUrl

删除变量

使用 unset 命令可以删除变量。语法:

1
unset variable_name

变量被删除后不能再次使用。unset 命令不能删除只读变量。

变量类型

  1. 字符串变量
    Shell中,变量通常被视为字符串。
    可以使用单引号 ‘ 或双引号 “ 来定义字符串,例如:
    1
    2
    3
    my_string='Hello, World!'
    # 或者
    my_string="Hello, World!"

获取字符串长度

1
2
string="abcd"
echo ${#string} # 输出 4
  1. 整形变量
    可以使用 declare 或 typeset 命令来声明整数变量。
    这样的变量只包含整数值,例如:

    1
    declare -i my_integer=42

    这样的声明告诉 Shellmy_integer 视为整数,如果尝试将非整数值赋给它,Shell会尝试将其转换为整数。

  2. 数组变量
    Shell 也支持数组,允许你在一个变量中存储多个值。
    数组可以是整数索引数组或关联数组,例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 整数数组
    my_array=(1 2 3 4 5)

    # 关联数组(键值对):
    # 创建一个关联数组site
    declare -A site=(["google"]="www.google.com" ["runoob"]="www.runoob.com" ["taobao"]="www.taobao.com")
    我们也可以先声明一个关联数组,然后再设置键和值:
    declare -A site
    site["google"]="www.google.com"
    site["runoob"]="www.runoob.com"
    site["taobao"]="www.taobao.com"

    读取数组

    1
    2
    3
    4
    5
    6
    # 使用下标读取
    valuen=${array_name[n]}
    # 使用 @ 符号可以获取数组中的所有元素,例如:
    echo ${array_name[@]}
    # 取得数组元素的个数
    length=${#array_name[@]}
  3. 环境变量
    这些是由操作系统或用户设置的特殊变量,用于配置 Shell 的行为和影响其执行环境。
    例如,PATH 变量包含了操作系统搜索可执行文件的路径:

    1
    echo $PATH

Shell 传递参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为 $n,n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数。

例如可以使用 $1$2 等来引用传递给脚本的参数,其中 $1 表示第一个参数,$2 表示第二个参数,依此类推。此外,$0 表示脚本的名称,$# 表示传递给脚本的参数数量, $? 表示上一个命令的退出状态等。

1
2
3
4
5
6
#!/bin/bash

echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";

传递参数并运行脚本后得到的结果:

1
2
3
4
5
6
./test.sh 1 2 3
Shell 传递参数实例!
执行的文件名:./test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3

Shell 基本运算符

数学运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awkexprexpr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

1
2
3
4
#!/bin/bash

val=`expr 2 + 2` #注意是反引号` ,表达式和运算符之间要有空格,必须写成 2 + 2
echo "两数之和为 : $val"

自增、自减:

1
2
let num++
let num--

test 命令

Shell 中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试,用于评估表达式并返回布尔值(真/假),它通常与 if 语句结合使用.
语法格式:

1
2
3
test EXPRESSION
# 或
[ EXPRESSION ] # 注意方括号内必须有空格

示例:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

file="/etc/passwd"

if [ -e "$file" ]; then #注意[ 与 ] 两边 必须有空格!!!
echo "$file 存在"
if [ -r "$file" ]; then
echo "并且可读"
fi
else
echo "$file 不存在"
fi

Shell 流程控制

if-else

语法:

1
2
3
4
5
6
7
8
9
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a=10
b=20
if [ $a == $b ]
then
echo "a 等于 b"
elif [ $a -gt $b ] # [...] 判断语句中大于使用 -gt,小于使用 -lt
then
echo "a 大于 b"
elif (($a < $b)) # 使用 ((...)) 作为判断语句,大于和小于可以直接使用 > 和 <。
then
echo "a 小于 b"
else
echo "没有符合的条件"
fi

for

循环的一般格式为:

1
2
3
4
5
6
7
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done

示例:

1
2
3
4
5
6
#!/bin/bash

for str in This is a string
do
echo $str
done

while

语法:

1
2
3
4
while condition
do
command
done

示例:

1
2
3
4
5
6
7
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done

case … esac

case ... esac 为多选择语句,与其他语言中的 switch ... case 语句类似,是一种多分支选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case ... esac 语句,esac(就是 case 反过来)作为结束标记。
语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
casein
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac

Shell 函数

可以直接 fun() 定义,不带任何参数。
参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。return 语句只能返回一个介于 0255 之间的整数.
下面的例子定义了一个函数并进行调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 函数定义
demoFun(){
echo "输入第一个数字: "
read aNum
echo "输入第二个数字: "
read anotherNum
echo "两个数字分别为 $aNum$anotherNum !"
return $(($aNum+$anotherNum))
}
#函数定义的另一种写法
function demoFun{
...
}

# 函数调用
# 调用时 不需要加括号,直接写函数名和参数即可
demoFun
echo "输入的数之和为:$?"

函数返回值在调用该函数后通过 $? 来获得

Shell 输入输出重定向

默认情况下标准输入和输出均是终端。
可以通过重定向命令更改输出和输入的位置:

1
2
3
4
command > file  #输出重定向到file
command >> file #将输出以追加的方式重定向到 file。(不会覆盖掉原来的信息)
command < file #输入重定向
command < file

需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

Shell 文件包含(文件调用)

和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
Shell 文件包含的语法格式如下:

1
2
3
4
5
. filename   # 注意点号(.)和文件名中间有一空格



source filename

示例:
test1.sh 代码如下:

1
2
3
#!/bin/bash

url="http://www.google.com"

test2.sh 代码如下:

1
2
3
4
5
6
7
8
9
#!/bin/bash

#使用 . 号来引用test1.sh 文件
. ./test1.sh

# 或者使用以下包含文件代码
# source ./test1.sh

echo "google地址:$url"

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null

1
$ command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出”的效果。


Shell 的使用案例

场景1

检测 PostgreSQL 是否在运行

1
2
3
4
5
6
7
#!/bin/bash
if pg_isready -h localhost -p 5432 > /dev/null 2>&1; then
echo "PostgreSQL 正在运行"
else
echo "PostgreSQL 未启动,尝试重启..."
systemctl restart postgresql
fi

把这段脚本放入 cron 中,每 5 分钟执行一次,就能实现自动“守护数据库”。

场景2

自动备份数据库并清理旧文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
DB=mydb
BACKUP_DIR=/var/backups/pgsql
DATE=$(date +%Y%m%d) # 获取当前日期

# 创建目录(如果不存在)
mkdir -p "$BACKUP_DIR"

# 执行备份
pg_dump -U postgres -Fc "$DB" > "${BACKUP_DIR}/${DB}_${DATE}.dump"

# 检查是否备份成功
if [ $? -eq 0 ]; then
echo "$(date '+%F %T') - 备份成功: ${DB}_${DATE}.dump"
else
echo "$(date '+%F %T') - 备份失败!" >&2
fi

# 清理7天前的旧备份
find "$BACKUP_DIR" -type f -mtime +7 -delete

自动化备份与清理,配合定时任务,实现数据库备份。

场景3

监控数据库连接数并报警

1
2
3
4
5
6
7
8
9
#!/bin/bash
THRESHOLD=80
CURRENT=$(psql -U postgres -d mydb -t -c "SELECT count(*) FROM pg_stat_activity;")

if (( CURRENT > THRESHOLD )); then
echo "连接数过高:$CURRENT" | mail -s "DB Connection Alert" [email protected]
else
echo "连接正常:$CURRENT"
fi

实时监控连接数,超过阈值时发邮件报警。

场景4

批量检查集群节点状态

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
NODES=("db1" "db2" "db3")

for host in "${NODES[@]}"; do
if pg_isready -h "$host" -p 5432 > /dev/null 2>&1; then
echo "$host 正常"
else
echo "$host 不可用"
fi
done

管理集群时快速查看每个节点是否存活。

场景5

检测磁盘空间并发出告警

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
THRESHOLD=80
# df / 查看根分区使用情况,awk 提取第 2 行的第 5 列(使用率),tr -d '%' 去掉百分号
USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')

if (( USAGE > THRESHOLD )); then
echo "磁盘使用率已达 ${USAGE}%" | mail -s "Disk Alert" [email protected]
else
echo "磁盘使用率正常:${USAGE}%"
fi

监控数据库服务器磁盘使用,防止磁盘写满导致数据库停机

  • Copyrights © 2023-2025 Hexo

请我喝杯咖啡吧~

支付宝
微信