本章导语:Shell 脚本编程是 Linux/Unix 系统管理的核心技能,它能够将繁琐的系统操作自动化,极大提升工作效率。本章将从最基础的 "Hello World" 开始,循序渐进地带你进入 Shell 脚本的世界,掌握脚本创建、执行、调试的完整流程,为后续深入学习打下坚实基础。
学习目标
完成本章学习后,你将能够:
- 🎯 理解 Shell 脚本的概念、特点和应用场景
- 🛠️ 熟练创建和执行第一个 Shell 脚本
- ⚙️ 掌握不同的脚本执行方式及其区别
- 🔧 学会使用 Shebang 行配置解释器
- 🐛 运用调试技巧快速定位和解决问题
- 🛡️ 建立健壮的错误处理机制
- 📋 遵循 Shell 脚本开发的最佳实践
前置知识
- 熟悉 Linux 基本命令(ls, cd, mkdir, echo 等)
- 了解 Linux 文件系统和权限概念
- 具备基本的文本编辑器使用经验
应用场景
Shell 脚本在实际工作中有着广泛的应用:
- 系统运维:自动化部署、监控告警、日志分析
- 数据处理:批量文件处理、文本分析、格式转换
- 开发辅助:代码编译、测试自动化、环境配置
- 日常办公:文件备份、定时任务、批量操作
1.1 什么是 Shell 脚本
Shell 脚本是一种用 Shell 命令编写的脚本程序,它将一系列命令组织在一起,可以实现复杂的自动化任务。Shell 是 Linux/Unix 系统的命令解释器,负责解释用户输入的命令并将其转换为系统调用。
Shell 脚本的特点
- 自动化执行:将重复性任务自动化,提高工作效率
- 批量处理:对文件、数据等进行批量操作
- 系统集成:与系统命令和工具无缝集成
- 跨平台兼容:大多数 Shell 脚本在不同 Unix/Linux 发行版上都能运行
- 简单易学:语法相对简单,学习曲线平缓
常见的 Shell 类型
- Bash (Bourne Again Shell):Linux 系统默认 Shell,功能最全面
- sh (Bourne Shell):Unix 系统的传统 Shell
- zsh (Z Shell):功能强大的交互式 Shell
- ksh (Korn Shell):结合了 Bourne Shell 和 C Shell 的特性
- csh (C Shell):语法类似 C 语言
1.2 第一个 Shell 脚本:Hello World
创建第一个脚本
bash
#!/bin/bash
# 这是我的第一个 Shell 脚本
# 脚本功能:输出 Hello World
echo "Hello World!"
echo "欢迎使用 Shell 脚本编程!"
脚本解析
#!/bin/bash:Shebang 行,指定脚本解释器#:注释符号,以#开头的行为注释echo:输出命令,将字符串输出到标准输出
保存和执行脚本
bash
# 保存脚本为 hello.sh
chmod +x hello.sh # 添加执行权限
./hello.sh # 执行脚本
# 或者使用 bash 命令直接执行
bash hello.sh
进阶版 Hello World
bash
#!/bin/bash
# 增强版 Hello World 脚本
# 包含用户输入、变量使用和条件判断
# 获取用户名
read -p "请输入您的名字: " username
# 获取当前时间
current_time=$(date "+%Y-%m-%d %H:%M:%S")
# 显示欢迎信息
echo "================================"
echo "Hello World!"
echo "欢迎您, $username!"
echo "当前时间: $current_time"
echo "================================"
# 根据时间显示不同问候语
hour=$(date +%H)
if [ $hour -lt 12 ]; then
echo "早上好!"
elif [ $hour -lt 18 ]; then
echo "下午好!"
else
echo "晚上好!"
fi
1.3 Shell 脚本的执行方式
方式一:直接执行(需要执行权限)
bash
# 添加执行权限
chmod +x script.sh
# 相对路径执行
./script.sh
# 绝对路径执行
/home/user/script.sh
方式二:使用 Shell 解释器执行
bash
# 使用 bash 执行
bash script.sh
# 使用 sh 执行
sh script.sh
# 使用 zsh 执行
zsh script.sh
方式三:使用 source 命令执行
bash
# source 命令在当前 Shell 中执行脚本
source script.sh
# 简写形式
. script.sh
不同执行方式的区别
| 执行方式 | 子进程 | 环境变量 | 适用场景 |
|---|---|---|---|
./script.sh |
✅ 创建新子进程 | 不影响当前 Shell | 独立脚本执行 |
bash script.sh |
✅ 创建新子进程 | 不影响当前 Shell | 测试脚本兼容性 |
source script.sh |
❌ 在当前 Shell 执行 | 影响当前 Shell | 加载配置文件 |
实际应用示例
bash
#!/bin/bash
# execute_demo.sh - 演示不同执行方式的区别
echo "脚本开始执行..."
# 定义变量
script_var="我是脚本中的变量"
echo "脚本内部变量: $script_var"
# 修改环境变量
export PATH="$PATH:/custom/path"
echo "脚本内修改的PATH: $PATH"
# 检查是否在子进程中
echo "当前进程ID: $$"
echo "父进程ID: $PPID"
echo "脚本执行完成"
1.4 Shebang 行的作用与选择
Shebang 行的作用
Shebang(也称为 hashbang)是脚本文件的第一行,以 #! 开头,用于指定解释该脚本的程序路径。
常见的 Shebang 写法
bash
#!/bin/bash # 使用 Bash 解释器(推荐)
#!/bin/sh # 使用系统默认的 Bourne Shell
#!/usr/bin/env bash # 使用 env 查找 bash 路径(更便携)
#!/usr/bin/env sh # 使用 env 查找 sh 路径
#!/bin/zsh # 使用 Z Shell
#!/usr/bin/python3 # 使用 Python 3 解释器
推荐使用 #!/usr/bin/env bash
bash
#!/usr/bin/env bash
# 使用 env 查找 bash 的优点:
# 1. 更好的可移植性,不依赖特定路径
# 2. 自动使用用户 PATH 中的 bash
# 3. 避免硬编码解释器路径
Shebang 最佳实践
bash
#!/usr/bin/env bash
# 文件:script_template.sh
# 作者:[作者名]
# 创建时间:$(date +%Y-%m-%d)
# 描述:脚本功能描述
# 严格模式
set -euo pipefail
IFS=$'\n\t'
# 脚本主体内容
echo "这是一个遵循最佳实践的脚本模板"
脚本兼容性处理
bash
#!/bin/bash
# 兼容性检查脚本
echo "检查 Shell 兼容性..."
# 检查 bash 版本
bash_version=$(/bin/bash --version | head -n1 | cut -d' ' -f4)
echo "Bash 版本: $bash_version"
# 检查是否启用了特定功能
if [[ -n "${BASH_VERSION:-}" ]]; then
echo "Bash 特有功能可用"
else
echo "仅使用 POSIX 兼容功能"
fi
# 数组支持测试
if declare -p test_array >/dev/null 2>&1; then
echo "数组功能可用"
else
echo "数组功能不可用"
fi
1.5 脚本的调试与错误处理
调试模式
bash
#!/bin/bash
# 启用调试模式的方法:
# 1. 在脚本中添加 set -x
# 2. 使用 bash -x script.sh
# 3. 在脚本开头设置调试选项
# 方法一:全局调试
set -x # 启用命令跟踪
set -v # 启用详细输出
set -e # 遇到错误立即退出
set -u # 使用未定义变量时报错
set -o pipefail # 管道中任何命令失败都返回失败
# 方法二:局部调试
debug_mode=1 # 开关变量
if [[ $debug_mode -eq 1 ]]; then
echo "调试模式已启用"
set -x
fi
# 示例代码
echo "开始执行..."
user="admin"
password="secret123"
echo "用户: $user"
echo "密码: $password" # 注意:实际生产中不要输出密码
# 禁用调试
set +x
echo "调试模式已禁用"
错误处理最佳实践
bash
#!/bin/bash
# error_handling_demo.sh - 错误处理演示
# 设置严格模式
set -euo pipefail
IFS=$'\n\t'
# 错误处理函数
handle_error() {
local exit_code=$?
local line_number=$1
echo "错误:脚本在第 $line_number 行执行失败,退出码:$exit_code"
exit $exit_code
}
# 设置错误陷阱
trap 'handle_error $LINENO' ERR
# 日志函数
log_info() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $*"
}
log_error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
}
log_warn() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARN: $*" >&2
}
# 主程序
main() {
log_info "脚本开始执行"
# 检查依赖
command -v curl >/dev/null 2>&1 || {
log_error "curl 命令未找到,请先安装 curl"
exit 1
}
# 创建备份目录
backup_dir="/tmp/backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir" || {
log_error "无法创建备份目录: $backup_dir"
exit 1
}
log_info "备份目录创建成功: $backup_dir"
# 模拟文件操作
log_info "执行文件操作..."
touch "$backup_dir/test_file.txt"
# 验证文件创建
if [[ -f "$backup_dir/test_file.txt" ]]; then
log_info "文件创建成功"
else
log_error "文件创建失败"
exit 1
fi
log_info "脚本执行完成"
}
# 调用主函数
main "$@"
交互式调试脚本
bash
#!/bin/bash
# interactive_debug.sh - 交互式调试脚本
# 断点函数
breakpoint() {
echo "断点: 调试模式激活"
echo "当前变量值:"
echo " PWD: $PWD"
echo " USER: $USER"
echo " HOME: $HOME"
# 列出所有局部变量
echo "局部变量:"
set | grep '^[a-zA-Z_][a-zA-Z0-9_]*=' | sort
read -p "按 Enter 继续执行,输入 'q' 退出: " choice
if [[ "$choice" == "q" ]]; then
echo "用户选择退出"
exit 0
fi
}
# 调试输出函数
debug() {
if [[ "${DEBUG:-}" == "1" ]]; then
echo "DEBUG: $*"
fi
}
# 演示调试功能
demo_function() {
local var1="test1"
local var2="test2"
debug "进入 demo_function"
debug "var1=$var1, var2=$var2"
breakpoint # 设置断点
local result=$(echo "$var1 $var2" | tr '[:lower:]' '[:upper:]')
debug "处理结果: $result"
echo "最终结果: $result"
}
# 执行演示
if [[ "${DEBUG:-}" == "1" ]]; then
echo "调试模式已启用"
fi
demo_function
1.6 常见错误与解决方案
常见错误类型及解决方案
1. 权限错误
bash
# 错误:Permission denied
./script.sh: Permission denied
# 解决方案:添加执行权限
chmod +x script.sh
# 或者使用解释器执行
bash script.sh
2. Shebang 错误
bash
# 错误:bad interpreter
./script.sh: /bin/bash: bad interpreter: No such file or directory
# 解决方案:检查解释器路径
which bash # 查找 bash 路径
# 更新 shebang 行为正确路径
3. 语法错误
bash
# 错误:syntax error
./script.sh: line 5: syntax error near unexpected token `done'
# 解决方案:使用语法检查
bash -n script.sh # 仅检查语法,不执行脚本
# 启用详细错误信息
bash -x script.sh # 显示执行的每个命令
4. 变量未定义错误
bash
# 错误:unbound variable
./script.sh: line 10: $undefined_var: unbound variable
# 解决方案:变量初始化或默认值
undefined_var="${undefined_var:-default_value}"
# 或者关闭严格模式
set +u # 允许未定义变量
错误预防脚本模板
bash
#!/bin/bash
# robust_script_template.sh - 健壮的脚本模板
# 脚本配置
script_name=$(basename "$0")
script_dir=$(dirname "$(readlink -f "$0")")
log_file="/tmp/${script_name%.*}.log"
# 错误处理
set -euo pipefail
IFS=$'\n\t'
# 信号处理
cleanup() {
local exit_code=$?
log_info "脚本退出,清理资源..."
# 清理临时文件
rm -rf "${temp_files[@]:-}" 2>/dev/null || true
exit $exit_code
}
trap cleanup EXIT
trap 'trap - EXIT; cleanup; trap - INT; kill -INT $$' INT
trap 'trap - EXIT; cleanup; trap - TERM; kill -TERM $$' TERM
# 日志函数
log() {
local level=$1
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$log_file"
}
log_info() { log "INFO" "$@"; }
log_warn() { log "WARN" "$@"; }
log_error() { log "ERROR" "$@"; }
# 参数验证
validate_args() {
if [[ $# -lt 1 ]]; then
echo "用法: $script_name <参数1> [参数2]"
echo "示例: $script_name input.txt output.txt"
exit 1
fi
}
# 环境检查
check_environment() {
local required_commands=("curl" "wget" "jq")
local missing_commands=()
for cmd in "${required_commands[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing_commands+=("$cmd")
fi
done
if [[ ${#missing_commands[@]} -gt 0 ]]; then
log_error "缺少必要的命令: ${missing_commands[*]}"
exit 1
fi
log_info "环境检查通过"
}
# 主函数
main() {
validate_args "$@"
check_environment
local input_file=$1
local output_file=${2:-"output.txt"}
log_info "处理文件: $input_file"
log_info "输出文件: $output_file"
# 检查输入文件
if [[ ! -f "$input_file" ]]; then
log_error "输入文件不存在: $input_file"
exit 1
fi
# 处理文件
temp_files+=("temp_processing_file")
cp "$input_file" "temp_processing_file"
# 模拟处理
log_info "正在处理文件..."
sleep 1
# 保存结果
cp "temp_processing_file" "$output_file"
log_info "处理完成,结果保存到: $output_file"
}
# 执行主函数
main "$@"
实用调试工具函数
bash
#!/bin/bash
# debug_utils.sh - 调试工具函数库
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 彩色输出函数
print_info() {
echo -e "${BLUE}[INFO]${NC} $*"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $*"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $*"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $*"
}
# 性能测量函数
time_function() {
local func_name=$1
shift
local start_time=$(date +%s.%N)
"$@"
local end_time=$(date +%s.%N)
local duration=$(echo "$end_time - $start_time" | bc -l)
print_info "$func_name 执行时间: ${duration}s"
}
# 内存使用监控
monitor_memory() {
local process_name=$1
local pid=$(pgf "$process_name" | head -n1 | awk '{print $2}')
if [[ -n "$pid" ]]; then
local memory_usage=$(ps -p "$pid" -o %mem --no-headers 2>/dev/null)
print_info "进程 $process_name (PID: $pid) 内存使用率: ${memory_usage}%"
else
print_warning "未找到进程: $process_name"
fi
}
# 变量转储函数
dump_variables() {
echo "=== 变量转储 ==="
local prefix=${1:-}
# 显示所有以指定前缀开头的变量
set | grep "^${prefix}" | sort
echo "=== 环境变量 ==="
env | grep "^${prefix}" | sort
}
# 函数调用栈
show_stack_trace() {
echo "=== 函数调用栈 ==="
local frame=0
while caller $frame; do
((frame++))
done 2>/dev/null
}
本章总结
本章介绍了 Shell 脚本的基础知识,从创建第一个 "Hello World" 脚本开始,详细讲解了脚本的执行方式、Shebang 行的作用、调试技巧和错误处理方法。
核心要点:
- Shell 脚本本质:是解释执行的程序,适合系统管理和自动化任务
- 执行方式差异:了解不同执行方式及其适用场景
- Shebang 重要性:选择合适的解释器路径,提高脚本可移植性
- 调试技能 :掌握
set命令和调试技巧,快速定位问题 - 错误处理:建立完善的错误处理机制,提高脚本健壮性
- 最佳实践:使用严格的编程规范和健壮的模板
实践练习
-
创建一个显示系统信息的脚本,包括:
- 操作系统版本
- 当前用户
- 系统负载
- 磁盘使用情况
-
编写一个带错误处理的文件备份脚本,要求:
- 检查源文件是否存在
- 创建备份目录
- 添加时间戳
- 验证备份成功
-
实现一个交互式脚本,让用户选择不同的系统管理操作,并添加调试和日志功能。