Shell脚本编程
Shell脚本基础
1. 什么是Shell脚本
Shell脚本概念
# Shell脚本是包含Shell命令的文本文件
# 可以自动化执行系统管理任务
# 支持变量、条件判断、循环等编程结构
第一个Shell脚本
#!/bin/bash
# 这是一个简单的Shell脚本
echo "Hello, World!"
echo "当前时间: $(date)"
echo "当前用户: $(whoami)"
echo "当前目录: $(pwd)"
脚本执行方式
# 方式1:直接执行
chmod +x script.sh
./script.sh
# 方式2:使用bash执行
bash script.sh
# 方式3:使用source执行
source script.sh
# 或
. script.sh
2. Shebang行
常用Shebang
#!/bin/bash # 使用bash解释器
#!/bin/sh # 使用sh解释器
#!/usr/bin/env bash # 使用env查找bash
#!/usr/bin/env python3 # Python脚本
#!/usr/bin/env node # Node.js脚本
变量和参数
1. 变量定义和使用
变量定义
#!/bin/bash
# 定义变量 (注意等号两边不能有空格)
name="张三"
age=25
city="北京"
# 使用变量
echo "姓名: $name"
echo "年龄: $age"
echo "城市: ${city}" # 推荐使用花括号
# 只读变量
readonly PI=3.14159
# PI=3.14 # 这会报错
# 删除变量
unset age
变量类型
#!/bin/bash
# 字符串变量
str1="Hello World"
str2='Single quotes'
str3="包含变量: $str1"
# 数组变量
fruits=("苹果" "香蕉" "橙子")
echo "第一个水果: ${fruits[0]}"
echo "所有水果: ${fruits[@]}"
echo "数组长度: ${#fruits[@]}"
# 环境变量
export MY_VAR="这是环境变量"
2. 特殊变量
位置参数
#!/bin/bash
# 脚本名: params.sh
echo "脚本名: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "第三个参数: $3"
echo "所有参数: $@"
echo "参数个数: $#"
echo "所有参数 (作为一个字符串): $*"
# 使用: ./params.sh arg1 arg2 arg3
特殊变量
#!/bin/bash
echo "当前进程ID: $$"
echo "上一个命令的退出状态: $?"
echo "后台运行的最后一个进程ID: $!"
# 命令执行状态
ls /nonexistent 2>/dev/null
if [ $? -eq 0 ]; then
echo "命令执行成功"
else
echo "命令执行失败"
fi
3. 变量操作
字符串操作
#!/bin/bash
str="Hello World Linux"
# 字符串长度
echo "字符串长度: ${#str}"
# 字符串截取
echo "从位置6开始: ${str:6}"
echo "从位置6开始,取5个字符: ${str:6:5}"
# 字符串替换
echo "替换第一个: ${str/o/O}" # Hello WOrld Linux
echo "替换所有: ${str//o/O}" # HellO WOrld Linux
# 字符串删除
echo "删除最短匹配: ${str#*o}" # llo World Linux
echo "删除最长匹配: ${str##*o}" # rld Linux
echo "从尾部删除最短: ${str%o*}" # Hello World Lin
echo "从尾部删除最长: ${str%%o*}" # Hell
变量默认值
#!/bin/bash
# 变量默认值设置
echo "USER变量: ${USER:-默认用户}"
echo "TEMP变量: ${TEMP:=临时目录}"
# 检查变量是否设置
echo "HOME变量: ${HOME:?HOME变量未设置}"
# 变量存在时使用替代值
echo "PATH存在时显示: ${PATH:+PATH已设置}"
条件判断
1. test命令和[]
文件测试
#!/bin/bash
file="/etc/passwd"
# 文件存在性测试
if [ -e "$file" ]; then
echo "文件存在"
fi
if [ -f "$file" ]; then
echo "是普通文件"
fi
if [ -d "/etc" ]; then
echo "/etc是目录"
fi
if [ -r "$file" ]; then
echo "文件可读"
fi
if [ -w "$file" ]; then
echo "文件可写"
fi
if [ -x "/bin/ls" ]; then
echo "/bin/ls可执行"
fi
字符串测试
#!/bin/bash
str1="hello"
str2="world"
str3=""
# 字符串比较
if [ "$str1" = "$str2" ]; then
echo "字符串相等"
else
echo "字符串不相等"
fi
# 字符串长度测试
if [ -z "$str3" ]; then
echo "字符串为空"
fi
if [ -n "$str1" ]; then
echo "字符串不为空"
fi
# 字符串包含 (需要使用[[ ]])
if [[ "$str1" == *"ell"* ]]; then
echo "str1包含ell"
fi
数值比较
#!/bin/bash
num1=10
num2=20
if [ "$num1" -eq "$num2" ]; then
echo "数值相等"
elif [ "$num1" -lt "$num2" ]; then
echo "num1小于num2"
elif [ "$num1" -gt "$num2" ]; then
echo "num1大于num2"
fi
# 数值比较操作符
# -eq 等于
# -ne 不等于
# -lt 小于
# -le 小于等于
# -gt 大于
# -ge 大于等于
2. if语句
if-else结构
#!/bin/bash
read -p "请输入一个数字: " number
if [ "$number" -gt 0 ]; then
echo "正数"
elif [ "$number" -lt 0 ]; then
echo "负数"
else
echo "零"
fi
复合条件
#!/bin/bash
age=25
gender="male"
# 逻辑与 (&&)
if [ "$age" -ge 18 ] && [ "$gender" = "male" ]; then
echo "成年男性"
fi
# 逻辑或 (||)
if [ "$age" -lt 18 ] || [ "$age" -gt 65 ]; then
echo "非工作年龄"
fi
# 使用[[ ]]的复合条件
if [[ $age -ge 18 && $gender == "male" ]]; then
echo "成年男性 (使用[[ ]])"
fi
3. case语句
case结构
#!/bin/bash
read -p "请选择操作 (start/stop/restart/status): " action
case $action in
start)
echo "启动服务..."
;;
stop)
echo "停止服务..."
;;
restart)
echo "重启服务..."
;;
status)
echo "查看状态..."
;;
*)
echo "无效选项"
exit 1
;;
esac
模式匹配
#!/bin/bash
read -p "请输入文件名: " filename
case $filename in
*.txt)
echo "文本文件"
;;
*.jpg|*.png|*.gif)
echo "图片文件"
;;
*.sh)
echo "Shell脚本"
;;
[Mm]akefile)
echo "Makefile"
;;
*)
echo "未知文件类型"
;;
esac
循环结构
1. for循环
基本for循环
#!/bin/bash
# 遍历列表
for fruit in apple banana orange; do
echo "水果: $fruit"
done
# 遍历数组
fruits=("苹果" "香蕉" "橙子")
for fruit in "${fruits[@]}"; do
echo "水果: $fruit"
done
# 遍历文件
for file in *.txt; do
if [ -f "$file" ]; then
echo "处理文件: $file"
fi
done
C风格for循环
#!/bin/bash
# C风格循环
for ((i=1; i<=10; i++)); do
echo "数字: $i"
done
# 计算1到100的和
sum=0
for ((i=1; i<=100; i++)); do
sum=$((sum + i))
done
echo "1到100的和: $sum"
遍历命令输出
#!/bin/bash
# 遍历用户列表
for user in $(cut -d: -f1 /etc/passwd); do
echo "用户: $user"
done
# 遍历目录
for dir in $(ls -d */); do
echo "目录: $dir"
done
2. while循环
while循环基础
#!/bin/bash
# 基本while循环
count=1
while [ $count -le 5 ]; do
echo "计数: $count"
count=$((count + 1))
done
# 读取文件内容
while read line; do
echo "行内容: $line"
done < /etc/passwd
无限循环
#!/bin/bash
# 无限循环
while true; do
echo "按Ctrl+C退出"
sleep 1
done
# 或使用while :
while :; do
echo "无限循环"
sleep 1
done
3. until循环
until循环
#!/bin/bash
# until循环 (条件为假时执行)
count=1
until [ $count -gt 5 ]; do
echo "计数: $count"
count=$((count + 1))
done
4. 循环控制
break和continue
#!/bin/bash
# break示例
for i in {1..10}; do
if [ $i -eq 6 ]; then
break
fi
echo "数字: $i"
done
# continue示例
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue
fi
echo "奇数: $i"
done
函数
1. 函数定义和调用
基本函数
#!/bin/bash
# 函数定义方式1
function greet() {
echo "Hello, $1!"
}
# 函数定义方式2
say_goodbye() {
echo "Goodbye, $1!"
}
# 函数调用
greet "World"
say_goodbye "Linux"
函数参数和返回值
#!/bin/bash
# 带参数的函数
add_numbers() {
local num1=$1
local num2=$2
local result=$((num1 + num2))
echo $result
}
# 带返回值的函数
is_even() {
local number=$1
if [ $((number % 2)) -eq 0 ]; then
return 0 # 真
else
return 1 # 假
fi
}
# 使用函数
result=$(add_numbers 10 20)
echo "10 + 20 = $result"
if is_even 4; then
echo "4是偶数"
fi
2. 局部变量和全局变量
变量作用域
#!/bin/bash
global_var="全局变量"
test_scope() {
local local_var="局部变量"
global_var="修改后的全局变量"
echo "函数内部:"
echo " 局部变量: $local_var"
echo " 全局变量: $global_var"
}
echo "函数调用前: $global_var"
test_scope
echo "函数调用后: $global_var"
# echo "局部变量: $local_var" # 这会报错,局部变量不可访问
输入输出
1. 用户输入
read命令
#!/bin/bash
# 基本输入
read -p "请输入您的姓名: " name
echo "您好, $name!"
# 隐藏输入 (密码)
read -s -p "请输入密码: " password
echo
echo "密码已设置"
# 设置超时
if read -t 10 -p "请在10秒内输入 (超时自动继续): " input; then
echo "您输入了: $input"
else
echo "输入超时"
fi
# 限制输入字符数
read -n 1 -p "按任意键继续..." key
echo
2. 输出重定向
重定向操作
#!/bin/bash
# 输出重定向
echo "写入文件" > output.txt
echo "追加内容" >> output.txt
# 错误重定向
ls /nonexistent 2> error.log
ls /nonexistent 2>> error.log
# 同时重定向标准输出和错误输出
command > output.log 2>&1
command &> output.log
# 丢弃输出
command > /dev/null 2>&1
3. 管道和过滤
管道使用
#!/bin/bash
# 管道示例
ps aux | grep nginx | awk '{print $2}'
# 统计文件行数
cat /etc/passwd | wc -l
# 排序和去重
cat file.txt | sort | uniq
# 查找最大的文件
ls -la | sort -k5 -nr | head -5
实用脚本示例
1. 系统监控脚本
系统信息收集
#!/bin/bash
# 系统监控脚本
echo "=== 系统监控报告 ==="
echo "生成时间: $(date)"
echo
echo "=== 系统信息 ==="
echo "主机名: $(hostname)"
echo "内核版本: $(uname -r)"
echo "系统负载: $(uptime | awk -F'load average:' '{print $2}')"
echo
echo "=== 内存使用 ==="
free -h
echo
echo "=== 磁盘使用 ==="
df -h
echo
echo "=== CPU使用率最高的进程 ==="
ps aux --sort=-%cpu | head -6
echo
echo "=== 内存使用率最高的进程 ==="
ps aux --sort=-%mem | head -6
2. 备份脚本
自动备份脚本
#!/bin/bash
# 备份脚本
SOURCE_DIR="/home/user/documents"
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_$DATE.tar.gz"
# 检查源目录
if [ ! -d "$SOURCE_DIR" ]; then
echo "错误: 源目录 $SOURCE_DIR 不存在"
exit 1
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 执行备份
echo "开始备份 $SOURCE_DIR..."
tar -czf "$BACKUP_DIR/$BACKUP_FILE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
if [ $? -eq 0 ]; then
echo "备份成功: $BACKUP_DIR/$BACKUP_FILE"
# 删除7天前的备份
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "已清理7天前的备份文件"
else
echo "备份失败"
exit 1
fi
3. 日志分析脚本
访问日志分析
#!/bin/bash
# 日志分析脚本
LOG_FILE="/var/log/nginx/access.log"
if [ ! -f "$LOG_FILE" ]; then
echo "日志文件不存在: $LOG_FILE"
exit 1
fi
echo "=== Nginx访问日志分析 ==="
echo "分析文件: $LOG_FILE"
echo "分析时间: $(date)"
echo
echo "=== 总访问量 ==="
total_requests=$(wc -l < "$LOG_FILE")
echo "总请求数: $total_requests"
echo
echo "=== 状态码统计 ==="
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -nr
echo
echo "=== 访问量最高的IP ==="
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10
echo
echo "=== 访问量最高的页面 ==="
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10
调试和错误处理
1. 脚本调试
调试选项
#!/bin/bash
# 启用调试模式
set -x # 显示执行的命令
set -e # 遇到错误立即退出
set -u # 使用未定义变量时报错
# 或在脚本开头使用
#!/bin/bash -x
# 调试特定部分
set -x
echo "这部分会显示调试信息"
set +x
echo "这部分不显示调试信息"
2. 错误处理
错误处理机制
#!/bin/bash
# 错误处理函数
error_exit() {
echo "错误: $1" >&2
exit 1
}
# 检查命令执行结果
backup_files() {
cp important_file.txt backup/ || error_exit "备份失败"
echo "备份成功"
}
# 使用trap捕获信号
cleanup() {
echo "清理临时文件..."
rm -f /tmp/temp_file
}
trap cleanup EXIT
# 脚本主体
echo "开始执行脚本..."
backup_files
echo "脚本执行完成"
总结
Shell脚本编程要点:
- 基础语法:掌握变量、条件判断、循环结构
- 函数使用:编写可重用的函数模块
- 输入输出:处理用户输入和文件操作
- 错误处理:实现健壮的错误处理机制
- 实际应用:编写系统监控、备份等实用脚本
下一课预告
在下一课中,我们将学习Web服务器配置,包括:
- Apache服务器配置
- Nginx服务器配置
- SSL证书配置
- 虚拟主机设置
💡 小贴士:Shell脚本是Linux系统管理的重要工具。建议从简单脚本开始练习,逐步掌握复杂的编程结构,多写多练才能熟练掌握。
📚 文章对你有帮助?请关注我的公众号,万分感谢!
获取更多优质技术文章,第一时间掌握最新技术动态

关注公众号
第一时间获取最新技术文章

添加微信
技术交流 · 问题答疑 · 学习指导
评论讨论
欢迎留下你的想法和建议