- Shell 脚本编程完整教程
- 一、Shell 分类与 Bash 初始化文件
- 1. Shell 分类
- 2. Bash 初始化文件说明
- 二、Shell 脚本基础
- 1. 创建和运行脚本
- 2. Export 和环境变量
- 三、Shell 变量
- 1. 变量基础
- 2. 特殊变量
- 3. 变量扩展
- 四、顺序执行
- 1. 命令组合
- 2. 输入输出
- 五、判断语句
- 1. if 语句
- 2. 测试表达式
- 3. case 语句
- 六、循环语句
- 1. for 循环
- 2. while 循环
- 3. until 循环
- 4. 循环控制
- 七、函数
- 1. 函数定义
- 2. 函数参数
- 八、实用脚本示例
- 1. 文件备份脚本
- 2. 系统监控脚本
- 3. 综合应用:网站部署脚本
- 九、最佳实践
- 1. 脚本模板
- 2. 调试技巧
- 十、总结
- Shell 脚本要点总结:
- 运行脚本:
Shell 脚本编程完整教程
一、Shell 分类与 Bash 初始化文件
1. Shell 分类
# 常见的 Shell 类型
# 1. Bourne Shell (sh) - 最经典
# 2. Bourne Again Shell (bash) - Linux 默认
# 3. Korn Shell (ksh) - 兼容 sh 的增强版
# 4. C Shell (csh) - 类似 C 语言语法
# 5. Z Shell (zsh) - 功能强大,macOS 默认
# 查看当前使用的 Shell
echo $SHELL
echo $0
# 查看可用的 Shell
cat /etc/shells2. Bash 初始化文件说明
# 启动文件加载顺序:
# 1. 登录 Shell 的加载顺序
# /etc/profile → ~/.bash_profile → ~/.bash_login → ~/.profile
# → /etc/bash.bashrc → ~/.bashrc
# 2. 非登录 Shell
# ~/.bashrc → /etc/bash.bashrc
# 文件作用说明:
# /etc/profile - 系统全局配置
# ~/.bash_profile - 用户登录配置
# ~/.bashrc - 用户交互式 Shell 配置
# ~/.profile - 用户配置(通用)
# /etc/bash.bashrc - 系统 bashrc 配置二、Shell 脚本基础
1. 创建和运行脚本
#!/bin/bash
# 创建脚本文件
# script.sh
# 1. 使用 . 或 source 执行(在当前Shell中执行)
. script.sh
source script.sh
# 2. 使用 bash 执行
bash script.sh
# 3. 作为可执行文件
chmod +x script.sh
./script.sh2. Export 和环境变量
#!/bin/bash
# export_demo.sh
# 局部变量(只在当前Shell中有效)
LOCAL_VAR="I'm local"
echo "局部变量: $LOCAL_VAR"
# 环境变量(可被子进程继承)
export GLOBAL_VAR="I'm global"
echo "环境变量: $GLOBAL_VAR"
# 查看所有环境变量
printenv
# 或
env
# 查看特定环境变量
echo $PATH
echo $HOME
echo $USER
# 设置永久环境变量
# 1. 当前会话
export MY_VAR="value"
# 2. 永久设置(添加到 ~/.bashrc 或 ~/.bash_profile)
echo 'export MY_VAR="value"' >> ~/.bashrc
source ~/.bashrc三、Shell 变量
1. 变量基础
#!/bin/bash
# variable_demo.sh
# 定义变量(等号两边不能有空格)
name="John"
age=25
PI=3.1415926
# 使用变量
echo "Name: $name"
echo "Age: ${age}" # 推荐使用${},更清晰
# 只读变量
readonly readonly_var="This is read only"
# readonly_var="change" # 这行会报错
# 删除变量
unset name详情参考 变量详细说明
2. 特殊变量
#!/bin/bash
# special_variables.sh
echo "当前脚本: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "所有参数: $@"
echo "参数个数: $#"
echo "上个命令的退出状态: $?"
echo "当前进程ID: $$"
echo "后台最后一个进程ID: $!"
echo "所有参数(一个字符串): $*"
# 示例
./special_variables.sh arg1 arg2 arg3
# 输出:
# 当前脚本: ./special_variables.sh
# 第一个参数: arg1
# 第二个参数: arg2
# 所有参数: arg1 arg2 arg3
# 参数个数: 33. 变量扩展
#!/bin/bash
# variable_expansion.sh
# 默认值
name=${1:-"Guest"}
echo "Hello, $name"
# 检查变量是否设置
value=${VAR?"VAR 没有设置!"}
# 字符串长度
str="Hello World"
echo "长度: ${#str}"
# 子字符串
echo "子串(1-5): ${str:0:5}"
echo "从第6个开始: ${str:6}"
# 模式匹配
filename="document.txt"
echo "去掉.txt: ${filename%.txt}"
echo "去掉doc: ${filename#doc}"
# 大小写转换
text="Hello World"
echo "大写: ${text^^}"
echo "小写: ${text,,}"四、顺序执行
1. 命令组合
#!/bin/bash
# sequential.sh
# 顺序执行
echo "第一步"
echo "第二步"
echo "第三步"
# 命令分隔符
date; pwd; whoami
# 命令分组
{ echo "开始"; ls -l; echo "结束"; } > output.txt
# 子Shell执行
(echo "在子Shell中"; cd /tmp; pwd)
echo "回到原目录: $(pwd)"2. 输入输出
#!/bin/bash
# io_demo.sh
# 标准输出
echo "正常输出"
# 标准错误
echo "错误信息" >&2
# 重定向
echo "输出到文件" > output.txt
echo "追加到文件" >> output.txt
ls nofile 2> error.txt
ls -l > all.txt 2>&1
ls -l &> all_output.txt
# 输入重定向
cat << EOF
多行文本
第二行
第三行
EOF
# 管道
cat /etc/passwd | grep root
ls -l | wc -l五、判断语句
1. if 语句
#!/bin/bash
# if_demo.sh
# 基本语法
if [ condition ]; then
commands
fi
# if-else
if [ condition ]; then
commands1
else
commands2
fi
# if-elif-else
if [ condition1 ]; then
commands1
elif [ condition2 ]; then
commands2
else
commands3
fi2. 测试表达式
#!/bin/bash
# test_expressions.sh
# 字符串比较
str1="hello"
str2="world"
if [ "$str1" = "$str2" ]; then
echo "字符串相等"
fi
if [ "$str1" != "$str2" ]; then
echo "字符串不相等"
fi
if [ -z "$str1" ]; then
echo "字符串为空"
fi
if [ -n "$str1" ]; then
echo "字符串非空"
fi
# 数值比较
num1=10
num2=20
if [ $num1 -eq $num2 ]; then
echo "相等"
fi
if [ $num1 -ne $num2 ]; then
echo "不相等"
fi
if [ $num1 -gt $num2 ]; then
echo "大于"
fi
if [ $num1 -lt $num2 ]; then
echo "小于"
fi
if [ $num1 -ge 10 ]; then
echo "大于等于10"
fi
if [ $num1 -le 10 ]; then
echo "小于等于10"
fi
# 文件测试
file="test.txt"
if [ -f "$file" ]; then
echo "是普通文件"
fi
if [ -d "$file" ]; then
echo "是目录"
fi
if [ -e "$file" ]; then
echo "文件存在"
fi
if [ -r "$file" ]; then
echo "可读"
fi
if [ -w "$file" ]; then
echo "可写"
fi
if [ -x "$file" ]; then
echo "可执行"
fi
if [ -s "$file" ]; then
echo "文件不为空"
fi3. case 语句
#!/bin/bash
# case_demo.sh
# 基本语法
case "$variable" in
pattern1)
commands1
;;
pattern2)
commands2
;;
pattern3|pattern4)
commands3
;;
*)
default_commands
;;
esac
# 示例
fruit="apple"
case "$fruit" in
apple)
echo "It's an apple"
;;
banana|orange)
echo "It's a banana or orange"
;;
"water melon")
echo "It's a water melon"
;;
*)
echo "Unknown fruit"
;;
esac六、循环语句
1. for 循环
#!/bin/bash
# for_loop.sh
# 基本语法
for variable in list; do
commands
done
# 示例1:遍历列表
for fruit in apple banana orange; do
echo "I like $fruit"
done
# 示例2:遍历文件
for file in *.txt; do
echo "Processing $file"
done
# 示例3:C风格for循环
for ((i=1; i<=5; i++)); do
echo "Count: $i"
done
# 示例4:遍历命令输出
for user in $(cut -d: -f1 /etc/passwd | head -5); do
echo "User: $user"
done
# 示例5:遍历数组
fruits=("apple" "banana" "orange")
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done2. while 循环
#!/bin/bash
# while_loop.sh
# 基本语法
while condition; do
commands
done
# 示例1:计数器
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
((count++))
done
# 示例2:读取文件
while IFS= read -r line; do
echo "Line: $line"
done < /etc/passwd
# 示例3:无限循环
while true; do
echo "Press Ctrl+C to stop"
sleep 1
done
# 示例4:读取输入
echo "Type 'quit' to exit"
while read input; do
if [ "$input" = "quit" ]; then
break
fi
echo "You typed: $input"
done3. until 循环
#!/bin/bash
# until_loop.sh
# 基本语法
until condition; do
commands
done
# 示例1:等待条件满足
count=1
until [ $count -gt 5 ]; do
echo "Count: $count"
((count++))
done
# 示例2:等待服务启动
echo "Waiting for service to start..."
until curl -f http://localhost:8080/health > /dev/null 2>&1; do
echo "Service not ready, waiting..."
sleep 2
done
echo "Service is up!"4. 循环控制
#!/bin/bash
# loop_control.sh
# break - 跳出循环
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo "i = $i"
done
# continue - 跳过本次循环
for i in {1..5}; do
if [ $i -eq 3 ]; then
continue
fi
echo "Processing $i"
done
# 嵌套循环
for i in {1..3}; do
echo "Outer loop: $i"
for j in {1..3}; do
echo " Inner loop: $j"
done
done七、函数
1. 函数定义
#!/bin/bash
# functions_demo.sh
# 定义函数
function say_hello {
echo "Hello, $1!"
}
# 另一种定义方式
greet() {
local name="$1" # 局部变量
echo "Greetings, $name!"
return 0
}
# 调用函数
say_hello "John"
greet "Alice"
# 返回值
get_date() {
date +%Y-%m-%d
}
# 捕获函数输出
today=$(get_date)
echo "Today is $today"2. 函数参数
#!/bin/bash
# function_params.sh
# 带参数的函数
print_info() {
echo "参数个数: $#"
echo "第一个参数: $1"
echo "所有参数: $@"
echo "参数列表: $*"
}
print_info arg1 arg2 arg3
# 返回值示例
add() {
local result=$(( $1 + $2 ))
return $result
}
add 10 20
echo "10 + 20 = $?" # 注意:返回值只能是0-255八、实用脚本示例
1. 文件备份脚本
#!/bin/bash
# backup.sh
BACKUP_DIR="/var/backups"
SOURCE_DIR="$1"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/backup_${DATE}.tar.gz"
# 检查参数
if [ $# -ne 1 ]; then
echo "用法: $0 <源目录>"
exit 1
fi
# 检查源目录是否存在
if [ ! -d "$SOURCE_DIR" ]; then
echo "错误: 目录 $SOURCE_DIR 不存在"
exit 1
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 执行备份
echo "开始备份 $SOURCE_DIR ..."
tar -czf "$BACKUP_FILE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
# 检查备份结果
if [ $? -eq 0 ]; then
echo "备份成功: $BACKUP_FILE"
# 删除7天前的备份
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "已清理7天前的备份"
else
echo "备份失败!"
exit 1
fi2. 系统监控脚本
#!/bin/bash
# system_monitor.sh
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 日志文件
LOG_FILE="/var/log/system_monitor.log"
# 记录日志
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 检查CPU使用率
check_cpu() {
local threshold=80
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
if [ $(echo "$cpu_usage > $threshold" | bc) -eq 1 ]; then
log "${RED}警告: CPU使用率 ${cpu_usage}% 超过阈值 ${threshold}%${NC}"
return 1
else
log "${GREEN}正常: CPU使用率 ${cpu_usage}%${NC}"
return 0
fi
}
# 检查内存使用率
check_memory() {
local threshold=85
local mem_total=$(free | grep Mem | awk '{print $2}')
local mem_used=$(free | grep Mem | awk '{print $3}')
local mem_usage=$((mem_used * 100 / mem_total))
if [ $mem_usage -gt $threshold ]; then
log "${RED}警告: 内存使用率 ${mem_usage}% 超过阈值 ${threshold}%${NC}"
return 1
else
log "${GREEN}正常: 内存使用率 ${mem_usage}%${NC}"
return 0
fi
}
# 检查磁盘空间
check_disk() {
local threshold=90
local disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $disk_usage -gt $threshold ]; then
log "${RED}警告: 磁盘使用率 ${disk_usage}% 超过阈值 ${threshold}%${NC}"
return 1
else
log "${GREEN}正常: 磁盘使用率 ${disk_usage}%${NC}"
return 0
fi
}
# 主函数
main() {
log "===== 系统监控开始 ====="
local errors=0
check_cpu || ((errors++))
check_memory || ((errors++))
check_disk || ((errors++))
log "===== 系统监控结束 ====="
log "发现 $errors 个问题"
if [ $errors -gt 0 ]; then
# 发送警报
echo "系统监控发现 $errors 个问题,请查看日志: $LOG_FILE" | mail -s "系统警报" admin@example.com
return 1
fi
return 0
}
# 执行主函数
main "$@"3. 综合应用:网站部署脚本
#!/bin/bash
# deploy_website.sh
# 配置
WEB_DIR="/var/www/html"
BACKUP_DIR="/var/backups/web"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/deploy_${DATE}.log"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 日志函数
log() {
local level="$1"
local message="$2"
local color=""
case "$level" in
INFO) color="$GREEN" ;;
WARN) color="$YELLOW" ;;
ERROR) color="$RED" ;;
*) color="$BLUE" ;;
esac
echo -e "${color}[$(date '+%H:%M:%S')] [$level] ${message}${NC}" | tee -a "$LOG_FILE"
}
# 检查参数
if [ $# -ne 1 ]; then
echo "用法: $0 <网站包文件>"
exit 1
fi
PACKAGE="$1"
# 检查包文件
if [ ! -f "$PACKAGE" ]; then
log "ERROR" "包文件 $PACKAGE 不存在"
exit 1
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 1. 备份当前网站
backup_website() {
log "INFO" "开始备份当前网站..."
local backup_file="${BACKUP_DIR}/website_${DATE}.tar.gz"
if tar -czf "$backup_file" -C "$WEB_DIR" . 2>> "$LOG_FILE"; then
log "INFO" "备份成功: $backup_file"
return 0
else
log "ERROR" "备份失败"
return 1
fi
}
# 2. 验证包文件
validate_package() {
log "INFO" "验证包文件..."
if file "$PACKAGE" | grep -q "gzip compressed"; then
log "INFO" "包文件验证通过"
return 0
else
log "ERROR" "包文件格式不正确"
return 1
fi
}
# 3. 停止Web服务
stop_service() {
log "INFO" "停止Web服务..."
if systemctl stop nginx 2>> "$LOG_FILE"; then
log "INFO" "Nginx已停止"
return 0
else
log "WARN" "停止Nginx失败,尝试强制停止..."
pkill -9 nginx
return 0
fi
}
# 4. 部署新网站
deploy_new() {
log "INFO" "开始部署新网站..."
# 清理原目录
rm -rf "${WEB_DIR}_new"
mkdir -p "${WEB_DIR}_new"
# 解压新包
if tar -xzf "$PACKAGE" -C "${WEB_DIR}_new" 2>> "$LOG_FILE"; then
log "INFO" "解压成功"
# 设置权限
chown -R www-data:www-data "${WEB_DIR}_new"
chmod -R 755 "${WEB_DIR}_new"
return 0
else
log "ERROR" "解压失败"
return 1
fi
}
# 5. 切换网站
switch_website() {
log "INFO" "切换网站..."
# 备份原网站
mv "$WEB_DIR" "${WEB_DIR}_old"
# 切换到新网站
mv "${WEB_DIR}_new" "$WEB_DIR"
log "INFO" "网站切换完成"
return 0
}
# 6. 启动Web服务
start_service() {
log "INFO" "启动Web服务..."
if systemctl start nginx 2>> "$LOG_FILE"; then
log "INFO" "Nginx已启动"
return 0
else
log "ERROR" "启动Nginx失败"
return 1
fi
}
# 7. 健康检查
health_check() {
log "INFO" "执行健康检查..."
local max_retry=5
local retry_count=0
while [ $retry_count -lt $max_retry ]; do
if curl -f http://localhost/health > /dev/null 2>&1; then
log "INFO" "健康检查通过"
return 0
fi
((retry_count++))
log "WARN" "健康检查失败,重试 $retry_count/$max_retry..."
sleep 3
done
log "ERROR" "健康检查失败"
return 1
}
# 8. 回滚函数
rollback() {
log "ERROR" "部署失败,执行回滚..."
# 恢复原网站
if [ -d "${WEB_DIR}_old" ]; then
rm -rf "$WEB_DIR"
mv "${WEB_DIR}_old" "$WEB_DIR"
log "INFO" "网站已回滚"
fi
# 尝试启动服务
systemctl start nginx
}
# 主部署流程
main() {
log "INFO" "========== 开始网站部署 =========="
# 执行步骤
steps=(
"备份当前网站:backup_website"
"验证包文件:validate_package"
"停止Web服务:stop_service"
"部署新网站:deploy_new"
"切换网站:switch_website"
"启动Web服务:start_service"
"健康检查:health_check"
)
local failed_step=""
for step in "${steps[@]}"; do
local step_name="${step%:*}"
local step_func="${step#*:}"
log "INFO" "执行: $step_name"
if ! $step_func; then
failed_step="$step_name"
break
fi
done
if [ -n "$failed_step" ]; then
log "ERROR" "部署失败在步骤: $failed_step"
rollback
log "INFO" "========== 部署失败 =========="
return 1
else
# 清理旧备份
find "$BACKUP_DIR" -name "website_*.tar.gz" -mtime +30 -delete
log "INFO" "已清理30天前的备份"
log "INFO" "========== 部署成功 =========="
return 0
fi
}
# 异常处理
trap 'log "ERROR" "脚本被中断"; rollback; exit 1' INT TERM
# 执行主函数
if main; then
exit 0
else
exit 1
fi九、最佳实践
1. 脚本模板
#!/bin/bash
# script_template.sh
set -euo pipefail # 严格模式
# -e: 遇到错误退出
# -u: 使用未定义变量时报错
# -o pipefail: 管道中任何命令失败则整个失败
# 配置
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
readonly LOG_FILE="/var/log/${SCRIPT_NAME}.log"
# 颜色定义
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m'
# 日志函数
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
# 错误处理
error_exit() {
log "ERROR" "$1"
exit 1
}
# 使用帮助
usage() {
cat << EOF
用法: $SCRIPT_NAME [选项] <参数>
描述: 这是一个脚本模板
选项:
-h, --help 显示帮助信息
-v, --version 显示版本信息
-d, --debug 调试模式
示例:
$SCRIPT_NAME --debug arg1
EOF
exit 0
}
# 解析参数
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
;;
-v|--version)
echo "$SCRIPT_NAME version 1.0.0"
exit 0
;;
-d|--debug)
set -x
shift
;;
--)
shift
break
;;
-*)
error_exit "未知选项: $1"
;;
*)
break
;;
esac
done
}
# 主函数
main() {
parse_args "$@"
log "INFO" "脚本开始执行"
# 你的代码逻辑
log "INFO" "脚本执行完成"
}
# 入口点
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi2. 调试技巧
#!/bin/bash
# debug_demo.sh
# 调试模式
# bash -x script.sh
# 或在脚本中
set -x
# 调试代码
set +x
# 调试特定部分
echo "开始调试"
PS4='+ ${LINENO}: ' # 显示行号
set -x
# 调试的代码
set +x
echo "调试结束"
# 使用trap调试
trap 'echo "在行号 $LINENO 退出,退出码: $?"' EXIT
# 记录所有输出
exec 2> debug.log
exec 1>&2
set -x十、总结
Shell 脚本要点总结:
- #! 指定解释器
- 变量使用要加引号
- 使用
set -euo pipefail防止错误 - 总是验证输入参数
- 使用函数组织代码
- 添加适当的日志记录
- 处理错误和异常
- 添加帮助文档
运行脚本:
# 添加执行权限
chmod +x script.sh
# 运行
./script.sh
# 调试运行
bash -x script.sh
# 语法检查
bash -n script.sh这个教程涵盖了 Shell 脚本编程的主要方面,从基础到高级应用。建议按照顺序学习,并通过实际编写脚本来加深理解。s
作者:严锋 创建时间:2023-09-14 16:33
最后编辑:严锋 更新时间:2025-12-25 10:39
最后编辑:严锋 更新时间:2025-12-25 10:39