1. Shell脚本编程入门指南
作为一名Linux系统管理员,我每天都要和Shell脚本打交道。Shell脚本就像一把瑞士军刀,能自动化处理各种重复性工作。记得刚入行时,我花了整整一周手动处理日志文件,而同事用10行脚本就搞定了。这个教训让我深刻认识到Shell脚本的价值。
Shell脚本本质上是包含一系列命令的文本文件,由Shell解释执行。它特别适合处理文件操作、系统管理和任务自动化。对于运维人员、开发者和数据分析师来说,掌握Shell脚本能极大提升工作效率。
提示:学习Shell脚本不需要编程基础,但需要对Linux基本命令有所了解。如果你熟悉ls、cd、grep这些命令,就已经具备了入门条件。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Shell脚本基础与环境搭建
2.1 选择适合的Shell环境
常见的Shell有Bash、Zsh、Ksh等。Bash(Bourne Again Shell)是最广泛使用的,也是大多数Linux发行版的默认Shell。我推荐从Bash开始学习,因为:
- 兼容性好,几乎所有Unix-like系统都预装
- 文档丰富,社区支持完善
- 功能全面,支持大多数Shell脚本需求
检查当前使用的Shell:
bash复制echo $SHELL
如果显示/bin/bash,说明你已经在使用Bash。如果不是,可以通过以下命令切换:
bash复制chsh -s /bin/bash
2.2 编写第一个Shell脚本
创建一个名为hello.sh的文件:
bash复制#!/bin/bash
# 这是我的第一个Shell脚本
echo "Hello World!"
关键点说明:
#!/bin/bash称为shebang,指定脚本使用的解释器#开头的行是注释,不会被执行echo命令用于输出文本
给脚本添加执行权限:
bash复制chmod +x hello.sh
执行脚本的三种方式:
bash复制./hello.sh # 直接执行
bash hello.sh # 指定解释器执行
source hello.sh # 在当前Shell环境执行
注意:直接使用./hello.sh需要脚本有执行权限,且当前目录在PATH环境变量中。新手常见错误是忘记加执行权限,导致"Permission denied"错误。
3. Shell脚本核心语法精要
3.1 变量与数据类型
Shell是弱类型语言,变量无需声明类型。但使用时有几个关键点:
定义变量:
bash复制name="John" # 字符串
count=10 # 整数
files=$(ls) # 命令执行结果
使用变量:
bash复制echo $name # 输出变量值
echo ${name} # 推荐这种写法,明确变量边界
特殊变量:
bash复制$0 # 脚本名称
$1-$9 # 脚本参数
$# # 参数个数
$? # 上条命令的退出状态
$$ # 当前进程ID
经验:变量赋值时等号两边不能有空格,这是Shell语法严格要求的。新手常犯的错误是写成
name = "John"导致报错。
3.2 算术运算
Shell中进行数学运算有几种方式:
使用expr(旧式):
bash复制result=$(expr 5 + 3)
使用$(( ))(推荐):
bash复制result=$((5 * 3)) # 乘法
result=$((2 ** 3)) # 幂运算
使用let命令:
bash复制let "result = 5 / 2"
注意:Shell默认只做整数运算。如果需要浮点运算,可以借助bc命令:
bash复制echo "scale=2; 5/3" | bc # 输出1.66
3.3 条件判断与分支结构
if语句基本结构:
bash复制if [ 条件 ]; then
# 命令
elif [ 条件 ]; then
# 命令
else
# 命令
fi
常用测试条件:
字符串比较:
bash复制[ "$a" = "$b" ] # 等于
[ "$a" != "$b" ] # 不等于
[ -z "$a" ] # 为空
[ -n "$a" ] # 不为空
数值比较:
bash复制[ $a -eq $b ] # 等于 equal
[ $a -ne $b ] # 不等于 not equal
[ $a -gt $b ] # 大于 greater than
[ $a -lt $b ] # 小于 less than
文件测试:
bash复制[ -e file ] # 文件存在
[ -f file ] # 是普通文件
[ -d file ] # 是目录
[ -r file ] # 可读
[ -w file ] # 可写
[ -x file ] # 可执行
避坑指南:条件判断中括号内两侧必须有空格,变量引用最好加双引号,避免空变量导致语法错误。例如应该写
[ "$name" = "John" ]而不是[$name="John"]。
3.4 循环结构
for循环:
bash复制for i in 1 2 3 4 5; do
echo "Number: $i"
done
# 遍历当前目录文件
for file in *; do
echo "Processing $file"
done
while循环:
bash复制count=1
while [ $count -le 5 ]; do
echo "Count: $count"
let "count++"
done
until循环(与while相反):
bash复制count=1
until [ $count -gt 5 ]; do
echo "Count: $count"
let "count++"
done
实用技巧:循环中可以使用
break和continue控制流程,与其他编程语言类似。在处理大文件时,合理使用这些控制语句能提升脚本效率。
4. Shell脚本高级特性
4.1 函数定义与使用
定义函数:
bash复制function greet() {
local name=$1 # local定义局部变量
echo "Hello, $name"
return 0 # 返回值
}
调用函数:
bash复制greet "Alice"
echo "函数返回值: $?" # 获取返回值
经验分享:函数内使用
local声明局部变量,避免污染全局命名空间。Shell函数只能返回整数(0-255),通常0表示成功,非0表示错误。
4.2 数组操作
定义数组:
bash复制fruits=("Apple" "Banana" "Orange")
访问数组元素:
bash复制echo ${fruits[0]} # 第一个元素
echo ${fruits[@]} # 所有元素
echo ${#fruits[@]} # 数组长度
遍历数组:
bash复制for fruit in "${fruits[@]}"; do
echo "$fruit"
done
关联数组(Bash 4.0+):
bash复制declare -A user
user=([name]="John" [age]=30)
echo "${user[name]} is ${user[age]} years old"
4.3 输入输出重定向
标准输入输出:
- 0 标准输入(stdin)
- 1 标准输出(stdout)
- 2 标准错误(stderr)
常见用法:
bash复制command > file # 输出重定向到文件(覆盖)
command >> file # 输出追加到文件
command < file # 从文件读取输入
command 2> file # 错误输出重定向
command &> file # 标准输出和错误都重定向
管道:
bash复制cat file.txt | grep "error" | wc -l
高级技巧:使用
exec可以重定向当前Shell的输入输出。例如将脚本所有输出同时记录到日志文件:
bash复制exec &> >(tee -a script.log)
5. 实用Shell脚本案例解析
5.1 系统监控脚本
bash复制#!/bin/bash
# 系统资源监控脚本
# 阈值设置
CPU_WARN=80
MEM_WARN=80
DISK_WARN=80
# 获取系统指标
cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')
disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
# 告警检查
alerts=()
[ $(echo "$cpu_usage > $CPU_WARN" | bc -l) -eq 1 ] && alerts+=("CPU使用率过高: ${cpu_usage}%")
[ $(echo "$mem_usage > $MEM_WARN" | bc -l) -eq 1 ] && alerts+=("内存使用率过高: ${mem_usage}%")
[ $disk_usage -gt $DISK_WARN ] && alerts+=("磁盘使用率过高: ${disk_usage}%")
# 发送告警
if [ ${#alerts[@]} -gt 0 ]; then
echo "系统告警:"
printf "%s\n" "${alerts[@]}"
# 这里可以添加邮件或钉钉告警代码
else
echo "系统状态正常"
fi
5.2 日志分析脚本
bash复制#!/bin/bash
# 分析Nginx访问日志
LOG_FILE="/var/log/nginx/access.log"
REPORT_FILE="access_report_$(date +%Y%m%d).txt"
# 统计访问量前10的IP
echo "Top 10 IPs:" > $REPORT_FILE
awk '{print $1}' $LOG_FILE | sort | uniq -c | sort -nr | head -10 >> $REPORT_FILE
# 统计访问量前10的URL
echo -e "\nTop 10 URLs:" >> $REPORT_FILE
awk '{print $7}' $LOG_FILE | sort | uniq -c | sort -nr | head -10 >> $REPORT_FILE
# 统计HTTP状态码
echo -e "\nHTTP Status Codes:" >> $REPORT_FILE
awk '{print $9}' $LOG_FILE | sort | uniq -c | sort -nr >> $REPORT_FILE
echo "报告已生成: $REPORT_FILE"
5.3 文件备份脚本
bash复制#!/bin/bash
# 增量备份脚本
BACKUP_DIR="/backup"
SOURCE_DIR="/data"
DATE=$(date +%Y%m%d)
BACKUP_FILE="${BACKUP_DIR}/backup_${DATE}.tar.gz"
# 检查备份目录是否存在
[ -d "$BACKUP_DIR" ] || mkdir -p "$BACKUP_DIR"
# 创建增量备份
if [ -f "${BACKUP_DIR}/last_backup.txt" ]; then
LAST_DATE=$(cat "${BACKUP_DIR}/last_backup.txt")
find "$SOURCE_DIR" -type f -newermt "$LAST_DATE" -print0 | tar -czvf "$BACKUP_FILE" --null -T -
else
tar -czvf "$BACKUP_FILE" "$SOURCE_DIR"
fi
# 记录本次备份时间
date +%Y-%m-%d > "${BACKUP_DIR}/last_backup.txt"
echo "备份完成: $BACKUP_FILE"
6. Shell脚本调试与优化
6.1 调试技巧
运行脚本时添加调试选项:
bash复制bash -x script.sh # 打印每条执行的命令
bash -v script.sh # 打印脚本原始内容
bash -n script.sh # 只检查语法不执行
在脚本中设置调试选项:
bash复制#!/bin/bash
set -x # 开启调试
# 脚本内容
set +x # 关闭调试
调试建议:对于复杂脚本,可以分段调试。先注释掉大部分代码,逐步放开测试,定位问题区域。
6.2 性能优化
-
减少子进程创建:
- 使用内建命令而非外部命令
- 合并多个命令到一个管道
-
避免不必要的循环:
- 使用awk/sed等文本处理工具替代shell循环
- 批量处理而非逐行处理
-
使用here document替代echo:
bash复制# 不推荐
echo "line1" >> file
echo "line2" >> file
# 推荐
cat <<EOF >> file
line1
line2
EOF
6.3 安全注意事项
- 变量引用加双引号,防止空格和特殊字符问题:
bash复制# 危险写法
rm -rf $DIR/*
# 安全写法
rm -rf "$DIR"/*
- 检查外部输入:
bash复制if [ -z "$1" ]; then
echo "Usage: $0 <filename>"
exit 1
fi
if [ ! -f "$1" ]; then
echo "Error: File $1 not found"
exit 1
fi
- 设置安全选项:
bash复制#!/bin/bash
set -e # 遇到错误立即退出
set -u # 使用未定义变量时报错
set -o pipefail # 管道中任意命令失败则整个管道失败
7. 常见问题解决方案
7.1 错误处理与日志记录
基本错误处理:
bash复制if ! command; then
echo "Error: command failed" >&2
exit 1
fi
记录日志的函数:
bash复制log() {
local level=$1
local message=$2
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo "[${timestamp}] [${level}] ${message}" >> script.log
}
# 使用示例
log "INFO" "脚本开始执行"
log "ERROR" "文件未找到"
7.2 处理包含空格的文件名
错误方式:
bash复制for file in $(ls); do
# 遇到带空格文件名会出错
done
正确方式:
bash复制find . -type f -print0 | while IFS= read -r -d '' file; do
# 安全处理所有文件名
done
7.3 跨平台兼容性
检测操作系统:
bash复制case "$(uname -s)" in
Linux*) machine=Linux;;
Darwin*) machine=Mac;;
CYGWIN*) machine=Cygwin;;
MINGW*) machine=MinGw;;
*) machine="UNKNOWN"
esac
echo "Running on $machine"
处理不同系统的命令差异:
bash复制if [ "$machine" = "Linux" ]; then
# Linux特有命令
alias mycmd='linux-command'
elif [ "$machine" = "Mac" ]; then
# Mac特有命令
alias mycmd='mac-command'
fi
7.4 处理adb shell device not found错误
当使用adb命令出现"device not found"错误时,可以编写检查脚本:
bash复制#!/bin/bash
# 检查设备连接状态
device_status=$(adb devices | grep -v "List of devices" | awk '{print $2}')
if [ "$device_status" != "device" ]; then
echo "错误:设备未连接或未授权"
echo "尝试解决方案:"
echo "1. 检查USB连接线"
echo "2. 检查开发者选项和USB调试是否开启"
echo "3. 执行adb kill-server && adb start-server"
exit 1
fi
# 设备已连接,执行后续命令
adb shell pm list packages
8. Shell脚本资源推荐
8.1 学习资源
-
书籍:
- 《Linux命令行与Shell脚本编程大全》
- 《Shell脚本学习指南》
- 《Bash Cookbook》
-
在线教程:
- Bash官方文档:https://www.gnu.org/software/bash/manual/
- Shell脚本教程:https://www.shellscript.sh/
- Linux命令大全:https://man.linuxde.net/
8.2 实用工具
-
ShellCheck:在线Shell脚本检查工具
bash复制# 安装 apt install shellcheck # Debian/Ubuntu brew install shellcheck # Mac # 使用 shellcheck script.sh -
Bash调试器:bashdb
-
脚本格式化工具:shfmt
8.3 进阶方向
- 正则表达式深入
- awk/sed高级文本处理
- 编写更复杂的函数库
- 开发交互式Shell工具
- 与其他语言(Python/Perl)结合使用
Shell脚本的学习曲线是先易后难。开始时能快速实现简单功能,但要写出健壮、高效的脚本需要不断积累经验。我建议从自动化日常任务开始,逐步挑战更复杂的场景。每次遇到问题都思考"能否用脚本解决",这种思维方式比记住所有命令更重要。
