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

Linux下自动备份系统脚本指南

作者:E4b9a6, 创建:2024-07-22, 字数:6448, 已阅:484, 最后更新:2024-07-22

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

服务器上的备份脚本是好几年前写的,前段时间发现有一些小问题,正好趁这个机会重写一下顺便回顾以下Bash语法

以下内容是从头搜集整理的Bash语法基础,若有错误欢迎指出

1. shell

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

shell是一个命令行解释器,为类Unix操作系统提供一个命令行用户界面

命令行用户界面也称之为Command Line Interface(CLI

在计算机领域,shell通常代指Bash是因为Bash在原始的shell(burne again shell,即SH)程序上增强了许多功能,提供了现代操作系统的非常多基础设施如环境变量、进程管理等

所以通常说shell时都会代指Bash,除了Bash外,现在也有许多其他shell如Cshell、Kshell等

Linux发行版都支持sh以及bash环境,无需安装,下面是Bash语法入门

2. 语法

Bash的语法简单,但毕竟是一门较老的语言了,在许多特点上是比较反直觉的,下面直接将一些语法点整理出来,以供参考

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

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

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

赋予权限,并执行,你就可以得到一个输出:"Hello World"

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

2.1. 变量

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

Bash
#!/bin/Bash

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

变量命名规则如下:

  • 只能使用英文字母,数字和下划线且首字符不能以数字开头
  • 中间不能有空格,不能使用标点符号
  • 不能使用保留关键字

Bash保留关键字如下

类型 关键字
判断 if、then、else、elif、fi
循环 for、do、done、while、until、break、continue、in
选择 case、esac
函数 function、return、exit
其他 select、time、{}、[[]]、(())、!

保留字相对比较多,更具体可以参考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

2.2. 参数

在写脚本时传递参数是很常见的做法:

Bash
./demo.sh -name chancel

接收参数并声明为变量:

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

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

使用效果

Bash
$ ./demo.sh -h
./demo.sh [-h|-?|--help] [--who me] [--why hhh]
-h|-?|--help    显示帮助
--who           输入你是谁

2.3. if else

控制流程方法如下:

Bash
# if写法
if condition
then
    command1 
    command2
    ...
    commandN 
fi

# if-else写法
if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi

参考示例:

Bash
#!/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"

2.4. 运算符

Bash的运算符比较简单,拢共如下几个

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

运算符例子如下:

Bash
#!/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

2.5. 其他常见语法

除以上语法外,常见的语法还包括:

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

例子如下:

Bash
#!/bin/bash

# 定义一个函数
greet() {
    printf "Hello, %s!\n" "$1"
}

# 输入输出重定向
ls > files.txt

# 调用函数并传递参数
greet "John"

# 判断文件是否存在
if [ -f "files.txt" ]; then
    printf "文件files.txt存在\n"
else
    printf "文件files.txt不存在\n"
fi

# 引入其他脚本文件
source helper.sh

# 调用引入的函数并传递参数
greet "Alice"

3. 备份脚本

我的需求主要有以下2点

  • 备份MySQL中的所有数据库
  • 备份/opt目录下的所有程序

基于以上语法,可实现脚本如下:

Bash
#!/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         额外需要备份的目录,以空格分开不同目录"
}

create_folders() {
    echo "创建必要的文件夹"
    mkdir -p "$mysql_backup_dir"
}

backup_mysql_databases() {
    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
}

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

create_backup_file() {
    echo "创建备份文件 $new_backup_file_path"
    tar -zcvf "$new_backup_file_path" "$mysql_backup_dir" $extra
    mv "$new_backup_file_path" "$target"
}

delete_temp_folder() {
    echo "删除临时文件夹 $temp"
    rm -rf "$temp"
    echo "备份结束"
}

# 解析命令行参数
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

# 设置变量
mysql_backup_dir="$temp/mysql_backup"
new_backup_file_path="$temp/$(date +%Y%m%d).tar.gz"
old_backup_file_path="$target/$(date -d -1day +%Y%m%d).tar.gz"

# 执行备份操作
create_folders
backup_mysql_databases
delete_previous_backup
create_backup_file
delete_temp_folder

4. 尾语

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


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

account_circle
email
web_asset
textsms

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

还没有可以显示的留言...
gravatar
[[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)]]