menu Chancel's blog
rss_feed
Chancel's blog
有善始者实繁,能克终者盖寡。

Linux下简易的系统备份脚本构建

作者:Chancel Yang, 创建:2019-08-30, 字数:6388, 已阅:730, 最后更新:2023-07-29

这篇文章更新于 135 天前,文中部分信息可能失效,请自行甄别无效内容。

服务器上的备份脚本是好几年前写的,前段时间发现不工作了

最近也打算重新看一下Bash Shell脚本的语法,就趁着这个机会复习下

unix shell

unix shell是一个命令行解释器或者说是shell,为类Unix操作系统提供一个命令行用户界面。这个shell既是一种交互式的命令语言,也是一种脚本语言,操作系统通过使用shell脚本来控制系统的执行

shell既是交互式命令语言又是脚本语言,操作系统使用它来控制使用shell脚本执行系统,用户通常使用终端仿真器与Unix shell进行交互;

通过串行硬件连接或Secure Shell直接操作对于服务器系统是常见的,所有Unix shell都提供文件名通配符、管道、这里的文档、命令替换、变量和控制结构、于条件测试和迭代

听起来很像python,Shell目前主要有两大主流流派

  • SH
    • burne shell(sh)
    • burne again shell(bash)
  • csh
    • c shell(csh)
    • tc shell(tcsh)
    • korn shell(ksh)

大部分Linux发行版都是预设bash环境,bash是shell的一种

shell是一种统称符合其语法规则语言的简称

语法

基础

bash的语法不复杂,对于有编程语言使用经验的人来说阅读一遍往往就记住了,网上的简中资料介绍太累赘了

因此,下面直接将一些语法点整理出来,以供参考

首先,bash的脚本一般有开头标记来告诉环境需要什么脚本解释器,linux下script大部分情况下都是由bash解释器来执行的,一个输出Hello World的脚本如下

编辑hello.sh文件,内容如下

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

赋予权限,并执行

chmod +x hello.sh
# 执行方法1. 使用指定解释器执行
/bin/bash hello.sh
# 执行方法2. 因为文件开头已经写了执行器类型,直接调用(./指当前目录,如有不理解请自行搜索)
./hello.sh

变量

继续编辑hello.sh文件,内容如下

#!/bin/bash

var_hello_str='Hello World!'
# 使用1. 不带花括号
echo $pick_hero 
# 使用2. 带花括号(推荐写法)
echo ${pick_hero}

变量命名规则与大部分编程语言类似

  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头
  • 中间不能有空格,可以使用下划线(_)
  • 不能使用标点符号
  • 不能使用bash里的关键字

以下是一些常用的Bash保留关键字:

  1. if, then, else, elif, fi:这些关键字用于条件判断。
  2. for, do, done:这些关键字用于循环。
  3. while, until:这些关键字也用于循环。
  4. case, esac:这些关键字用于多路选择。
  5. function:定义函数。
  6. select:用于生成菜单。
  7. time:用于计算命令或程序运行时间。
  8. {}:用于命令组。
  9. [[ ]]:用于条件表达式。
  10. (( )):用于算术运算。
  11. !:用于逻辑非。
  12. in:用于for循环中指定一个列表。
  13. breakcontinue:分别用于跳出循环和跳过当前循环迭代。
  14. return:用于从函数中返回。
  15. exit:用于退出脚本。

以上是一些常见的Bash保留关键字,但并不全面,具体的关键字和用法可以查阅Bash的官方文档。

下面是变量其他写法一览

#!/bin/bash

# 定义并设置只读变量,只读变量不能被重新定义或删除
readonly my_name="chancel"

# 拼接字符串
str01="hello"
str02="world"
echo "${str01} ${str02}" # 用空格拼接两个字符串

# 定义数组
myArray=("x" "y" "z")
echo "${myArray[0]}" #取出第一个元素
echo "${myArray[*]}" # 取出所有元素
echo "${myArray[@]}" # 取出所有元素

# 遍历数组
for var in "${myArray[@]}"; do
    echo "$var"
done

# 遍历数组并打印索引和元素
for i in "${!myArray[@]}"; do
    printf "%s\t%s\n" "$i" "${myArray[$i]}"
done

# 使用while循环遍历数组
i=0
while [ $i -lt ${#myArray[@]} ]; do
    echo "${myArray[$i]}"
    ((i++))
done

参数

我们经常使用下面这种方法调用脚本

./xswl.sh -who chancel -why cxk

这种做法一般需要检查用户输入,下面是一个完整的方法

#!/bin/bash -e
show_help() {
    echo "$0 [-h|-?|--help] [--who me] [--why hhh]"
    echo "-h|-?|--help    显示帮助"
    echo "--who           输入你是谁"
    echo "--why           为什么笑"
}

while [[ $# -gt 0 ]]; do
    case $1 in
    -h | -\? | --help)
        show_help
        exit 0
        ;;
    --who)
        WHO="${2}"
        shift
        ;;
    --why)
        WHY="${2}"
        shift
        ;;
    *)
        echo -e "Error: $0 invalid option '$1'\nTry '$0 --help' for more information.\n" >&2
        exit -1
        ;;
    esac
    shift
done

调用效果

