最后更新于:2026年7月

如果你在 Linux 上重复执行同一个操作超过 3 次,就该写脚本了。 Shell 脚本是运维自动化的基石——不需要安装额外环境,不需要编译,写完就能跑。从简单的日志清理到复杂的服务部署,Shell 脚本都能胜任。
但很多人写 Shell 脚本全靠复制粘贴,遇到引号转义就懵,脚本出错不知道怎么调试,更不知道 set -euo pipefail 这种安全选项。
本文从基础语法到生产级脚本,带你全面掌握 Shell 自动化运维。
一、Shell 脚本基础
1.1 什么是 Shell?
Shell 是用户与 Linux 内核之间的接口,你输入的命令都由 Shell 解释执行。
常见 Shell:
| Shell | 说明 | 路径 |
|---|---|---|
| Bash | 最常用,Linux 默认 | /bin/bash |
| Zsh | macOS 默认,功能更丰富 | /bin/zsh |
| Fish | 用户友好,不支持 POSIX | /usr/bin/fish |
| Sh | POSIX Shell,最基础 | /bin/sh |
| Dash | 轻量,Debian 默认 sh | /usr/bin/dash |
本文以 Bash 为主,因为它是 Linux 最通用的 Shell。
1.2 第一个脚本
BASH
#!/bin/bash
# 这是一个注释
echo "Hello, World!"
echo "当前用户: $(whoami)"
echo "当前目录: $(pwd)"
echo "系统时间: $(date '+%Y-%m-%d %H:%M:%S')"#!/bin/bash
# 这是一个注释
echo "Hello, World!"
echo "当前用户: $(whoami)"
echo "当前目录: $(pwd)"
echo "系统时间: $(date '+%Y-%m-%d %H:%M:%S')"
运行脚本:
BASH
# 方式1:添加执行权限
chmod +x script.sh
./script.sh
# 方式2:直接用 bash 执行
bash script.sh
# 方式3:source 执行(在当前 Shell 中运行)
source script.sh
# 或
. script.sh# 方式1:添加执行权限
chmod +x script.sh
./script.sh
# 方式2:直接用 bash 执行
bash script.sh
# 方式3:source 执行(在当前 Shell 中运行)
source script.sh
# 或
. script.sh
Shebang(#!)说明:
BASH
#!/bin/bash # 用 bash 执行
#!/usr/bin/env bash # 更通用的写法(推荐)
#!/bin/sh # 用 sh 执行(POSIX 兼容)
#!/usr/bin/env python3 # 用 python3 执行#!/bin/bash # 用 bash 执行
#!/usr/bin/env bash # 更通用的写法(推荐)
#!/bin/sh # 用 sh 执行(POSIX 兼容)
#!/usr/bin/env python3 # 用 python3 执行
1.3 脚本安全选项
生产级脚本必须加的安全头:
BASH
#!/usr/bin/env bash
set -euo pipefail
# 解释:
# set -e → 遇到错误立即退出
# set -u → 使用未定义变量时报错
# set -o pipefail → 管道中任一命令失败则整个失败#!/usr/bin/env bash
set -euo pipefail
# 解释:
# set -e → 遇到错误立即退出
# set -u → 使用未定义变量时报错
# set -o pipefail → 管道中任一命令失败则整个失败
更严格的调试模式:
BASH
#!/usr/bin/env bash
set -euo pipefail
# 调试模式(打印每条执行的命令)
# set -x # 开启调试
# set +x # 关闭调试
# 或在运行时开启
# bash -x script.sh#!/usr/bin/env bash
set -euo pipefail
# 调试模式(打印每条执行的命令)
# set -x # 开启调试
# set +x # 关闭调试
# 或在运行时开启
# bash -x script.sh
二、变量与数据类型
2.1 变量定义
BASH
#!/usr/bin/env bash
# 基本变量(等号两边不能有空格!)
name="Alice"
age=30
PI=3.14159
# 使用变量(推荐加花括号)
echo "姓名: ${name}"
echo "年龄: ${age}"
# 只读变量
readonly VERSION="1.0.0"
# VERSION="2.0.0" # 报错
# 删除变量
unset temp_var#!/usr/bin/env bash
# 基本变量(等号两边不能有空格!)
name="Alice"
age=30
PI=3.14159
# 使用变量(推荐加花括号)
echo "姓名: ${name}"
echo "年龄: ${age}"
# 只读变量
readonly VERSION="1.0.0"
# VERSION="2.0.0" # 报错
# 删除变量
unset temp_var
2.2 字符串操作
BASH
#!/usr/bin/env bash
str="Hello, World!"
# 字符串长度
echo ${#str} # 13
# 子字符串(从位置0开始,取5个字符)
echo ${str:0:5} # Hello
# 从位置7开始到末尾
echo ${str:7} # World!
# 字符串替换(替换第一个)
echo ${str/World/Shell} # Hello, Shell!
# 字符串替换(替换所有)
str2="a,b,c,d"
echo ${str2//,/-} # a-b-c-d
# 删除前缀
file="archive.tar.gz"
echo ${file#*.} # tar.gz(删除最短匹配前缀)
echo ${file##*.} # gz(删除最长匹配前缀)
# 删除后缀
echo ${file%.*} # archive.tar(删除最短匹配后缀)
echo ${file%%.*} # archive(删除最长匹配后缀)
# 大小写转换
echo ${str,,} # hello, world!(转小写)
echo ${str^^} # HELLO, WORLD!(转大写)#!/usr/bin/env bash
str="Hello, World!"
# 字符串长度
echo ${#str} # 13
# 子字符串(从位置0开始,取5个字符)
echo ${str:0:5} # Hello
# 从位置7开始到末尾
echo ${str:7} # World!
# 字符串替换(替换第一个)
echo ${str/World/Shell} # Hello, Shell!
# 字符串替换(替换所有)
str2="a,b,c,d"
echo ${str2//,/-} # a-b-c-d
# 删除前缀
file="archive.tar.gz"
echo ${file#*.} # tar.gz(删除最短匹配前缀)
echo ${file##*.} # gz(删除最长匹配前缀)
# 删除后缀
echo ${file%.*} # archive.tar(删除最短匹配后缀)
echo ${file%%.*} # archive(删除最长匹配后缀)
# 大小写转换
echo ${str,,} # hello, world!(转小写)
echo ${str^^} # HELLO, WORLD!(转大写)
2.3 特殊变量
| 变量 | 说明 | 示例 |
|---|---|---|
$0 | 脚本名 | ./script.sh |
$1~$9 | 位置参数 | 第1~9个参数 |
$# | 参数个数 | 3 |
$@ | 所有参数(各自独立) | "a" "b" "c" |
$* | 所有参数(合为一个字符串) | "a b c" |
$? | 上一条命令的退出码 | 0(成功) |
$$ | 当前脚本 PID | 12345 |
$! | 最近后台进程 PID | 12346 |
$_ | 上一条命令的最后一个参数 | - |
BASH
#!/usr/bin/env bash
echo "脚本名: $0"
echo "参数个数: $#"
echo "所有参数: $@"
echo "第一个参数: $1"
echo "第二个参数: $2"
# 运行:./script.sh foo bar baz
# 脚本名: ./script.sh
# 参数个数: 3
# 所有参数: foo bar baz
# 第一个参数: foo
# 第二个参数: bar#!/usr/bin/env bash
echo "脚本名: $0"
echo "参数个数: $#"
echo "所有参数: $@"
echo "第一个参数: $1"
echo "第二个参数: $2"
# 运行:./script.sh foo bar baz
# 脚本名: ./script.sh
# 参数个数: 3
# 所有参数: foo bar baz
# 第一个参数: foo
# 第二个参数: bar
2.4 数组
BASH
#!/usr/bin/env bash
# 定义数组
fruits=("apple" "banana" "cherry" "date")
# 访问元素
echo ${fruits[0]} # apple
echo ${fruits[2]} # cherry
# 所有元素
echo ${fruits[@]} # apple banana cherry date
# 数组长度
echo ${#fruits[@]} # 4
# 遍历数组
for fruit in "${fruits[@]}"; do
echo "水果: $fruit"
done
# 追加元素
fruits+=("elderberry")
# 删除元素
unset fruits[1] # 删除 banana
# 关联数组(Bash 4+)
declare -A config
config[host]="localhost"
config[port]="8080"
config[debug]="true"
echo "主机: ${config[host]}"
echo "端口: ${config[port]}"
# 遍历关联数组
for key in "${!config[@]}"; do
echo "${key} = ${config[$key]}"
done#!/usr/bin/env bash
# 定义数组
fruits=("apple" "banana" "cherry" "date")
# 访问元素
echo ${fruits[0]} # apple
echo ${fruits[2]} # cherry
# 所有元素
echo ${fruits[@]} # apple banana cherry date
# 数组长度
echo ${#fruits[@]} # 4
# 遍历数组
for fruit in "${fruits[@]}"; do
echo "水果: $fruit"
done
# 追加元素
fruits+=("elderberry")
# 删除元素
unset fruits[1] # 删除 banana
# 关联数组(Bash 4+)
declare -A config
config[host]="localhost"
config[port]="8080"
config[debug]="true"
echo "主机: ${config[host]}"
echo "端口: ${config[port]}"
# 遍历关联数组
for key in "${!config[@]}"; do
echo "${key} = ${config[$key]}"
done
三、条件判断
3.1 if 语句
BASH
#!/usr/bin/env bash
# 基本 if
if [ "$1" == "hello" ]; then
echo "你好!"
fi
# if-else
if [ $# -eq 0 ]; then
echo "没有提供参数"
else
echo "参数个数: $#"
fi
# if-elif-else
if [ "$1" == "start" ]; then
echo "启动服务..."
elif [ "$1" == "stop" ]; then
echo "停止服务..."
elif [ "$1" == "restart" ]; then
echo "重启服务..."
else
echo "用法: $0 {start|stop|restart}"
exit 1
fi#!/usr/bin/env bash
# 基本 if
if [ "$1" == "hello" ]; then
echo "你好!"
fi
# if-else
if [ $# -eq 0 ]; then
echo "没有提供参数"
else
echo "参数个数: $#"
fi
# if-elif-else
if [ "$1" == "start" ]; then
echo "启动服务..."
elif [ "$1" == "stop" ]; then
echo "停止服务..."
elif [ "$1" == "restart" ]; then
echo "重启服务..."
else
echo "用法: $0 {start|stop|restart}"
exit 1
fi
3.2 条件测试
字符串测试:
BASH
[ "$str" = "hello" ] # 相等
[ "$str" != "hello" ] # 不等
[ -z "$str" ] # 为空
[ -n "$str" ] # 不为空
[[ "$str" == h* ]] # 通配符匹配(Bash 扩展)
[[ "$str" =~ ^[0-9]+$ ]] # 正则匹配(Bash 扩展)[ "$str" = "hello" ] # 相等
[ "$str" != "hello" ] # 不等
[ -z "$str" ] # 为空
[ -n "$str" ] # 不为空
[[ "$str" == h* ]] # 通配符匹配(Bash 扩展)
[[ "$str" =~ ^[0-9]+$ ]] # 正则匹配(Bash 扩展)
数字测试:
BASH
[ $num -eq 10 ] # 等于
[ $num -ne 10 ] # 不等于
[ $num -lt 10 ] # 小于
[ $num -le 10 ] # 小于等于
[ $num -gt 10 ] # 大于
[ $num -ge 10 ] # 大于等于[ $num -eq 10 ] # 等于
[ $num -ne 10 ] # 不等于
[ $num -lt 10 ] # 小于
[ $num -le 10 ] # 小于等于
[ $num -gt 10 ] # 大于
[ $num -ge 10 ] # 大于等于
文件测试:
BASH
[ -f file.txt ] # 是普通文件且存在
[ -d /path/dir ] # 是目录且存在
[ -e file.txt ] # 存在(任何类型)
[ -r file.txt ] # 可读
[ -w file.txt ] # 可写
[ -x file.txt ] # 可执行
[ -s file.txt ] # 文件大小 > 0
[ -L symlink ] # 是符号链接
[ -f file1 -nt file2 ] # file1 比 file2 新
[ -f file1 -ot file2 ] # file1 比 file2 旧[ -f file.txt ] # 是普通文件且存在
[ -d /path/dir ] # 是目录且存在
[ -e file.txt ] # 存在(任何类型)
[ -r file.txt ] # 可读
[ -w file.txt ] # 可写
[ -x file.txt ] # 可执行
[ -s file.txt ] # 文件大小 > 0
[ -L symlink ] # 是符号链接
[ -f file1 -nt file2 ] # file1 比 file2 新
[ -f file1 -ot file2 ] # file1 比 file2 旧
逻辑运算:
BASH
# 传统写法
if [ -f file.txt ] && [ -r file.txt ]; then
echo "文件存在且可读"
fi
# Bash 扩展写法(推荐,支持 && 和 ||)
if [[ -f file.txt && -r file.txt ]]; then
echo "文件存在且可读"
fi
# 或
if [ -f file.txt ] || [ -d dir ]; then
echo "文件或目录存在"
fi# 传统写法
if [ -f file.txt ] && [ -r file.txt ]; then
echo "文件存在且可读"
fi
# Bash 扩展写法(推荐,支持 && 和 ||)
if [[ -f file.txt && -r file.txt ]]; then
echo "文件存在且可读"
fi
# 或
if [ -f file.txt ] || [ -d dir ]; then
echo "文件或目录存在"
fi
3.3 case 语句
BASH
#!/usr/bin/env bash
case "$1" in
start)
echo "启动服务..."
systemctl start myservice
;;
stop)
echo "停止服务..."
systemctl stop myservice
;;
restart)
echo "重启服务..."
systemctl restart myservice
;;
status)
systemctl status myservice
;;
*)
echo "用法: $0 {start|stop|restart|status}"
exit 1
;;
esac#!/usr/bin/env bash
case "$1" in
start)
echo "启动服务..."
systemctl start myservice
;;
stop)
echo "停止服务..."
systemctl stop myservice
;;
restart)
echo "重启服务..."
systemctl restart myservice
;;
status)
systemctl status myservice
;;
*)
echo "用法: $0 {start|stop|restart|status}"
exit 1
;;
esac
3.4 三元运算符
BASH
#!/usr/bin/env bash
# 条件 && 真值 || 假值
result=$([ $age -ge 18 ] && echo "成年" || echo "未成年")
echo $result#!/usr/bin/env bash
# 条件 && 真值 || 假值
result=$([ $age -ge 18 ] && echo "成年" || echo "未成年")
echo $result
四、循环
4.1 for 循环
BASH
#!/usr/bin/env bash
# 遍历列表
for fruit in apple banana cherry; do
echo "水果: $fruit"
done
# 遍历数字范围
for i in {1..5}; do
echo "数字: $i"
done
# 带步长
for i in {0..10..2}; do
echo "偶数: $i"
done
# C 风格 for 循环
for ((i=0; i<5; i++)); do
echo "索引: $i"
done
# 遍历文件
for file in /etc/*.conf; do
echo "配置文件: $file"
done
# 遍历命令输出
for user in $(cut -d: -f1 /etc/passwd); do
echo "用户: $user"
done#!/usr/bin/env bash
# 遍历列表
for fruit in apple banana cherry; do
echo "水果: $fruit"
done
# 遍历数字范围
for i in {1..5}; do
echo "数字: $i"
done
# 带步长
for i in {0..10..2}; do
echo "偶数: $i"
done
# C 风格 for 循环
for ((i=0; i<5; i++)); do
echo "索引: $i"
done
# 遍历文件
for file in /etc/*.conf; do
echo "配置文件: $file"
done
# 遍历命令输出
for user in $(cut -d: -f1 /etc/passwd); do
echo "用户: $user"
done
4.2 while 循环
BASH
#!/usr/bin/env bash
# 基本循环
count=0
while [ $count -lt 5 ]; do
echo "计数: $count"
((count++))
done
# 读取文件逐行处理
while IFS= read -r line; do
echo "行内容: $line"
done < /etc/hosts
# 无限循环
while true; do
echo "运行中..."
sleep 1
done
# 按条件退出
while true; do
read -p "继续吗?(y/n): " answer
if [[ "$answer" != "y" ]]; then
break
fi
done#!/usr/bin/env bash
# 基本循环
count=0
while [ $count -lt 5 ]; do
echo "计数: $count"
((count++))
done
# 读取文件逐行处理
while IFS= read -r line; do
echo "行内容: $line"
done < /etc/hosts
# 无限循环
while true; do
echo "运行中..."
sleep 1
done
# 按条件退出
while true; do
read -p "继续吗?(y/n): " answer
if [[ "$answer" != "y" ]]; then
break
fi
done
4.3 until 循环
BASH
#!/usr/bin/env bash
# 直到条件为真才停止
count=0
until [ $count -ge 5 ]; do
echo "计数: $count"
((count++))
done
# 等待服务启动
until curl -s http://localhost:8080/health > /dev/null; do
echo "等待服务启动..."
sleep 2
done
echo "服务已启动!"#!/usr/bin/env bash
# 直到条件为真才停止
count=0
until [ $count -ge 5 ]; do
echo "计数: $count"
((count++))
done
# 等待服务启动
until curl -s http://localhost:8080/health > /dev/null; do
echo "等待服务启动..."
sleep 2
done
echo "服务已启动!"
4.4 循环控制
BASH
#!/usr/bin/env bash
# break - 跳出循环
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo "数字: $i"
done
# continue - 跳过本次
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue # 跳过偶数
fi
echo "奇数: $i"
done#!/usr/bin/env bash
# break - 跳出循环
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo "数字: $i"
done
# continue - 跳过本次
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue # 跳过偶数
fi
echo "奇数: $i"
done
五、函数
5.1 定义与调用
BASH
#!/usr/bin/env bash
# 基本函数
greet() {
echo "Hello, $1!"
}
# 调用
greet "Alice"
greet "Bob"
# 带返回值的函数
is_even() {
local num=$1
if [ $((num % 2)) -eq 0 ]; then
return 0 # true
else
return 1 # false
fi
}
# 使用函数返回值
if is_even 4; then
echo "4 是偶数"
fi
# 函数输出捕获
get_date() {
date '+%Y-%m-%d'
}
today=$(get_date)
echo "今天是: $today"#!/usr/bin/env bash
# 基本函数
greet() {
echo "Hello, $1!"
}
# 调用
greet "Alice"
greet "Bob"
# 带返回值的函数
is_even() {
local num=$1
if [ $((num % 2)) -eq 0 ]; then
return 0 # true
else
return 1 # false
fi
}
# 使用函数返回值
if is_even 4; then
echo "4 是偶数"
fi
# 函数输出捕获
get_date() {
date '+%Y-%m-%d'
}
today=$(get_date)
echo "今天是: $today"
5.2 函数参数
BASH
#!/usr/bin/env bash
# 函数内 $1, $2... 是函数参数,不是脚本参数
add() {
local a=$1
local b=$2
local sum=$((a + b))
echo $sum
}
result=$(add 10 20)
echo "10 + 20 = $result"
# 多参数函数
create_user() {
local username=$1
local shell=$2
local home_dir="/home/${username}"
echo "创建用户: $username"
echo "Shell: $shell"
echo "Home: $home_dir"
useradd -m -s "$shell" "$username"
echo "用户 $username 创建成功"
}
create_user "deploy" "/bin/bash"#!/usr/bin/env bash
# 函数内 $1, $2... 是函数参数,不是脚本参数
add() {
local a=$1
local b=$2
local sum=$((a + b))
echo $sum
}
result=$(add 10 20)
echo "10 + 20 = $result"
# 多参数函数
create_user() {
local username=$1
local shell=$2
local home_dir="/home/${username}"
echo "创建用户: $username"
echo "Shell: $shell"
echo "Home: $home_dir"
useradd -m -s "$shell" "$username"
echo "用户 $username 创建成功"
}
create_user "deploy" "/bin/bash"
5.3 局部变量
BASH
#!/usr/bin/env bash
# 全局变量(函数内外都能访问)
global_var="I am global"
my_func() {
# 局部变量(只在函数内有效)
local local_var="I am local"
echo "函数内 - global: $global_var"
echo "函数内 - local: $local_var"
}
my_func
echo "函数外 - global: $global_var"
echo "函数外 - local: $local_var" # 空,因为 local_var 只在函数内有效#!/usr/bin/env bash
# 全局变量(函数内外都能访问)
global_var="I am global"
my_func() {
# 局部变量(只在函数内有效)
local local_var="I am local"
echo "函数内 - global: $global_var"
echo "函数内 - local: $local_var"
}
my_func
echo "函数外 - global: $global_var"
echo "函数外 - local: $local_var" # 空,因为 local_var 只在函数内有效
重要:函数内一定要用
local声明变量,避免污染全局作用域!
六、文本处理三剑客
6.1 grep — 文本搜索
BASH
#!/usr/bin/env bash
# 基本搜索
grep "error" /var/log/syslog
# 忽略大小写
grep -i "error" /var/log/syslog
# 反向匹配(不包含 error 的行)
grep -v "error" /var/log/syslog
# 显示行号
grep -n "error" /var/log/syslog
# 只显示匹配的部分
grep -o "ERROR: .*" /var/log/syslog
# 统计匹配行数
grep -c "error" /var/log/syslog
# 递归搜索目录
grep -r "TODO" /project/src/
# 只搜索特定文件
grep "error" /var/log/syslog --include="*.log"
# 使用正则表达式
grep -E "error|warning|critical" /var/log/syslog
grep -E "^2026-07" /var/log/syslog # 以日期开头
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" /var/log/syslog # IP地址
# 上下文
grep -B 2 "error" /var/log/syslog # 匹配行前2行
grep -A 2 "error" /var/log/syslog # 匹配行后2行
grep -C 2 "error" /var/log/syslog # 匹配行前后各2行
# 只输出文件名
grep -l "error" /var/log/*.log#!/usr/bin/env bash
# 基本搜索
grep "error" /var/log/syslog
# 忽略大小写
grep -i "error" /var/log/syslog
# 反向匹配(不包含 error 的行)
grep -v "error" /var/log/syslog
# 显示行号
grep -n "error" /var/log/syslog
# 只显示匹配的部分
grep -o "ERROR: .*" /var/log/syslog
# 统计匹配行数
grep -c "error" /var/log/syslog
# 递归搜索目录
grep -r "TODO" /project/src/
# 只搜索特定文件
grep "error" /var/log/syslog --include="*.log"
# 使用正则表达式
grep -E "error|warning|critical" /var/log/syslog
grep -E "^2026-07" /var/log/syslog # 以日期开头
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" /var/log/syslog # IP地址
# 上下文
grep -B 2 "error" /var/log/syslog # 匹配行前2行
grep -A 2 "error" /var/log/syslog # 匹配行后2行
grep -C 2 "error" /var/log/syslog # 匹配行前后各2行
# 只输出文件名
grep -l "error" /var/log/*.log
6.2 sed — 流编辑器
BASH
#!/usr/bin/env bash
# 替换(每行第一个匹配)
sed 's/old/new/' file.txt
# 全局替换
sed 's/old/new/g' file.txt
# 忽略大小写替换
sed 's/old/new/gi' file.txt
# 只替换特定行
sed '5s/old/new/' file.txt # 第5行
sed '5,10s/old/new/g' file.txt # 第5-10行
sed '/pattern/s/old/new/g' file.txt # 匹配 pattern 的行
# 删除行
sed '/^$/d' file.txt # 删除空行
sed '/^#/d' file.txt # 删除注释行
sed '1,3d' file.txt # 删除1-3行
sed '$d' file.txt # 删除最后一行
# 插入和追加
sed '1i\Header Line' file.txt # 在第1行前插入
sed '$a\Footer Line' file.txt # 在最后行后追加
# 原地修改文件
sed -i 's/old/new/g' file.txt
sed -i.bak 's/old/new/g' file.txt # 保留备份
# 多个命令
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt
# 使用 & 引用匹配内容
sed 's/[0-9]\+/[&]/g' file.txt # 数字加方括号: 123 → [123]
# 使用捕获组
echo "2026-07-11" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\1年\2月\3日/'
# 输出: 2026年07月11日#!/usr/bin/env bash
# 替换(每行第一个匹配)
sed 's/old/new/' file.txt
# 全局替换
sed 's/old/new/g' file.txt
# 忽略大小写替换
sed 's/old/new/gi' file.txt
# 只替换特定行
sed '5s/old/new/' file.txt # 第5行
sed '5,10s/old/new/g' file.txt # 第5-10行
sed '/pattern/s/old/new/g' file.txt # 匹配 pattern 的行
# 删除行
sed '/^$/d' file.txt # 删除空行
sed '/^#/d' file.txt # 删除注释行
sed '1,3d' file.txt # 删除1-3行
sed '$d' file.txt # 删除最后一行
# 插入和追加
sed '1i\Header Line' file.txt # 在第1行前插入
sed '$a\Footer Line' file.txt # 在最后行后追加
# 原地修改文件
sed -i 's/old/new/g' file.txt
sed -i.bak 's/old/new/g' file.txt # 保留备份
# 多个命令
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt
# 使用 & 引用匹配内容
sed 's/[0-9]\+/[&]/g' file.txt # 数字加方括号: 123 → [123]
# 使用捕获组
echo "2026-07-11" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\1年\2月\3日/'
# 输出: 2026年07月11日
6.3 awk — 文本分析
BASH
#!/usr/bin/env bash
# 基本用法:按列提取
awk '{print $1}' file.txt # 第一列
awk '{print $1, $3}' file.txt # 第一列和第三列
awk '{print $NF}' file.txt # 最后一列
awk '{print $(NF-1)}' file.txt # 倒数第二列
# 指定分隔符
awk -F: '{print $1}' /etc/passwd # 用户名
awk -F: '{print $1, $7}' /etc/passwd # 用户名和Shell
awk -F',' '{print $2}' data.csv # CSV 第二列
# 条件过滤
awk '$3 > 100' file.txt # 第三列大于100的行
awk '$1 == "ERROR"' file.txt # 第一列等于ERROR
awk '/error/' file.txt # 包含error的行
awk '/2026-07/ && /error/' file.txt # 同时满足两个条件
# 内置变量
awk '{print NR, NF, $0}' file.txt
# NR - 行号
# NF - 列数
# $0 - 整行
# BEGIN 和 END
awk 'BEGIN {print "开始处理"} {print $1} END {print "处理完成"}' file.txt
# 统计行数
awk 'END {print NR}' file.txt
# 求和
awk '{sum += $1} END {print sum}' file.txt
# 求平均值
awk '{sum += $1} END {print sum/NR}' file.txt
# 按列分组统计
awk '{count[$1]++} END {for (key in count) print key, count[key]}' file.txt
# 格式化输出
awk '{printf "%-10s %5d\n", $1, $2}' file.txt
# 实战:统计日志中各 IP 的访问次数(取前10)
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10#!/usr/bin/env bash
# 基本用法:按列提取
awk '{print $1}' file.txt # 第一列
awk '{print $1, $3}' file.txt # 第一列和第三列
awk '{print $NF}' file.txt # 最后一列
awk '{print $(NF-1)}' file.txt # 倒数第二列
# 指定分隔符
awk -F: '{print $1}' /etc/passwd # 用户名
awk -F: '{print $1, $7}' /etc/passwd # 用户名和Shell
awk -F',' '{print $2}' data.csv # CSV 第二列
# 条件过滤
awk '$3 > 100' file.txt # 第三列大于100的行
awk '$1 == "ERROR"' file.txt # 第一列等于ERROR
awk '/error/' file.txt # 包含error的行
awk '/2026-07/ && /error/' file.txt # 同时满足两个条件
# 内置变量
awk '{print NR, NF, $0}' file.txt
# NR - 行号
# NF - 列数
# $0 - 整行
# BEGIN 和 END
awk 'BEGIN {print "开始处理"} {print $1} END {print "处理完成"}' file.txt
# 统计行数
awk 'END {print NR}' file.txt
# 求和
awk '{sum += $1} END {print sum}' file.txt
# 求平均值
awk '{sum += $1} END {print sum/NR}' file.txt
# 按列分组统计
awk '{count[$1]++} END {for (key in count) print key, count[key]}' file.txt
# 格式化输出
awk '{printf "%-10s %5d\n", $1, $2}' file.txt
# 实战:统计日志中各 IP 的访问次数(取前10)
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
6.4 其他常用文本工具
BASH
# sort - 排序
sort file.txt # 升序
sort -r file.txt # 降序
sort -n file.txt # 数字排序
sort -u file.txt # 去重排序
sort -t: -k3n /etc/passwd # 按第3列数字排序
# uniq - 去重
sort file.txt | uniq # 去重
sort file.txt | uniq -c # 去重并计数
sort file.txt | uniq -d # 只显示重复行
# cut - 切分
cut -d: -f1 /etc/passwd # 按冒号切分,取第1列
cut -c1-10 file.txt # 取每行第1-10个字符
# tr - 字符替换
echo "HELLO" | tr 'A-Z' 'a-z' # 大写转小写
echo "hello world" | tr ' ' '-' # 空格转横线
cat file.txt | tr -d '\r' # 删除 Windows 换行符
echo "12345" | tr -d '0-9' # 删除数字
# paste - 合并文件
paste file1.txt file2.txt # 按列合并
paste -d',' file1.txt file2.txt # 用逗号分隔
# wc - 统计
wc -l file.txt # 行数
wc -w file.txt # 单词数
wc -c file.txt # 字符数
# find - 查找文件
find /var/log -name "*.log" # 按名查找
find /var/log -name "*.log" -mtime -7 # 7天内修改的
find /var/log -name "*.log" -size +100M # 大于100MB的
find /tmp -type f -delete # 删除找到的文件
find . -name "*.js" -exec chmod 644 {} + # 对找到的文件执行命令
# xargs - 参数传递
find . -name "*.log" | xargs grep "error"
find . -name "*.bak" | xargs rm -f
cat urls.txt | xargs -I{} curl -s {}# sort - 排序
sort file.txt # 升序
sort -r file.txt # 降序
sort -n file.txt # 数字排序
sort -u file.txt # 去重排序
sort -t: -k3n /etc/passwd # 按第3列数字排序
# uniq - 去重
sort file.txt | uniq # 去重
sort file.txt | uniq -c # 去重并计数
sort file.txt | uniq -d # 只显示重复行
# cut - 切分
cut -d: -f1 /etc/passwd # 按冒号切分,取第1列
cut -c1-10 file.txt # 取每行第1-10个字符
# tr - 字符替换
echo "HELLO" | tr 'A-Z' 'a-z' # 大写转小写
echo "hello world" | tr ' ' '-' # 空格转横线
cat file.txt | tr -d '\r' # 删除 Windows 换行符
echo "12345" | tr -d '0-9' # 删除数字
# paste - 合并文件
paste file1.txt file2.txt # 按列合并
paste -d',' file1.txt file2.txt # 用逗号分隔
# wc - 统计
wc -l file.txt # 行数
wc -w file.txt # 单词数
wc -c file.txt # 字符数
# find - 查找文件
find /var/log -name "*.log" # 按名查找
find /var/log -name "*.log" -mtime -7 # 7天内修改的
find /var/log -name "*.log" -size +100M # 大于100MB的
find /tmp -type f -delete # 删除找到的文件
find . -name "*.js" -exec chmod 644 {} + # 对找到的文件执行命令
# xargs - 参数传递
find . -name "*.log" | xargs grep "error"
find . -name "*.bak" | xargs rm -f
cat urls.txt | xargs -I{} curl -s {}
七、输入输出与重定向
7.1 重定向
BASH
#!/usr/bin/env bash
# 标准输出重定向
echo "hello" > file.txt # 覆盖
echo "world" >> file.txt # 追加
# 标准错误重定向
command 2> error.log # 只重定向错误
command 2>> error.log # 追加错误
# 标准输出和错误都重定向
command > all.log 2>&1 # 传统写法
command &> all.log # Bash 4+ 简写
# 丢弃输出
command > /dev/null 2>&1
# 标准输入重定向
while read line; do
echo "$line"
done < file.txt
# Here Document(多行文本)
cat << 'EOF' > config.txt
server:
host: localhost
port: 8080
EOF
# Here String
grep "pattern" <<< "search in this string"#!/usr/bin/env bash
# 标准输出重定向
echo "hello" > file.txt # 覆盖
echo "world" >> file.txt # 追加
# 标准错误重定向
command 2> error.log # 只重定向错误
command 2>> error.log # 追加错误
# 标准输出和错误都重定向
command > all.log 2>&1 # 传统写法
command &> all.log # Bash 4+ 简写
# 丢弃输出
command > /dev/null 2>&1
# 标准输入重定向
while read line; do
echo "$line"
done < file.txt
# Here Document(多行文本)
cat << 'EOF' > config.txt
server:
host: localhost
port: 8080
EOF
# Here String
grep "pattern" <<< "search in this string"
7.2 管道
BASH
#!/usr/bin/env bash
# 命令管道
cat /etc/passwd | grep "/bin/bash" | cut -d: -f1
# 统计 Nginx 访问日志中状态码分布
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 找出最大的10个文件
du -ah /var/log | sort -rh | head -10
# 查找并终止进程
ps aux | grep nginx | grep -v grep | awk '{print $2}' | xargs kill
# 使用 tee 同时输出到屏幕和文件
command | tee output.log
command | tee -a output.log # 追加模式#!/usr/bin/env bash
# 命令管道
cat /etc/passwd | grep "/bin/bash" | cut -d: -f1
# 统计 Nginx 访问日志中状态码分布
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 找出最大的10个文件
du -ah /var/log | sort -rh | head -10
# 查找并终止进程
ps aux | grep nginx | grep -v grep | awk '{print $2}' | xargs kill
# 使用 tee 同时输出到屏幕和文件
command | tee output.log
command | tee -a output.log # 追加模式
八、实用运维脚本
8.1 系统监控脚本
BASH
#!/usr/bin/env bash
# system_monitor.sh - 系统资源监控
set -euo pipefail
echo "======================================"
echo " 系统资源监控报告"
echo "======================================"
echo ""
# CPU 信息
echo "【CPU 使用率】"
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}')
echo " CPU: ${cpu_usage}%"
echo ""
# 内存信息
echo "【内存使用】"
free -h | awk '/^Mem:/ {printf " 总计: %s\n 已用: %s (%.1f%%)\n 可用: %s\n", $2, $3, ($3/$2)*100, $7}'
echo ""
# 磁盘使用
echo "【磁盘使用】"
df -h | awk 'NR==1 || /^\/dev\// {printf " %-20s %6s %6s %6s %s\n", $1, $2, $3, $5, $6}'
echo ""
# 网络连接
echo "【网络连接统计】"
echo " ESTABLISHED: $(ss -s | grep -o 'estab=[0-9]*' | cut -d= -f2)"
echo " TIME_WAIT: $(ss -ant | grep -c TIME-WAIT)"
echo " LISTEN: $(ss -ant | grep -c LISTEN)"
echo ""
# 负载均衡
echo "【系统负载】"
uptime | awk -F'load average:' '{print " 负载:" $2}'
echo ""
# Top 5 CPU 进程
echo "【CPU 占用 Top 5】"
ps aux --sort=-%cpu | head -6 | awk '{printf " %-10s %6s%% %s\n", $1, $3, $11}'
echo ""
# Top 5 内存进程
echo "【内存占用 Top 5】"
ps aux --sort=-%mem | head -6 | awk '{printf " %-10s %6s%% %s\n", $1, $4, $11}'
echo ""
echo "======================================"
echo " 报告生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "======================================"#!/usr/bin/env bash
# system_monitor.sh - 系统资源监控
set -euo pipefail
echo "======================================"
echo " 系统资源监控报告"
echo "======================================"
echo ""
# CPU 信息
echo "【CPU 使用率】"
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}')
echo " CPU: ${cpu_usage}%"
echo ""
# 内存信息
echo "【内存使用】"
free -h | awk '/^Mem:/ {printf " 总计: %s\n 已用: %s (%.1f%%)\n 可用: %s\n", $2, $3, ($3/$2)*100, $7}'
echo ""
# 磁盘使用
echo "【磁盘使用】"
df -h | awk 'NR==1 || /^\/dev\// {printf " %-20s %6s %6s %6s %s\n", $1, $2, $3, $5, $6}'
echo ""
# 网络连接
echo "【网络连接统计】"
echo " ESTABLISHED: $(ss -s | grep -o 'estab=[0-9]*' | cut -d= -f2)"
echo " TIME_WAIT: $(ss -ant | grep -c TIME-WAIT)"
echo " LISTEN: $(ss -ant | grep -c LISTEN)"
echo ""
# 负载均衡
echo "【系统负载】"
uptime | awk -F'load average:' '{print " 负载:" $2}'
echo ""
# Top 5 CPU 进程
echo "【CPU 占用 Top 5】"
ps aux --sort=-%cpu | head -6 | awk '{printf " %-10s %6s%% %s\n", $1, $3, $11}'
echo ""
# Top 5 内存进程
echo "【内存占用 Top 5】"
ps aux --sort=-%mem | head -6 | awk '{printf " %-10s %6s%% %s\n", $1, $4, $11}'
echo ""
echo "======================================"
echo " 报告生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "======================================"
8.2 日志分析脚本
BASH
#!/usr/bin/env bash
# log_analyzer.sh - Nginx 日志分析
set -euo pipefail
LOG_FILE="${1:-/var/log/nginx/access.log}"
if [ ! -f "$LOG_FILE" ]; then
echo "错误: 日志文件 $LOG_FILE 不存在"
exit 1
fi
echo "======================================"
echo " Nginx 日志分析报告"
echo " 日志文件: $LOG_FILE"
echo "======================================"
echo ""
# 总请求数
total=$(wc -l < "$LOG_FILE")
echo "【总请求数】 $total"
echo ""
# 状态码分布
echo "【状态码分布】"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn | while read count code; do
printf " %s: %d (%.1f%%)\n" "$code" "$count" "$(echo "scale=1; $count * 100 / $total" | bc)"
done
echo ""
# Top 10 IP
echo "【访问 IP Top 10】"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 | while read count ip; do
printf " %-15s %8d 次\n" "$ip" "$count"
done
echo ""
# Top 10 请求路径
echo "【请求路径 Top 10】"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 | while read count path; do
printf " %-40s %8d 次\n" "$path" "$count"
done
echo ""
# 404 错误页面
echo "【404 错误页面 Top 10】"
awk '$9 == 404 {print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 | while read count path; do
printf " %-40s %8d 次\n" "$path" "$count"
done
echo ""
# 流量统计
echo "【流量统计】"
awk '{sum += $10} END {printf " 总流量: %.2f GB\n", sum/1024/1024/1024}' "$LOG_FILE"
echo ""
# 按小时分布
echo "【按小时请求分布】"
awk '{print $4}' "$LOG_FILE" | cut -c14-15 | sort | uniq -c | while read count hour; do
printf " %s时: %d\n" "$hour" "$count"
done#!/usr/bin/env bash
# log_analyzer.sh - Nginx 日志分析
set -euo pipefail
LOG_FILE="${1:-/var/log/nginx/access.log}"
if [ ! -f "$LOG_FILE" ]; then
echo "错误: 日志文件 $LOG_FILE 不存在"
exit 1
fi
echo "======================================"
echo " Nginx 日志分析报告"
echo " 日志文件: $LOG_FILE"
echo "======================================"
echo ""
# 总请求数
total=$(wc -l < "$LOG_FILE")
echo "【总请求数】 $total"
echo ""
# 状态码分布
echo "【状态码分布】"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn | while read count code; do
printf " %s: %d (%.1f%%)\n" "$code" "$count" "$(echo "scale=1; $count * 100 / $total" | bc)"
done
echo ""
# Top 10 IP
echo "【访问 IP Top 10】"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 | while read count ip; do
printf " %-15s %8d 次\n" "$ip" "$count"
done
echo ""
# Top 10 请求路径
echo "【请求路径 Top 10】"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 | while read count path; do
printf " %-40s %8d 次\n" "$path" "$count"
done
echo ""
# 404 错误页面
echo "【404 错误页面 Top 10】"
awk '$9 == 404 {print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 | while read count path; do
printf " %-40s %8d 次\n" "$path" "$count"
done
echo ""
# 流量统计
echo "【流量统计】"
awk '{sum += $10} END {printf " 总流量: %.2f GB\n", sum/1024/1024/1024}' "$LOG_FILE"
echo ""
# 按小时分布
echo "【按小时请求分布】"
awk '{print $4}' "$LOG_FILE" | cut -c14-15 | sort | uniq -c | while read count hour; do
printf " %s时: %d\n" "$hour" "$count"
done
8.3 自动备份脚本
BASH
#!/usr/bin/env bash
# backup.sh - 网站数据自动备份
set -euo pipefail
# ===== 配置 =====
SOURCE_DIR="/var/www/mysite"
BACKUP_DIR="/backup"
REMOTE_DIR="user@backup-server:/backup"
RETENTION_DAYS=7
MYSQL_DB="mysite"
MYSQL_USER="root"
MYSQL_PASS="${DB_PASSWORD}"
LOG_FILE="/var/log/backup.log"
# ===== 函数 =====
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
send_alert() {
# 发送告警邮件或 Telegram 通知
curl -s "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
-d "chat_id=${TG_CHAT_ID}" \
-d "text=备份告警: $1" > /dev/null
}
# ===== 主流程 =====
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
BACKUP_NAME="mysite_${TIMESTAMP}"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
log "开始备份: ${BACKUP_NAME}"
# 创建备份目录
mkdir -p "$BACKUP_PATH"
# 1. 备份文件
log "备份网站文件..."
if tar -czf "${BACKUP_PATH}/files.tar.gz" -C "$SOURCE_DIR" .; then
log "文件备份完成: $(du -sh "${BACKUP_PATH}/files.tar.gz" | cut -f1)"
else
log "文件备份失败!"
send_alert "网站文件备份失败"
exit 1
fi
# 2. 备份数据库
log "备份数据库..."
if mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASS" "$MYSQL_DB" | gzip > "${BACKUP_PATH}/db.sql.gz"; then
log "数据库备份完成: $(du -sh "${BACKUP_PATH}/db.sql.gz" | cut -f1)"
else
log "数据库备份失败!"
send_alert "数据库备份失败"
exit 1
fi
# 3. 生成校验文件
cd "$BACKUP_DIR"
md5sum "${BACKUP_NAME}"/* > "${BACKUP_NAME}/checksum.md5"
log "校验文件已生成"
# 4. 打包总文件
tar -czf "${BACKUP_NAME}.tar.gz" "$BACKUP_NAME"
log "总备份文件: $(du -sh "${BACKUP_NAME}.tar.gz" | cut -f1)"
# 5. 上传到远程
log "上传到远程服务器..."
if rsync -az --progress "${BACKUP_NAME}.tar.gz" "$REMOTE_DIR/"; then
log "远程上传完成"
else
log "远程上传失败!"
send_alert "远程备份上传失败"
fi
# 6. 清理旧备份
log "清理 ${RETENTION_DAYS} 天前的旧备份..."
find "$BACKUP_DIR" -name "mysite_*.tar.gz" -mtime +${RETENTION_DAYS} -delete
find "$BACKUP_DIR" -type d -name "mysite_*" -mtime +${RETENTION_DAYS} -exec rm -rf {} +
# 清理远程旧备份
ssh user@backup-server "find /backup -name 'mysite_*.tar.gz' -mtime +${RETENTION_DAYS} -delete"
log "备份完成!"#!/usr/bin/env bash
# backup.sh - 网站数据自动备份
set -euo pipefail
# ===== 配置 =====
SOURCE_DIR="/var/www/mysite"
BACKUP_DIR="/backup"
REMOTE_DIR="user@backup-server:/backup"
RETENTION_DAYS=7
MYSQL_DB="mysite"
MYSQL_USER="root"
MYSQL_PASS="${DB_PASSWORD}"
LOG_FILE="/var/log/backup.log"
# ===== 函数 =====
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
send_alert() {
# 发送告警邮件或 Telegram 通知
curl -s "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
-d "chat_id=${TG_CHAT_ID}" \
-d "text=备份告警: $1" > /dev/null
}
# ===== 主流程 =====
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
BACKUP_NAME="mysite_${TIMESTAMP}"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
log "开始备份: ${BACKUP_NAME}"
# 创建备份目录
mkdir -p "$BACKUP_PATH"
# 1. 备份文件
log "备份网站文件..."
if tar -czf "${BACKUP_PATH}/files.tar.gz" -C "$SOURCE_DIR" .; then
log "文件备份完成: $(du -sh "${BACKUP_PATH}/files.tar.gz" | cut -f1)"
else
log "文件备份失败!"
send_alert "网站文件备份失败"
exit 1
fi
# 2. 备份数据库
log "备份数据库..."
if mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASS" "$MYSQL_DB" | gzip > "${BACKUP_PATH}/db.sql.gz"; then
log "数据库备份完成: $(du -sh "${BACKUP_PATH}/db.sql.gz" | cut -f1)"
else
log "数据库备份失败!"
send_alert "数据库备份失败"
exit 1
fi
# 3. 生成校验文件
cd "$BACKUP_DIR"
md5sum "${BACKUP_NAME}"/* > "${BACKUP_NAME}/checksum.md5"
log "校验文件已生成"
# 4. 打包总文件
tar -czf "${BACKUP_NAME}.tar.gz" "$BACKUP_NAME"
log "总备份文件: $(du -sh "${BACKUP_NAME}.tar.gz" | cut -f1)"
# 5. 上传到远程
log "上传到远程服务器..."
if rsync -az --progress "${BACKUP_NAME}.tar.gz" "$REMOTE_DIR/"; then
log "远程上传完成"
else
log "远程上传失败!"
send_alert "远程备份上传失败"
fi
# 6. 清理旧备份
log "清理 ${RETENTION_DAYS} 天前的旧备份..."
find "$BACKUP_DIR" -name "mysite_*.tar.gz" -mtime +${RETENTION_DAYS} -delete
find "$BACKUP_DIR" -type d -name "mysite_*" -mtime +${RETENTION_DAYS} -exec rm -rf {} +
# 清理远程旧备份
ssh user@backup-server "find /backup -name 'mysite_*.tar.gz' -mtime +${RETENTION_DAYS} -delete"
log "备份完成!"
8.4 服务部署脚本
BASH
#!/usr/bin/env bash
# deploy.sh - 自动化部署脚本
set -euo pipefail
# ===== 配置 =====
APP_NAME="myapp"
APP_DIR="/opt/${APP_NAME}"
REPO_URL="git@github.com:user/myapp.git"
BRANCH="main"
PID_FILE="/var/run/${APP_NAME}.pid"
LOG_FILE="/var/log/${APP_NAME}.log"
# ===== 函数 =====
log() {
echo -e "\033[32m[$(date '+%H:%M:%S')]\033[0m $1"
}
error() {
echo -e "\033[31m[错误]\033[0m $1"
exit 1
}
check_service() {
if systemctl is-active --quiet "$APP_NAME"; then
return 0
else
return 1
fi
}
# ===== 主流程 =====
log "开始部署 ${APP_NAME}..."
# 1. 拉取最新代码
log "拉取最新代码..."
if [ -d "$APP_DIR/.git" ]; then
cd "$APP_DIR"
git fetch origin
git reset --hard "origin/${BRANCH}"
else
git clone -b "$BRANCH" "$REPO_URL" "$APP_DIR"
cd "$APP_DIR"
fi
log "当前版本: $(git rev-parse --short HEAD)"
# 2. 安装依赖
log "安装依赖..."
if [ -f "package.json" ]; then
pnpm install --frozen-lockfile
elif [ -f "requirements.txt" ]; then
pip install -r requirements.txt
elif [ -f "go.mod" ]; then
go mod download
fi
# 3. 构建
log "构建项目..."
if [ -f "package.json" ]; then
pnpm build
elif [ -f "Makefile" ]; then
make build
fi
# 4. 运行测试
log "运行测试..."
if [ -f "package.json" ]; then
pnpm test
fi
# 5. 重启服务
log "重启服务..."
if check_service; then
systemctl restart "$APP_NAME"
log "服务已重启"
else
systemctl start "$APP_NAME"
log "服务已启动"
fi
# 6. 健康检查
log "健康检查..."
sleep 3
for i in {1..10}; do
if curl -sf "http://localhost:8080/health" > /dev/null; then
log "健康检查通过!"
log "部署成功!版本: $(git rev-parse --short HEAD)"
exit 0
fi
log "等待服务就绪... ($i/10)"
sleep 2
done
error "健康检查失败!请检查日志: $LOG_FILE"#!/usr/bin/env bash
# deploy.sh - 自动化部署脚本
set -euo pipefail
# ===== 配置 =====
APP_NAME="myapp"
APP_DIR="/opt/${APP_NAME}"
REPO_URL="git@github.com:user/myapp.git"
BRANCH="main"
PID_FILE="/var/run/${APP_NAME}.pid"
LOG_FILE="/var/log/${APP_NAME}.log"
# ===== 函数 =====
log() {
echo -e "\033[32m[$(date '+%H:%M:%S')]\033[0m $1"
}
error() {
echo -e "\033[31m[错误]\033[0m $1"
exit 1
}
check_service() {
if systemctl is-active --quiet "$APP_NAME"; then
return 0
else
return 1
fi
}
# ===== 主流程 =====
log "开始部署 ${APP_NAME}..."
# 1. 拉取最新代码
log "拉取最新代码..."
if [ -d "$APP_DIR/.git" ]; then
cd "$APP_DIR"
git fetch origin
git reset --hard "origin/${BRANCH}"
else
git clone -b "$BRANCH" "$REPO_URL" "$APP_DIR"
cd "$APP_DIR"
fi
log "当前版本: $(git rev-parse --short HEAD)"
# 2. 安装依赖
log "安装依赖..."
if [ -f "package.json" ]; then
pnpm install --frozen-lockfile
elif [ -f "requirements.txt" ]; then
pip install -r requirements.txt
elif [ -f "go.mod" ]; then
go mod download
fi
# 3. 构建
log "构建项目..."
if [ -f "package.json" ]; then
pnpm build
elif [ -f "Makefile" ]; then
make build
fi
# 4. 运行测试
log "运行测试..."
if [ -f "package.json" ]; then
pnpm test
fi
# 5. 重启服务
log "重启服务..."
if check_service; then
systemctl restart "$APP_NAME"
log "服务已重启"
else
systemctl start "$APP_NAME"
log "服务已启动"
fi
# 6. 健康检查
log "健康检查..."
sleep 3
for i in {1..10}; do
if curl -sf "http://localhost:8080/health" > /dev/null; then
log "健康检查通过!"
log "部署成功!版本: $(git rev-parse --short HEAD)"
exit 0
fi
log "等待服务就绪... ($i/10)"
sleep 2
done
error "健康检查失败!请检查日志: $LOG_FILE"
8.5 批量服务器管理
BASH
#!/usr/bin/env bash
# multi_server.sh - 批量管理多台服务器
set -euo pipefail
# 服务器列表
SERVERS=(
"web01:192.168.1.10"
"web02:192.168.1.11"
"db01:192.168.1.20"
"cache01:192.168.1.30"
)
SSH_USER="admin"
SSH_OPTS="-o ConnectTimeout=5 -o StrictHostKeyChecking=no"
# 在所有服务器上执行命令
run_on_all() {
local cmd="$1"
for server in "${SERVERS[@]}"; do
local name="${server%%:*}"
local ip="${server##*:}"
echo "--- ${name} (${ip}) ---"
ssh $SSH_OPTS "${SSH_USER}@${ip}" "$cmd" 2>&1
echo ""
done
}
# 查看所有服务器状态
status() {
run_on_all 'uptime && free -h | grep Mem && df -h / | tail -1'
}
# 在所有服务器上更新系统
update_all() {
run_on_all 'sudo apt update && sudo apt upgrade -y'
}
# 在所有服务器上重启某服务
restart_service() {
local service="$1"
run_on_all "sudo systemctl restart $service && sudo systemctl status $service"
}
# 拷贝文件到所有服务器
distribute_file() {
local file="$1"
local dest="$2"
for server in "${SERVERS[@]}"; do
local name="${server%%:*}"
local ip="${server##*:}"
echo "拷贝到 ${name} (${ip})..."
scp $SSH_OPTS "$file" "${SSH_USER}@${ip}:${dest}"
done
}
# 主菜单
case "${1:-}" in
status) status ;;
update) update_all ;;
restart) restart_service "${2:?需要指定服务名}" ;;
distribute) distribute_file "${2:?需要指定源文件}" "${3:?需要指定目标路径}" ;;
exec) run_on_all "${2:?需要指定命令}" ;;
*)
echo "用法: $0 {status|update|restart <service>|distribute <file> <dest>|exec <command>}"
exit 1
;;
esac#!/usr/bin/env bash
# multi_server.sh - 批量管理多台服务器
set -euo pipefail
# 服务器列表
SERVERS=(
"web01:192.168.1.10"
"web02:192.168.1.11"
"db01:192.168.1.20"
"cache01:192.168.1.30"
)
SSH_USER="admin"
SSH_OPTS="-o ConnectTimeout=5 -o StrictHostKeyChecking=no"
# 在所有服务器上执行命令
run_on_all() {
local cmd="$1"
for server in "${SERVERS[@]}"; do
local name="${server%%:*}"
local ip="${server##*:}"
echo "--- ${name} (${ip}) ---"
ssh $SSH_OPTS "${SSH_USER}@${ip}" "$cmd" 2>&1
echo ""
done
}
# 查看所有服务器状态
status() {
run_on_all 'uptime && free -h | grep Mem && df -h / | tail -1'
}
# 在所有服务器上更新系统
update_all() {
run_on_all 'sudo apt update && sudo apt upgrade -y'
}
# 在所有服务器上重启某服务
restart_service() {
local service="$1"
run_on_all "sudo systemctl restart $service && sudo systemctl status $service"
}
# 拷贝文件到所有服务器
distribute_file() {
local file="$1"
local dest="$2"
for server in "${SERVERS[@]}"; do
local name="${server%%:*}"
local ip="${server##*:}"
echo "拷贝到 ${name} (${ip})..."
scp $SSH_OPTS "$file" "${SSH_USER}@${ip}:${dest}"
done
}
# 主菜单
case "${1:-}" in
status) status ;;
update) update_all ;;
restart) restart_service "${2:?需要指定服务名}" ;;
distribute) distribute_file "${2:?需要指定源文件}" "${3:?需要指定目标路径}" ;;
exec) run_on_all "${2:?需要指定命令}" ;;
*)
echo "用法: $0 {status|update|restart <service>|distribute <file> <dest>|exec <command>}"
exit 1
;;
esac
九、systemd 服务管理
9.1 创建 systemd 服务
INI
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
Documentation=https://github.com/user/myapp
After=network.target mysql.service
Wants=mysql.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/dist/index.js
ExecStop=/bin/kill -TERM $MAINPID
Restart=always
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000
# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
# 安全限制
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data
# 资源限制
LimitNOFILE=65536
MemoryMax=512M
CPUQuota=100%
[Install]
WantedBy=multi-user.target# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
Documentation=https://github.com/user/myapp
After=network.target mysql.service
Wants=mysql.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/dist/index.js
ExecStop=/bin/kill -TERM $MAINPID
Restart=always
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000
# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
# 安全限制
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data
# 资源限制
LimitNOFILE=65536
MemoryMax=512M
CPUQuota=100%
[Install]
WantedBy=multi-user.target
9.2 管理命令
BASH
# 重新加载 systemd 配置
sudo systemctl daemon-reload
# 启动/停止/重启
sudo systemctl start myapp
sudo systemctl stop myapp
sudo systemctl restart myapp
# 查看状态
sudo systemctl status myapp
# 开机自启
sudo systemctl enable myapp
sudo systemctl disable myapp
# 查看日志
journalctl -u myapp # 所有日志
journalctl -u myapp -f # 实时跟踪
journalctl -u myapp --since today
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp -p err # 只看错误# 重新加载 systemd 配置
sudo systemctl daemon-reload
# 启动/停止/重启
sudo systemctl start myapp
sudo systemctl stop myapp
sudo systemctl restart myapp
# 查看状态
sudo systemctl status myapp
# 开机自启
sudo systemctl enable myapp
sudo systemctl disable myapp
# 查看日志
journalctl -u myapp # 所有日志
journalctl -u myapp -f # 实时跟踪
journalctl -u myapp --since today
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp -p err # 只看错误
9.3 systemd 定时器(替代 Cron)
INI
# /etc/systemd/system/backup.service
[Unit]
Description=Backup Service
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh# /etc/systemd/system/backup.service
[Unit]
Description=Backup Service
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
INI
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
BASH
# 启用定时器
sudo systemctl enable backup.timer
sudo systemctl start backup.timer
# 查看定时器
systemctl list-timers# 启用定时器
sudo systemctl enable backup.timer
sudo systemctl start backup.timer
# 查看定时器
systemctl list-timers
十、Cron 定时任务
10.1 Cron 配置
BASH
# 编辑 crontab
crontab -e
# 查看当前 crontab
crontab -l
# 格式:
# 分钟 小时 日期 月份 星期 命令
# * * * * * command
#
# ┌───── 分钟 (0-59)
# │ ┌───── 小时 (0-23)
# │ │ ┌───── 日期 (1-31)
# │ │ │ ┌───── 月份 (1-12)
# │ │ │ │ ┌───── 星期 (0-7, 0和7都是周日)
# │ │ │ │ │
# * * * * * command# 编辑 crontab
crontab -e
# 查看当前 crontab
crontab -l
# 格式:
# 分钟 小时 日期 月份 星期 命令
# * * * * * command
#
# ┌───── 分钟 (0-59)
# │ ┌───── 小时 (0-23)
# │ │ ┌───── 日期 (1-31)
# │ │ │ ┌───── 月份 (1-12)
# │ │ │ │ ┌───── 星期 (0-7, 0和7都是周日)
# │ │ │ │ │
# * * * * * command
10.2 常用 Cron 示例
BASH
# 每天凌晨2点执行备份
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# 每5分钟检查服务
*/5 * * * * /opt/scripts/health-check.sh
# 每周一早上8点
0 8 * * 1 /opt/scripts/weekly-report.sh
# 每月1号执行
0 0 1 * * /opt/scripts/monthly-task.sh
# 工作日每小时执行
0 * * * 1-5 /opt/scripts/workday-task.sh
# 每天的 9:00, 12:00, 18:00
0 9,12,18 * * * /opt/scripts/check.sh
# 每30秒执行(Cron最小粒度是分钟)
* * * * * /opt/scripts/task.sh ; sleep 30 ; /opt/scripts/task.sh
# 环境变量
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
MAILTO=admin@example.com
0 2 * * * /opt/scripts/backup.sh# 每天凌晨2点执行备份
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# 每5分钟检查服务
*/5 * * * * /opt/scripts/health-check.sh
# 每周一早上8点
0 8 * * 1 /opt/scripts/weekly-report.sh
# 每月1号执行
0 0 1 * * /opt/scripts/monthly-task.sh
# 工作日每小时执行
0 * * * 1-5 /opt/scripts/workday-task.sh
# 每天的 9:00, 12:00, 18:00
0 9,12,18 * * * /opt/scripts/check.sh
# 每30秒执行(Cron最小粒度是分钟)
* * * * * /opt/scripts/task.sh ; sleep 30 ; /opt/scripts/task.sh
# 环境变量
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
MAILTO=admin@example.com
0 2 * * * /opt/scripts/backup.sh
十一、脚本调试与最佳实践
11.1 调试技巧
BASH
# 1. 语法检查(不执行)
bash -n script.sh
# 2. 执行追踪(打印每条命令)
bash -x script.sh
# 3. 在脚本中开启/关闭调试
#!/usr/bin/env bash
set -x # 开启调试
# ... 代码 ...
set +x # 关闭调试
# 4. 检查特定行
set -x # 从这里开始追踪
echo "debugging this part"
set +x # 停止追踪
# 5. 使用 PS4 自定义调试前缀
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x script.sh
# 6. 临时输出变量值
echo "DEBUG: var=$var" >&2# 1. 语法检查(不执行)
bash -n script.sh
# 2. 执行追踪(打印每条命令)
bash -x script.sh
# 3. 在脚本中开启/关闭调试
#!/usr/bin/env bash
set -x # 开启调试
# ... 代码 ...
set +x # 关闭调试
# 4. 检查特定行
set -x # 从这里开始追踪
echo "debugging this part"
set +x # 停止追踪
# 5. 使用 PS4 自定义调试前缀
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x script.sh
# 6. 临时输出变量值
echo "DEBUG: var=$var" >&2
11.2 最佳实践
BASH
#!/usr/bin/env bash
# ========================================
# 脚本名称: deploy.sh
# 描述: 自动化部署脚本
# 作者: Your Name
# 日期: 2026-07-11
# 版本: 1.0.0
# ========================================
set -euo pipefail
IFS=$'\n\t'
# ===== 配置 =====
readonly APP_NAME="myapp"
readonly APP_DIR="/opt/myapp"
readonly LOG_FILE="/var/log/deploy.log"
# ===== 函数 =====
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
die() {
echo "[FATAL] $*" >&2
exit 1
}
cleanup() {
log "清理临时文件..."
rm -f /tmp/myapp_*
}
trap cleanup EXIT # 脚本退出时自动清理
usage() {
cat << EOF
用法: $0 [选项]
选项:
-e, --env ENV 环境名 (dev/staging/prod)
-v, --version VER 部署版本号
-h, --help 显示帮助
示例:
$0 --env prod --version v1.2.3
EOF
}
# ===== 参数解析 =====
while [[ $# -gt 0 ]]; do
case "$1" in
-e|--env)
ENV="$2"
shift 2
;;
-v|--version)
VERSION="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
die "未知参数: $1"
;;
esac
done
# ===== 参数校验 =====
: "${ENV:?请指定环境 (--env)}"
: "${VERSION:?请指定版本 (--version)}"
[[ "$ENV" =~ ^(dev|staging|prod)$ ]] || die "环境必须是 dev/staging/prod"
# ===== 主流程 =====
log "开始部署 ${APP_NAME} ${VERSION} 到 ${ENV}..."
# ... 部署逻辑 ...
log "部署完成!"#!/usr/bin/env bash
# ========================================
# 脚本名称: deploy.sh
# 描述: 自动化部署脚本
# 作者: Your Name
# 日期: 2026-07-11
# 版本: 1.0.0
# ========================================
set -euo pipefail
IFS=$'\n\t'
# ===== 配置 =====
readonly APP_NAME="myapp"
readonly APP_DIR="/opt/myapp"
readonly LOG_FILE="/var/log/deploy.log"
# ===== 函数 =====
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
die() {
echo "[FATAL] $*" >&2
exit 1
}
cleanup() {
log "清理临时文件..."
rm -f /tmp/myapp_*
}
trap cleanup EXIT # 脚本退出时自动清理
usage() {
cat << EOF
用法: $0 [选项]
选项:
-e, --env ENV 环境名 (dev/staging/prod)
-v, --version VER 部署版本号
-h, --help 显示帮助
示例:
$0 --env prod --version v1.2.3
EOF
}
# ===== 参数解析 =====
while [[ $# -gt 0 ]]; do
case "$1" in
-e|--env)
ENV="$2"
shift 2
;;
-v|--version)
VERSION="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
die "未知参数: $1"
;;
esac
done
# ===== 参数校验 =====
: "${ENV:?请指定环境 (--env)}"
: "${VERSION:?请指定版本 (--version)}"
[[ "$ENV" =~ ^(dev|staging|prod)$ ]] || die "环境必须是 dev/staging/prod"
# ===== 主流程 =====
log "开始部署 ${APP_NAME} ${VERSION} 到 ${ENV}..."
# ... 部署逻辑 ...
log "部署完成!"
11.3 安全注意事项
BASH
# 1. 永远引用变量
rm "$file" # ✅ 安全
rm $file # ❌ 危险(文件名有空格会出错)
# 2. 使用 [[ ]] 代替 [ ]
if [[ -f "$file" ]]; then # ✅ 更安全
if [ -f "$file" ]; then # 也可以,但不支持正则
# 3. 使用 read -r 防止反斜杠转义
while read -r line; do # ✅
while read line; do # ❌
# 4. 临时文件用 mktemp
tmpfile=$(mktemp) # ✅ 安全
tmpfile="/tmp/myapp.$$" # ❌ 可能被预测
# 5. 敏感信息用环境变量
DB_PASS="${DB_PASS:?请设置 DB_PASS 环境变量}"
# 6. 限制脚本权限
chmod 700 script.sh # 只有 owner 能读写执行
# 7. 避免命令注入
# 如果用户输入作为命令的一部分,要小心
user_input="$(cat user_input.txt)"
# ❌ 危险
eval "$user_input"
# ✅ 安全
echo "$user_input" | bash# 1. 永远引用变量
rm "$file" # ✅ 安全
rm $file # ❌ 危险(文件名有空格会出错)
# 2. 使用 [[ ]] 代替 [ ]
if [[ -f "$file" ]]; then # ✅ 更安全
if [ -f "$file" ]; then # 也可以,但不支持正则
# 3. 使用 read -r 防止反斜杠转义
while read -r line; do # ✅
while read line; do # ❌
# 4. 临时文件用 mktemp
tmpfile=$(mktemp) # ✅ 安全
tmpfile="/tmp/myapp.$$" # ❌ 可能被预测
# 5. 敏感信息用环境变量
DB_PASS="${DB_PASS:?请设置 DB_PASS 环境变量}"
# 6. 限制脚本权限
chmod 700 script.sh # 只有 owner 能读写执行
# 7. 避免命令注入
# 如果用户输入作为命令的一部分,要小心
user_input="$(cat user_input.txt)"
# ❌ 危险
eval "$user_input"
# ✅ 安全
echo "$user_input" | bash
十二、常用脚本速查表
12.1 文件操作
BASH
# 批量重命名
for f in *.jpg; do mv "$f" "vacation_$f"; done
# 批量转换格式
for f in *.wav; do ffmpeg -i "$f" "${f%.wav}.mp3"; done
# 同步目录
rsync -avz --delete source/ dest/
# 查找大文件
find / -type f -size +500M -exec ls -lh {} \; 2>/dev/null
# 查找并删除空目录
find . -type d -empty -delete
# 比较两个目录
diff -rq dir1/ dir2/# 批量重命名
for f in *.jpg; do mv "$f" "vacation_$f"; done
# 批量转换格式
for f in *.wav; do ffmpeg -i "$f" "${f%.wav}.mp3"; done
# 同步目录
rsync -avz --delete source/ dest/
# 查找大文件
find / -type f -size +500M -exec ls -lh {} \; 2>/dev/null
# 查找并删除空目录
find . -type d -empty -delete
# 比较两个目录
diff -rq dir1/ dir2/
12.2 进程管理
BASH
# 查找并终止进程
kill $(pgrep -f "node app.js")
# 等待进程结束
while pgrep -f "myapp" > /dev/null; do
echo "等待进程结束..."
sleep 1
done
# 限制进程 CPU
cpulimit -p 12345 -l 50 # 限制为50% CPU
# 查看进程打开的文件
lsof -p 12345# 查找并终止进程
kill $(pgrep -f "node app.js")
# 等待进程结束
while pgrep -f "myapp" > /dev/null; do
echo "等待进程结束..."
sleep 1
done
# 限制进程 CPU
cpulimit -p 12345 -l 50 # 限制为50% CPU
# 查看进程打开的文件
lsof -p 12345
12.3 网络操作
BASH
# 检查端口是否开放
nc -zv host 80
# 批量检查多台服务器
for host in web01 web02 db01; do
echo -n "$host: "
nc -zv -w2 "$host" 22 2>&1 | grep -o "succeeded\|failed"
done
# 下载文件并校验
wget -q "https://example.com/file.tar.gz"
echo "abc123 file.tar.gz" | md5sum -c
# 批量下载 URL
while read url; do
wget -q "$url"
done < urls.txt# 检查端口是否开放
nc -zv host 80
# 批量检查多台服务器
for host in web01 web02 db01; do
echo -n "$host: "
nc -zv -w2 "$host" 22 2>&1 | grep -o "succeeded\|failed"
done
# 下载文件并校验
wget -q "https://example.com/file.tar.gz"
echo "abc123 file.tar.gz" | md5sum -c
# 批量下载 URL
while read url; do
wget -q "$url"
done < urls.txt
12.4 数据库操作
BASH
# 备份所有数据库
mysqldump -u root -p --all-databases > all_db.sql
# 备份单个数据库并压缩
mysqldump -u root -p mydb | gzip > mydb_$(date +%Y%m%d).sql.gz
# 恢复数据库
gunzip < mydb_20260711.sql.gz | mysql -u root -p mydb
# 批量执行 SQL
mysql -u root -p -e "SELECT User, Host FROM mysql.user;"
# 检查数据库大小
mysql -u root -p -e "
SELECT table_schema AS '数据库',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS '大小 (MB)'
FROM information_schema.tables
GROUP BY table_schema;"# 备份所有数据库
mysqldump -u root -p --all-databases > all_db.sql
# 备份单个数据库并压缩
mysqldump -u root -p mydb | gzip > mydb_$(date +%Y%m%d).sql.gz
# 恢复数据库
gunzip < mydb_20260711.sql.gz | mysql -u root -p mydb
# 批量执行 SQL
mysql -u root -p -e "SELECT User, Host FROM mysql.user;"
# 检查数据库大小
mysql -u root -p -e "
SELECT table_schema AS '数据库',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS '大小 (MB)'
FROM information_schema.tables
GROUP BY table_schema;"
十三、总结
Shell 脚本学习路径
PLAINTEXT
第 1 周:基础
→ 变量、条件、循环、函数
→ 写简单的自动化脚本
第 2 周:文本处理
→ grep、sed、awk 熟练使用
→ 管道和重定向
→ 日志分析脚本
第 3-4 周:进阶
→ 数组、关联数组
→ 正则表达式
→ 函数库和复用
→ 参数解析
第 2 月:生产级
→ systemd 服务管理
→ Cron 定时任务
→ 错误处理和日志
→ 安全最佳实践
第 3 月+:高级
→ 批量服务器管理
→ CI/CD 集成
→ 监控告警脚本
→ 性能优化第 1 周:基础
→ 变量、条件、循环、函数
→ 写简单的自动化脚本
第 2 周:文本处理
→ grep、sed、awk 熟练使用
→ 管道和重定向
→ 日志分析脚本
第 3-4 周:进阶
→ 数组、关联数组
→ 正则表达式
→ 函数库和复用
→ 参数解析
第 2 月:生产级
→ systemd 服务管理
→ Cron 定时任务
→ 错误处理和日志
→ 安全最佳实践
第 3 月+:高级
→ 批量服务器管理
→ CI/CD 集成
→ 监控告警脚本
→ 性能优化
必知必会清单
-
set -euo pipefail安全选项 - 变量引用加双引号
"$var" - 函数内使用
local变量 - 使用
mktemp创建临时文件 -
trap清理资源 -
readonly常量 - 参数校验和帮助信息
- 日志记录
- 错误处理和退出码
- 脚本可执行权限
chmod +x
【相关推荐】
- Nginx 反向代理与负载均衡完全指南 - Web 服务器
- VPS 性能优化完全指南 - 服务器调优
- VPS 安全加固完全指南 - 服务器安全
- Docker Compose 配置指南 - 容器编排
Shell 脚本是运维人员的超能力。 掌握它,你可以把重复的工作自动化,把复杂的操作一键化,把手动的过程标准化。写好 Shell 脚本,是成为高级运维工程师的必经之路。🚀
