Shell脚本入门指南
从入门到实战:30分钟掌握Linux自动化脚本编写
目录
什么是Shell脚本
Shell脚本是一个包含一系列命令的文本文件,由Shell解释器逐行执行。它允许你将多个命令组合在一起,通过运行一个脚本来自动完成复杂的任务。
Shell与Bash的区别
- Shell: 提供命令行界面与操作系统交互的程序的统称
- Bash (Bourne-Again SHell): 最常用的Unix/Linux Shell之一,是许多Linux发行版的默认Shell
其他常见的Shell还包括:
- Korn Shell (ksh)
- C Shell (csh)
- Z Shell (zsh)
为什么学习Shell脚本
1. 自动化重复任务
将手动执行的命令序列化为脚本,一键完成复杂操作。
2. 系统管理
批量管理文件、监控系统状态、定时执行任务。
3. 提高开发效率
快速搭建开发环境、自动化部署、日志分析。
4. 跨平台兼容
可在Unix、Linux、macOS上运行,Windows也可通过WSL使用。
5. 与其他工具集成
可与数据库、Web服务器、云服务等其他工具无缝集成。
环境准备
检查当前Shell类型
echo $SHELL
或
ps
查找Bash路径
which bash
常见路径:/bin/bash 或 /usr/bin/bash
创建脚本文件
touch myscript.sh
chmod +x myscript.sh
第一个Shell脚本
Shebang(#!)
每个Shell脚本的第一行应该是shebang,它告诉系统使用哪个解释器来执行脚本:
#!/bin/bash
Hello World示例
创建文件 hello.sh:
#!/bin/bash
# 这是一个注释
echo "Hello, World!"
echo "当前日期: $(date)"
echo "当前用户: $USER"
echo "当前目录: $(pwd)"
执行脚本
# 方法1:直接执行(需要执行权限)
./hello.sh
# 方法2:使用bash命令
bash hello.sh
# 方法3:使用sh命令
sh hello.sh
授予执行权限
chmod +x hello.sh
变量与数据类型
变量定义
#!/bin/bash
# 字符串变量
name="张三"
greeting='Hello'
# 数字变量
age=25
count=100
# 命令替换
current_date=$(date +%Y-%m-%d)
file_count=$(ls -1 | wc -l)
# 使用变量
echo "姓名: $name"
echo "年龄: ${age}"
echo "日期: $current_date"
echo "文件数: $file_count"
变量命名规则
- 只能包含字母、数字和下划线
- 不能以数字开头
- 区分大小写
- 等号两边不能有空格
# 正确
my_var=10
MY_VAR=20
_my_var=30
# 错误
my-var=40 # 包含连字符
my var=50 # 包含空格
1var=60 # 以数字开头
只读变量
#!/bin/bash
readonly PI=3.14159
readonly -r VERSION="1.0.0"
# 以下会报错
# PI=3.14 # 错误:只读变量
删除变量
#!/bin/bash
temp="temporary"
echo $temp # 输出: temporary
unset temp
echo $temp # 输出: 空
特殊变量
| 变量 | 说明 |
|---|---|
$0 | 脚本名称 |
$1 - $9 | 第1到第9个参数 |
${10} | 第10个及以后的参数 |
$# | 参数个数 |
$* | 所有参数(单个字符串) |
$@ | 所有参数(数组) |
$? | 上一个命令的退出状态 |
$$ | 当前进程ID |
$! | 最后一个后台进程的PID |
示例:
#!/bin/bash
echo "脚本名: $0"
echo "参数1: $1"
echo "参数2: $2"
echo "参数总数: $#"
echo "所有参数: $*"
echo "进程ID: $$"
执行:
./script.sh arg1 arg2 arg3
条件判断
if语句
#!/bin/bash
age=18
if [ $age -ge 18 ]; then
echo "已成年"
fi
if-else语句
#!/bin/bash
score=85
if [ $score -ge 60 ]; then
echo "及格"
else
echo "不及格"
fi
if-elif-else语句
#!/bin/bash
score=85
if [ $score -ge 90 ]; then
echo "优秀"
elif [ $score -ge 80 ]; then
echo "良好"
elif [ $score -ge 60 ]; then
echo "及格"
else
echo "不及格"
fi
比较运算符
数字比较
| 运算符 | 说明 |
|---|---|
-eq | 等于 (equal) |
-ne | 不等于 (not equal) |
-gt | 大于 (greater than) |
-ge | 大于等于 (greater or equal) |
-lt | 小于 (less than) |
-le | 小于等于 (less or equal) |
字符串比较
| 运算符 | 说明 |
|---|---|
= | 字符串相等 |
!= | 字符串不相等 |
-z | 字符串为空 |
-n | 字符串不为空 |
文件测试
| 运算符 | 说明 |
|---|---|
-e | 文件存在 |
-f | 是普通文件 |
-d | 是目录 |
-r | 可读 |
-w | 可写 |
-x | 可执行 |
-s | 文件大小大于0 |
逻辑运算符
#!/bin/bash
age=25
has_license=true
# AND 逻辑
if [ $age -ge 18 ] && [ "$has_license" = true ]; then
echo "可以开车"
fi
# OR 逻辑
if [ $age -lt 18 ] || [ $age -ge 65 ]; then
echo "享受优惠票价"
fi
# NOT 逻辑
if [ ! -f "config.txt" ]; then
echo "配置文件不存在"
fi
case语句
#!/bin/bash
echo "请选择操作:"
echo "1) 启动服务"
echo "2) 停止服务"
echo "3) 重启服务"
read choice
case $choice in
1)
echo "正在启动服务..."
;;
2)
echo "正在停止服务..."
;;
3)
echo "正在重启服务..."
;;
*)
echo "无效选择"
;;
esac
循环结构
for循环
基本语法
#!/bin/bash
# 遍历数字
for i in 1 2 3 4 5; do
echo "数字: $i"
done
# 使用seq命令
for i in $(seq 1 5); do
echo "数字: $i"
done
# C风格for循环
for ((i=1; i<=5; i++)); do
echo "数字: $i"
done
遍历文件
#!/bin/bash
# 遍历当前目录的所有.txt文件
for file in *.txt; do
echo "处理文件: $file"
done
# 遍历指定目录
for file in /var/log/*.log; do
echo "日志文件: $file"
done
遍历数组
#!/bin/bash
fruits=("苹果" "香蕉" "橙子" "葡萄")
for fruit in "${fruits[@]}"; do
echo "水果: $fruit"
done
while循环
#!/bin/bash
# 基本while循环
count=1
while [ $count -le 5 ]; do
echo "计数: $count"
((count++))
done
# 读取文件
while IFS= read -r line; do
echo "行内容: $line"
done < "file.txt"
# 无限循环
while true; do
echo "运行中..."
sleep 1
done
until循环
until循环在条件为假时执行,条件为真时停止:
#!/bin/bash
count=1
until [ $count -gt 5 ]; do
echo "计数: $count"
((count++))
done
break和continue
#!/bin/bash
# break - 跳出循环
for i in $(seq 1 10); do
if [ $i -eq 5 ]; then
echo "遇到5,跳出循环"
break
fi
echo "数字: $i"
done
# continue - 跳过本次循环
for i in $(seq 1 10); do
if [ $((i % 2)) -eq 0 ]; then
continue # 跳过偶数
fi
echo "奇数: $i"
done
函数
定义和调用函数
#!/bin/bash
# 定义函数
greeting() {
echo "你好, $1!"
}
# 调用函数
greeting "张三"
greeting "李四"
带返回值的函数
#!/bin/bash
# 返回状态码(0-255)
check_file() {
if [ -f "$1" ]; then
return 0 # 成功
else
return 1 # 失败
fi
}
check_file "test.txt"
if [ $? -eq 0 ]; then
echo "文件存在"
else
echo "文件不存在"
fi
# 返回字符串(通过echo)
get_date() {
echo $(date +%Y-%m-%d)
}
current_date=$(get_date)
echo "今天日期: $current_date"
函数参数
#!/bin/bash
add() {
local a=$1
local b=$2
local sum=$((a + b))
echo "$sum"
}
result=$(add 10 20)
echo "结果: $result"
注意:使用 local 关键字定义局部变量,避免污染全局命名空间。
函数库
创建一个函数库文件 functions.sh:
#!/bin/bash
# 日志函数
log_info() {
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_error() {
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}
# 检查命令是否存在
command_exists() {
command -v "$1" >/dev/null 2>&1
}
在主脚本中使用:
#!/bin/bash
# 导入函数库
source ./functions.sh
# 或 . ./functions.sh
log_info "脚本开始执行"
if command_exists "git"; then
log_info "Git已安装"
else
log_error "Git未安装"
fi
输入输出重定向
标准流
- 标准输入 (stdin) - 文件描述符: 0
- 标准输出 (stdout) - 文件描述符: 1
- 标准错误 (stderr) - 文件描述符: 2
重定向操作符
#!/bin/bash
# 输出重定向(覆盖)
echo "Hello" > output.txt
# 输出重定向(追加)
echo "World" >> output.txt
# 错误重定向
ls nonexistent_dir 2> error.log
# 标准输出和错误输出都重定向
command > output.log 2>&1
# 或(Bash 4+)
command &> output.log
# 输入重定向
sort < input.txt
# Here Document
cat << EOF
这是一段
多行文本
EOF
管道
管道将一个命令的输出作为另一个命令的输入:
#!/bin/bash
# 查看进程
ps aux | grep nginx
# 统计行数
cat file.txt | wc -l
# 排序去重
cat file.txt | sort | uniq
# 复杂管道
ps aux | grep python | grep -v grep | awk '{print $2}'
tee命令
同时输出到屏幕和文件:
#!/bin/bash
# 输出到屏幕并保存到文件
echo "Hello World" | tee output.txt
# 追加模式
echo "Another line" | tee -a output.txt
调试技巧
调试选项
#!/bin/bash
# 在shebang中启用调试
#!/bin/bash -x
# 或使用set命令
set -x # 开启调试
# 你的代码
set +x # 关闭调试
常用调试选项
| 选项 | 说明 |
|---|---|
-x | 显示每个命令及其参数 |
-v | 显示Shell读取的输入行 |
-n | 只读取命令,不执行(语法检查) |
-e | 命令失败时立即退出 |
-u | 使用未定义变量时报错 |
推荐的最佳实践
#!/bin/bash
set -euo pipefail
# -e: 命令失败时退出
# -u: 未定义变量报错
# -o pipefail: 管道中任何命令失败都返回错误
调试示例
#!/bin/bash
set -x
name="张三"
age=25
echo "姓名: $name"
echo "年龄: $age"
set +x
echo "调试模式已关闭"
实用示例
示例1:备份脚本
#!/bin/bash
set -euo pipefail
# 配置
BACKUP_DIR="/backup"
SOURCE_DIR="/data"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/backup_${DATE}.tar.gz"
# 创建备份目录
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"
echo "文件大小: $(du -h $BACKUP_FILE | cut -f1)"
else
echo "备份失败!" >&2
exit 1
fi
# 删除7天前的备份
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "已清理旧备份文件"
示例2:日志分析脚本
#!/bin/bash
LOG_FILE="/var/log/nginx/access.log"
if [ ! -f "$LOG_FILE" ]; then
echo "日志文件不存在: $LOG_FILE"
exit 1
fi
echo "=== Nginx访问日志分析 ==="
echo ""
# 访问量Top 10 IP
echo "📊 Top 10 访问IP:"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10
echo ""
# 访问量Top 10 URL
echo "📊 Top 10 访问URL:"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10
echo ""
# HTTP状态码统计
echo "📊 HTTP状态码统计:"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -nr
echo ""
# 今日访问量
TODAY=$(date +%d/%b/%Y)
TODAY_COUNT=$(grep -c "$TODAY" "$LOG_FILE")
echo "📊 今日访问量: $TODAY_COUNT"
示例3:批量重命名文件
#!/bin/bash
# 检查参数
if [ $# -ne 2 ]; then
echo "用法: $0 <旧扩展名> <新扩展名>"
echo "示例: $0 .jpg .png"
exit 1
fi
OLD_EXT=$1
NEW_EXT=$2
COUNT=0
# 批量重命名
for file in *"$OLD_EXT"; do
# 检查文件是否存在
if [ -f "$file" ]; then
new_name="${file%$OLD_EXT}$NEW_EXT"
mv "$file" "$new_name"
echo "重命名: $file -> $new_name"
((COUNT++))
fi
done
echo "完成! 共重命名 $COUNT 个文件"
示例4:系统监控脚本
#!/bin/bash
echo "========== 系统监控报告 =========="
echo "时间: $(date)"
echo ""
# CPU使用率
echo "📊 CPU使用率:"
top -bn1 | grep "Cpu(s)" | awk '{print "使用率: " 100 - $8 "%"}'
echo ""
# 内存使用
echo "📊 内存使用:"
free -h | grep Mem | awk '{printf "总计: %s, 已用: %s, 可用: %s, 使用率: %.1f%%\n", $2, $3, $4, $3/$2*100}'
echo ""
# 磁盘使用
echo "📊 磁盘使用:"
df -h | grep -E '^/dev/' | awk '{printf "%s - 总计: %s, 已用: %s, 可用: %s, 使用率: %s\n", $6, $2, $3, $4, $5}'
echo ""
# 系统负载
echo "📊 系统负载:"
uptime | awk -F'load average:' '{print $2}'
echo ""
# 登录用户
echo "📊 当前登录用户:"
who
echo ""
echo "========== 报告结束 =========="
最佳实践
1. 始终使用Shebang
#!/bin/bash
2. 启用严格模式
set -euo pipefail
3. 使用引号包裹变量
# 推荐
echo "$variable"
if [ -f "$file" ]; then
# 不推荐(可能在空格处断开)
echo $variable
4. 使用函数组织代码
#!/bin/bash
main() {
# 主逻辑
}
main "$@"
5. 添加注释和文档
#!/bin/bash
#
# 脚本名称: backup.sh
# 描述: 自动备份指定目录
# 用法: ./backup.sh [目录路径]
# 作者: Your Name
# 日期: 2024-01-01
6. 检查命令是否存在
if ! command -v git &> /dev/null; then
echo "错误: git未安装"
exit 1
fi
7. 使用临时文件时清理
#!/bin/bash
TEMP_FILE=$(mktemp)
trap "rm -f $TEMP_FILE" EXIT # 脚本退出时清理
# 使用临时文件
echo "data" > "$TEMP_FILE"
8. 处理用户输入
#!/bin/bash
read -p "确认删除? (y/n): " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "操作已取消"
exit 0
fi
9. 使用颜色输出
#!/bin/bash
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}成功${NC}"
echo -e "${RED}错误${NC}"
echo -e "${YELLOW}警告${NC}"
10. 记录日志
#!/bin/bash
LOG_FILE="/var/log/myscript.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "脚本开始执行"
学习资源
在线教程
推荐书籍
- 《Linux命令行与shell脚本编程大全》
- 《Advanced Bash-Scripting Guide》
- 《Shell脚本学习指南》
实践建议
- 从简单的脚本开始
- 多阅读优秀脚本
- 善用
man命令查看文档 - 使用
shellcheck检查脚本语法 - 在测试环境中先验证
总结
Shell脚本是系统管理和自动化任务的强大工具。通过本指南,你已经学习了:
- ✅ Shell脚本基础概念
- ✅ 变量和运算符
- ✅ 条件判断和循环
- ✅ 函数定义和使用
- ✅ 输入输出重定向
- ✅ 调试技巧
- ✅ 实用示例和最佳实践
下一步:开始编写自己的脚本,从简单的自动化任务开始,逐步掌握更高级的技巧。记住,实践是最好的学习方式!
祝学习愉快!🚀