$ ./xswl.sh -h
./xswl.sh [-h|-?|--help] [--who me] [--why hhh]
-h|-?|--help    显示帮助
--who           输入你是谁
--why           为什么笑

if else

bash是如何控制流程的,参考下面的例子 if-else规则

if condition
then
    command1 
    command2
    ...
    commandN 
fi

if-elseif-else

if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi

一个简单的例子

#!/bin/bash -e
x=100
y=200
if [ $x == $y]
then
    echo "x = y"
elif [ $x -gt $y ]
then
    echo "x > y"
else
    echo "x < y"

运算符

上面涉及到运算符,bash的运算符相对简单

参数 说明
-eq =
-ne !=
-gt >
-ge >=
-lt =<
-le <=
-z(字符串) 字符串长度为零
-n(字符串) 字符串长度不为零

下面是例子

#!/bin/bash

# 加减乘除运算
x=100
y=200
$z=$[x+y] #这语法有点奇怪,且实测等号两侧不可空格(写惯了c#可能很不习惯这种写法)
echo $z

# 字符串判断
x="hello"
y="hallo"
if test $x = $y
then
    echo "字符串内容相同"
else
    echo "字符串内容不同"
fi

到这写一个最基础的服务器备份脚本知识点基本都齐了,如果对其他语法点还感兴趣,可以参考下面进行学习

bash中文手册Handbook - 时光小栈

日常常见的语法还有下面这些

  • printf
  • function
  • 输入输出重定向
  • 脚本引入
  • 关于文件的运算符

备份脚本

我的需求主要有以下2点

  • 备份MySQL中除去自带数据库外的所有数据库
  • 备份/opt目录下的所有程序

程序思路

  • 创建/tmp/_backup文件夹
  • 导出数据库文件到/tmp/_backup/mysql_backup文件夹
  • 压缩指定文件夹与数据库备份文件夹到/tmp/_backup/日期.tar.gz
  • 删掉目标文件夹的旧备份并移动备份文件到目标文件夹(比如Sycnthing与我的Nas同步的文件夹下)
  • 删掉/tmp/_backup文件夹

于是经过上面的学习以及备份思路,你可以得到以下这份脚本,里面的所有知识点基本都在上面提到了,只有date函数没有提到(这个比较复杂,请自行搜索相关资料参阅)

#!/bin/bash
# author:chancel
# url:www.chancel.me

show_help() {
    echo "$0 [-h|-?|--help] [--temp /tmp/_backup] [--target /opt/backup/] [--dbuser root] [--dbpasswd passwd] [--extra /opt]"
    echo "-h|-?|--help    显示帮助"
    echo "--temp          设置备份文件时的缓存目录"
    echo "--target        设置备份文件的目标(存放)路径"
    echo "--dbuser        设置mysql数据库用户名称"
    echo "--dbpasswd      设置mysql数据库用户密码"
    echo "--extra         额外需要备份的目录,以空格分开不同目录"
}

while [[ $# -gt 0 ]]; do
    case $1 in
    -h | -\? | --help)
        show_help
        exit 0
        ;;
    --temp)
        temp="${2}"
        shift
        ;;
    --target)
        target="${2}"
        shift
        ;;
    --dbuser)
        dbuser="${2}"
        shift
        ;;
    --dbpasswd)
        dbpasswd="${2}"
        shift
        ;;
    --extra)
        extra="${2}"
        shift
        ;;
    --)
        shift
        break
        ;;
    *)
        echo -e "错误: $0 无效操作 '$1'\n可输入命令 '$0 --help' 获取更多帮助.\n" >&2
        exit -1
        ;;
    esac
    shift
done

echo "创建必要的文件夹"
mysql_backup_dir=$temp'/mysql_backup'
mkdir -p $mysql_backup_dir
new_backup_file_path=$temp"/`date +%Y%m%d`.tar.gz"
old_backup_file_path=$target"/`date -d -1day +%Y%m%d`.tar.gz"

echo "备份mysql数据库"
databases=`mysql -u$dbuser -p$dbpasswd -e "SHOW DATABASES;" | tr -d "| " | grep -v Database`
for db in $databases; do
    if [[ "$db" != "sentry" ]] && [[ "$db" != "information_schema" ]] && [[ "$db" != "performance_schema" ]] && [[ "$db" != "mysql" ]] && [[ "$db" != _* ]] ; then
        echo "正在导出数据库$db"
        mysqldump -u$dbuser -p$dbpasswd --databases $db > $mysql_backup_dir'/'`date +%Y%m%d`-$db.sql
    fi
done


echo "正在删除上一次产生的备份包"
rm -f $old_backup_file_path

echo "创建备份文件"$new_backup_file_path
tar -zcvf $new_backup_file_path $mysql_backup_dir $extra
mv $new_backup_file_path $target

echo "删除临时文件夹"$temp
rm -rf $temp
echo "备份结束"

参考这份文档,根据你的需要自定修改你就可以快速得到一份备份脚本


[[replyMessage== null?"发表评论":"发表评论 @ " + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageResponse.total]])

还没有可以显示的留言...
[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[getEnviron(messageItem.m_environ)]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[getEnviron(messageItem.m_environ)]]