Bash命令行参数完全指南
在Linux系统管理和Shell脚本编程中,命令行参数的处理是一项基础而重要的技能。Bash作为最常用的Shell环境,提供了一套完整而灵活的机制来处理脚本运行时接收的参数。本文将通过大量表格和图表示例,系统性地介绍Bash命令行参数的各种用法和技巧。
一、位置参数速查表
1.1 位置参数映射表
| 参数变量 | 含义 | 示例值 | 注意事项 |
|---|---|---|---|
$0 | 脚本名称/路径 | ./script.sh 或 /home/user/script.sh | 取决于执行方式 |
$1 | 第1个参数 | hello | 0-9可直接使用 |
$2 | 第2个参数 | world | 0-9可直接使用 |
$3 | 第3个参数 | 2024 | 0-9可直接使用 |
$4–$9 | 第4-9个参数 | 依次类推 | 0-9可直接使用 |
${10} | 第10个参数 | ten | 必须使用花括号 |
${11} | 第11个参数 | eleven | 必须使用花括号 |
${N} | 第N个参数 | 任意值 | N≥10时强制使用{} |
1.2 参数访问示例对比
# 执行命令: ./demo.sh a b c d e f g h i j k l m

二、特殊变量一览表
2.1 核心特殊变量
| 变量 | 类型 | 返回值 | 典型用途 | 示例代码 |
|---|---|---|---|---|
$# | 整数 | 参数个数 | 参数数量校验 | if [ $# -lt 2 ]; then exit; fi |
$* | 字符串 | 所有参数(单字符串) | 简单场景 | echo "Args: $*" |
$@ | 数组 | 所有参数(独立) | 推荐使用 | for arg in "$@"; do |
$$ | 整数 | 当前Shell PID | 临时文件命名 | temp_$$.txt |
$? | 整数 | 上条命令退出码 | 错误检测 | if [ $? -eq 0 ]; then |
$! | 整数 | 最后后台进程PID | 进程管理 | wait $! |
$- | 字符串 | Shell选项标志 | 调试检测 | echo "$-" |
2.2 $* vs $@ 详细对比表
| 使用方式 | $* 行为 | $@ 行为 | 推荐度 |
|---|---|---|---|
| 无双引号 | $1 $2 $3 (IFS分隔) | $1 $2 $3 (IFS分隔) | ⭐⭐ |
有双引号 "$*" | "$1 $2 $3" 单个字符串 | "$1" "$2" "$3" 多个参数 | ⭐⭐⭐⭐⭐ |
| 空格参数 | "a b" c → 变成3个参数 ❌ | "a b" c → 保持2个参数 ✅ | ⭐⭐⭐⭐⭐ |
2.3 实战对比示例
#!/bin/bash
# 文件: compare.sh
echo "========== 测试参数: \"hello world\" \"foo\" \"bar\" =========="
set -- "hello world" "foo" "bar" # 设置测试参数
echo ""
echo "┌─────────────────┬──────────────────────┬───────┐"
echo "│ 用法 │ 值/个数 │ 说明 │"
echo "├─────────────────┼──────────────────────┼───────┤"
echo "│ \$# │ $(echo $#) │ 总参数个数 │"
echo "├─────────────────┼──────────────────────┼───────┤"
echo "│ \$* │ $(echo $*) │ 无引号,IFS分隔 │"
echo "├─────────────────┼──────────────────────┼───────┤"
echo "│ \"\$*\" │ \"$*\" │ 单字符串 │"
echo "├─────────────────┼──────────────────────┼───────┤"
param_count=$(echo "$@" | wc -w)
echo "│ \$@ │ $(echo $@) │ 无引号,IFS分隔 │"
echo "├─────────────────┼──────────────────────┼───────┤"
echo "│ \"\$@\" │ 共 $# 个参数: │ ✅ 保留参数边界 │"
printf "│ │ 1. '%s'\n" "$1"
printf "│ │ 2. '%s'\n" "$2"
printf "│ │ 3. '%s'\n" "$3"
echo "└─────────────────┴──────────────────────┴───────┘"
输出结果:
┌─────────────────┬──────────────────────┬────────┐
│ 用法 │ 值/个数 │ 说明 │
├─────────────────┼──────────────────────┼────────┤
│ $# │ 3 │ 总参数个数 │
├─────────────────┼──────────────────────┼────────┤
│ $* │ hello world foo bar │ 无引号,IFS分隔 │
├─────────────────┼──────────────────────┼────────┤
│ "$*" │ hello world foo bar │ 单字符串 │
├─────────────────┼──────────────────────┼────────┤
│ $@ │ hello world foo bar │ 无引号,IFS分隔 │
├─────────────────┼──────────────────────┼────────┤
│ "$@" │ 共 3 个参数: │ ✅ 保留参数边界 │
│ │ 1. 'hello world' │ │
│ │ 2. 'foo' │ │
│ │ 3. 'bar' │ │
└─────────────────┴──────────────────────┴────────┘
三、参数操作符对比
3.1 参数扩展操作符速查表
| 操作符 | 语法 | 条件 | 返回值 | 场景 |
|---|---|---|---|---|
:- | ${var:-word} | var未设或为空 | word | 设置默认值 |
- | ${var-word} | 仅var未设 | word | 区分空字符串 |
:= | ${var:=word} | var未设或为空 | 赋值word并返回 | 同时设置默认值 |
= | ${var=word} | 仅var未设 | 赋值word并返回 | 谨慎使用 |
:+ | ${var:+word} | var非空 | word | 条件替换 |
+ | ${var+word} | var已设(可为空) | word | 检测变量存在 |
:? | ${var:?msg} | var未设或为空 | 打印msg并退出 | 必需参数检查 |
? | ${var?msg} | 仅var未设 | 打印msg并退出 | 区分空字符串 |
3.2 默认值操作示例表
| 代码 | 场景1: VAR未定义 | 场景2: VAR="" | 场景3: VAR="value" |
|---|---|---|---|
echo ${VAR:-default} | default | default | value |
echo ${VAR-default} | default | "" | value |
echo ${VAR:=default} | 赋值后default | 赋值后default | value |
echo ${VAR:+default} | "" | "" | default |
echo ${VAR:?错误} | 打印错误并退出 | 打印错误并退出 | value |
3.3 字符串切片与替换表
| 操作符 | 功能 | 示例 | 结果 |
|---|---|---|---|
${#var} | 字符串长度 | var="hello" | 5 |
${var:offset} | 从offset开始截取 | var="hello" → ${var:2} | llo |
${var:offset:len} | 截取len长度 | var="hello" → ${var:1:3} | ell |
${var#pattern} | 删除最短前缀 | var="a.b.c" → ${var#*.} | b.c |
${var##pattern} | 删除最长前缀 | var="a.b.c" → ${var##*.} | c |
${var%pattern} | 删除最短后缀 | var="a.b.c" → ${var%.*} | a.b |
${var%%pattern} | 删除最长后缀 | var="a.b.c" → ${var%%.*} | a |
${var/old/new} | 替换第一个 | var="foo foo" → ${var/foo/bar} | bar foo |
${var//old/new} | 替换全部 | var="foo foo" → ${var//foo/bar} | bar bar |
3.4 实际应用场景表
| 场景 | 推荐写法 | 说明 |
|---|---|---|
| 可选参数默认值 | port=${1:-8080} | 未提供参数时使用8080 |
| 必需参数检查 | : ${1:?错误: 缺少文件名} | 参数缺失时退出 |
| 条件性添加标志 | debug=${verbose:+"-d"} | verbose非空时返回”-d” |
| 提取文件扩展名 | ext=${filename##*.} | 获取点号后的部分 |
| 移除文件扩展名 | name=${filename%.*} | 删除最后一个点号及之后 |
| 获取目录路径 | dir=${filepath%/*} | 删除最后一个斜杠及之后 |
| 获取文件名 | base=${filepath##*/} | 删除最后一个斜杠及之前 |
四、shift命令详解
4.1 shift工作原理图
初始状态:
┌─────┬─────┬─────┬─────┬─────┐
│ $1 │ $2 │ $3 │ $4 │ $5 │
│ A │ B │ C │ D │ E │
└─────┴─────┴─────┴─────┴─────┘
$# = 5
执行 shift 1 后:
┌─────┬─────┬─────┬─────┐
│ $1 │ $2 │ $3 │ $4 │
│ B │ C │ D │ E │
└─────┴─────┴─────┴─────┘
$# = 4
执行 shift 2 后:
┌─────┬─────┐
│ $1 │ $2 │
│ D │ E │
└─────┴─────┘
$# = 2
4.2 shift使用模式对比表
| 模式 | 代码 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 逐个处理 | while [ $# -gt 0 ]; do case $1 in ... esac; shift; done | 简单选项 | 代码简洁 | 不支持选项参数 |
| 参数配对 | while [ $# -gt 0 ]; do key=$1; value=$2; shift 2; done | 键值对参数 | 结构清晰 | 需确保成对出现 |
| 批量跳过 | shift 3 | 已知固定位置 | 效率高 | 灵活性差 |
| getopts方式 | while getopts "ab:" opt; do ... | 标准选项 | 功能完整 | 语法稍复杂 |
4.3 shift实战示例
#!/bin/bash
# 文件: shift_demo.sh - 演示参数解析器
echo "╔═══════════════════════════════╗"
echo "║ Shift 参数解析器 ║"
echo "╚═══════════════════════════════╝"
declare -A config=(
[verbose]=0
[port]=8080
[host]="localhost"
)
echo ""
echo "📥 原始参数: $@"
echo "📊 参数个数: $#"
echo ""
step=1
while [ $# -gt 0 ]; do
echo "┌─ 步骤 $step ──────────────────────────────┐"
echo "│ 当前 \$1 = '$1'"
echo "│ 剩余参数: $# 个"
case $1 in
-v|--verbose)
config[verbose]=1
echo "│ ✅ 启用详细模式"
shift 1
;;
-p|--port)
if [ -n "$2" ] && [ "$2" -eq "$2" ] 2>/dev/null; then
config[port]=$2
echo "│ ✅ 设置端口: $2"
shift 2
else
echo "│ ❌ 错误: 端口号无效"
exit 1
fi
;;
-h|--host)
if [ -n "$2" ]; then
config[host]=$2
echo "│ ✅ 设置主机: $2"
shift 2
else
echo "│ ❌ 错误: 缺少主机名"
exit 1
fi
;;
--help)
echo "│ 📖 用法: $0 [-v] [-p PORT] [-h HOST]"
echo "│ -v, --verbose 详细输出"
echo "│ -p, --port PORT 设置端口号"
echo "│ -h, --host HOST 设置主机名"
exit 0
;;
*)
echo "│ ⚠️ 未知参数: $1"
shift 1
;;
esac
echo "└─────────────────────────────────────┘"
echo ""
((step++))
done
echo "╔═══════════════════════════════╗"
echo "║ 最终配置 ║"
echo "╠═══════════════════════════════╣"
echo "║ 详细模式: ${config[verbose]} ║"
echo "║ 端口号: ${config[port]} ║"
echo "║ 主机名: ${config[host]} ║"
echo "╚═══════════════════════════════╝"
五、getopts完整指南
5.1 getopts选项定义规则表
| 模式 | 语法 | 含义 | 示例 |
|---|---|---|---|
| 无参数选项 | a | 布尔标志,不需要值 | -a |
| 必需参数选项 | a: | 必须跟一个参数值 | -a value |
| 可选参数选项 | a:: | GNU扩展,较少用 | -a[value] |
5.2 getopts选项字符串解析表
| 选项字符串 | 支持选项 | 参数要求 | 合法用法示例 |
|---|---|---|---|
"abc" | -a, -b, -c | 均无参数 | -a -b -c 或 -abc |
"a:bc" | -a(需参数), -b, -c | -a必需值 | -a file -b -c |
"a:b:c:" | -a, -b, -c | 都需要参数 | -a A -b B -c C |
"ab:c:" | -a(无), -b(需), -c(需) | -b和-c需值 | -a -b B -c C |
"a:bc:d:" | 混合模式 | 按定义 | 灵活组合 |
5.3 getopts内置变量表
| 变量 | 作用 | 初始值 | 变化规则 |
|---|---|---|---|
$opt | 当前处理的选项字符 | – | 每次调用更新 |
$OPTARG | 当前选项的参数值 | 空 | 有参数选项时设置 |
$OPTIND | 下一个要处理的参数索引 | 1 | 处理一个选项后递增 |
$OPTERR | 是否显示错误(默认1) | 1 | 可设置为0禁用错误 |
5.4 选项处理流程图

5.5 完整getopts示例
#!/bin/bash
# 文件: getopts_complete.sh - 完整的选项解析器
APP_NAME="MyApp"
APP_VERSION="1.0.0"
# 显示帮助信息
show_help() {
cat << "EOF"
╔═════════════════════════════════╗
║ MyApp 命令行工具 v1.0.0 ║
╠═════════════════════════════════╣
║ 用法: ║
║ ./$(basename "$0") [选项] [文件...] ║
╠═════════════════════════════════╣
║ 选项: ║
║ -h, --help 显示此帮助信息 ║
║ -v, --version 显示版本信息 ║
║ -V, --verbose 启用详细输出模式 ║
║ -q, --quiet 静默模式(只输出错误) ║
║ -c, --config FILE 指定配置文件 ║
║ -o, --output FILE 指定输出文件 ║
║ -l, --log-level LVL 设置日志级别(debug/info/warn/error) ║
║ -D name=value 定义自定义变量 ║
╠═════════════════════════════════╣
║ 示例: ║
║ ./$(basename "$0") -v -c app.conf input.txt ║
║ ./$(basename "$0") --verbose --output result.txt data/* ║
║ ./$(basename "$0") -l debug -D key1=val1 -D key2=val2 ║
╚═════════════════════════════════╝
EOF
}
# 默认配置
declare -A CONFIG=(
[verbose]=0
[quiet]=0
[config]=""
[output]=""
[log_level]="info"
)
declare -A CUSTOM_VARS=()
INPUT_FILES=()
# 解析参数
echo "🔧 开始解析命令行参数..."
echo ""
while getopts "hVvqc:o:l:D:" opt; do
case $opt in
h)
show_help
exit 0
;;
V)
echo "$APP_NAME $APP_VERSION"
exit 0
;;
v)
CONFIG[verbose]=1
echo " ✅ 启用详细模式"
;;
q)
CONFIG[quiet]=1
echo " 🔇 启用静默模式"
;;
c)
CONFIG[config]="$OPTARG"
echo " 📁 配置文件: $OPTARG"
;;
o)
CONFIG[output]="$OPTARG"
echo " 💾 输出文件: $OPTARG"
;;
l)
case "$OPTARG" in
debug|info|warn|error)
CONFIG[log_level]="$OPTARG"
echo " 📊 日志级别: $OPTARG"
;;
*)
echo "❌ 错误: 无效的日志级别 '$OPTARG'"
echo " 有效值: debug, info, warn, error"
exit 1
;;
esac
;;
D)
if [[ "$OPTARG" =~ ^([^=]+)=(.*)$ ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
CUSTOM_VARS["$key"]="$value"
echo " 🎯 自定义变量: $key = $value"
else
echo "❌ 错误: 无效的自定义变量格式 '$OPTARG'"
echo " 正确格式: name=value"
exit 1
fi
;;
\?)
echo "❌ 错误: 无效选项"
show_help
exit 1
;;
esac
done
# 移除已处理的选项
shift $((OPTIND-1))
# 收集剩余参数作为输入文件
INPUT_FILES=("$@")
echo ""
echo "╔═════════════════════════════════════════════════╗"
echo "║ 解析完成 ║"
echo "╠═════════════════════════════════════════════════╣"
printf "║ 详细模式: %-44s ║\n" "${CONFIG[verbose]}"
printf "║ 静默模式: %-44s ║\n" "${CONFIG[quiet]}"
printf "║ 日志级别: %-44s ║\n" "${CONFIG[log_level]}"
printf "║ 配置文件: %-44s ║\n" "${CONFIG[config]:-(未指定)}"
printf "║ 输出文件: %-44s ║\n" "${CONFIG[output]:-(未指定)}"
printf "║ 自定义变量: %-44s ║\n" "${#CUSTOM_VARS[@]} 个"
printf "║ 输入文件: %-44s ║\n" "${#INPUT_FILES[@]} 个"
echo "╚═════════════════════════════════════════════════╝"
# 显示详细的自定义变量
if [ ${CONFIG[verbose]} -eq 1 ] && [ ${#CUSTOM_VARS[@]} -gt 0 ]; then
echo ""
echo "📋 自定义变量列表:"
echo "┌────────────────────────┬────────────────────┐"
for key in "${!CUSTOM_VARS[@]}"; do
printf "│ %-22s │ %-30s │\n" "$key" "${CUSTOM_VARS[$key]}"
done
echo "└────────────────────────┴────────────────────┘"
fi
# 显示输入文件
if [ ${#INPUT_FILES[@]} -gt 0 ]; then
echo ""
echo "📁 输入文件列表:"
echo"┌────┬───────────────────────────────────────────┐"
for i in "${!INPUT_FILES[@]}"; do
printf "│ %2d │ %-48s │\n" $((i+1)) "${INPUT_FILES[$i]}"
done
echo "└────┴───────────────────────────────────────────┘"
fi
# 模拟实际工作
if [ ${CONFIG[quiet]} -eq 0 ]; then
echo ""
echo "🚀 开始执行任务..."
# 实际业务逻辑放在这里
fi
5.6 getopts vs 手动解析对比表
| 特性 | getopts | 手动解析(shift+case) |
|---|---|---|
选项合并(-abc) | ✅ 自动支持 | ❌ 需手动实现 |
| 错误处理 | ✅ 内置 | ❌ 需自行编写 |
| 必需参数检查 | ✅ 自动检测 | ⚠️ 手动检查 |
长选项(--help) | ❌ 不支持 | ✅ 可支持 |
| 可选参数 | ⚠️ 有限支持 | ✅ 完全可控 |
| 代码复杂度 | 低 | 中-高 |
| 灵活度 | 中 | 高 |
| 推荐场景 | 短选项为主 | 需要长选项或复杂逻辑 |
六、实战案例集锦
6.1 案例1:文件处理脚本
#!/bin/bash
# 文件: file_processor.sh - 批量文件处理工具
PROG_NAME=$(basename "$0")
# 显示帮助
usage() {
cat << EOF
╔══════════════════════════════════╗
║ 文件批量处理工具 v1.0 ║
╠══════════════════════════════════╣
║ 用法: $PROG_NAME [选项] <文件...> ║
╠══════════════════════════════════╣
║ 选项: ║
║ -b, --backup 处理前备份原文件 ║
║ -e EXT, --ext EXT 修改文件扩展名为 EXT ║
║ -p PREFIX 添加文件名前缀 ║
║ -s SUFFIX 添加文件名后缀 ║
║ -r, --recursive 递归处理子目录 ║
║ -d DIR, --dir DIR 指定输出目录(默认: ./output) ║
║ -h, --help 显示此帮助信息 ║
╠══════════════════════════════════╣
║ 示例: ║
║ $PROG_NAME -e txt *.log ║
║ $PROG_NAME -p "backup_" -d ./backup/ *.txt ║
║ $PROG_NAME -b -s ".old" config.conf ║
╚══════════════════════════════════╝
EOF
}
# 默认配置
BACKUP=0
NEW_EXT=""
PREFIX=""
SUFFIX=""
RECURSIVE=0
OUTPUT_DIR="./output"
FILES=()
# 解析参数
while [ $# -gt 0 ]; do
case $1 in
-b|--backup)
BACKUP=1
shift
;;
-e|--ext)
NEW_EXT="$2"
shift 2
;;
-p)
PREFIX="$2"
shift 2
;;
-s)
SUFFIX="$2"
shift 2
;;
-r|--recursive)
RECURSIVE=1
shift
;;
-d|--dir)
OUTPUT_DIR="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
-*)
echo "❌ 错误: 未知选项 $1"
usage
exit 1
;;
*)
FILES+=("$1")
shift
;;
esac
done
# 配置总结
echo "╔═════════════════════════════════════════════════╗"
echo "║ 处理配置 ║"
echo "╠═════════════════════════════════════════════════╣"
printf "║ 备份原文件: %-44s ║\n" $([ $BACKUP -eq 1 ] && echo "是" || echo "否")
printf "║ 新扩展名: %-44s ║\n" "${NEW_EXT:-(保持原样)}"
printf "║ 文件名前缀: %-44s ║\n" "${PREFIX:-(无)}"
printf "║ 文件名后缀: %-44s ║\n" "${SUFFIX:-(无)}"
printf "║ 递归处理: %-44s ║\n" $([ $RECURSIVE -eq 1 ] && echo "是" || echo "否")
printf "║ 输出目录: %-44s ║\n" "$OUTPUT_DIR"
printf "║ 文件数量: %-44s ║\n" "${#FILES[@]}"
echo "╚═════════════════════════════════════════════════╝"
6.2 案例2:服务管理脚本
#!/bin/bash
# 文件: service.sh - 服务管理脚本
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 表格输出函数
print_table() {
local title="$1"
local width=60
echo ""
echo "╔$(printf '═%.0s' $(seq 1 $width))╗"
printf "║ %-58s ║\n" "$title"
echo "╠$(printf '═%.0s' $(seq 1 $width))╣"
}
print_row() {
printf "║ %-58s ║\n" "$1"
}
print_footer() {
local width=60
echo "╚$(printf '═%.0s' $(seq 1 $width))╝"
}
# 显示使用说明
show_usage() {
cat << EOF
${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}
${GREEN} 服务管理工具 v2.0${NC}
${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}
${YELLOW}用法:${NC} $0 <命令> [服务名] [选项]
${YELLOW}命令:${NC}
start 启动服务
stop 停止服务
restart 重启服务
status 查看服务状态
list 列出所有服务
logs 查看服务日志
${YELLOW}选项:${NC}
-f, --force 强制执行(跳过确认)
-v, --verbose 详细输出
-n, --lines N 显示最后N行日志(默认50)
${YELLOW}示例:${NC}
$0 start nginx
$0 restart mysql -f
$0 logs nginx -n 100
${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}
EOF
}
# 检查参数
if [ $# -lt 1 ]; then
show_usage
exit 1
fi
COMMAND="$1"
shift
# 解析剩余参数
SERVICE=""
FORCE=0
VERBOSE=0
LOG_LINES=50
while [ $# -gt 0 ]; do
case $1 in
-f|--force)
FORCE=1
shift
;;
-v|--verbose)
VERBOSE=1
shift
;;
-n|--lines)
LOG_LINES="$2"
shift 2
;;
-*)
echo "${RED}错误: 未知选项 $1${NC}"
show_usage
exit 1
;;
*)
if [ -z "$SERVICE" ]; then
SERVICE="$1"
else
echo "${RED}错误: 只能指定一个服务名${NC}"
exit 1
fi
shift
;;
esac
done
# 检查服务是否存在
check_service() {
if systemctl list-units --full -all | grep -q "$1.service"; then
return 0
else
return 1
fi
}
# 执行命令
case $COMMAND in
start)
if [ -z "$SERVICE" ]; then
echo "${RED}错误: 缺少服务名${NC}"
exit 1
fi
print_table "启动服务: $SERVICE"
if systemctl start "$SERVICE" 2>/dev/null; then
print_row "${GREEN}✅ $SERVICE 启动成功${NC}"
else
print_row "${RED}❌ $SERVICE 启动失败${NC}"
exit 1
fi
print_footer
;;
stop)
if [ -z "$SERVICE" ]; then
echo "${RED}错误: 缺少服务名${NC}"
exit 1
fi
if [ $FORCE -eq 0 ]; then
read -p "确定要停止 $SERVICE 吗?(y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "操作取消"
exit 0
fi
fi
print_table "停止服务: $SERVICE"
if systemctl stop "$SERVICE" 2>/dev/null; then
print_row "${GREEN}✅ $SERVICE 已停止${NC}"
else
print_row "${RED}❌ $SERVICE 停止失败${NC}"
exit 1
fi
print_footer
;;
status)
if [ -z "$SERVICE" ]; then
echo "${RED}错误: 缺少服务名${NC}"
exit 1
fi
print_table "$SERVICE 状态"
systemctl status "$SERVICE" --no-pager
print_footer
;;
list)
print_table "系统服务列表"
systemctl list-units --type=service --all --no-pager | head -20
print_footer
;;
logs)
if [ -z "$SERVICE" ]; then
echo "${RED}错误: 缺少服务名${NC}"
exit 1
fi
print_table "$SERVICE 日志 (最后 $LOG_LINES 行)"
journalctl -u "$SERVICE" -n "$LOG_LINES" --no-pager
print_footer
;;
restart)
if [ -z "$SERVICE" ]; then
echo "${RED}错误: 缺少服务名${NC}"
exit 1
fi
print_table "重启服务: $SERVICE"
if systemctl restart "$SERVICE" 2>/dev/null; then
print_row "${GREEN}✅ $SERVICE 重启成功${NC}"
else
print_row "${RED}❌ $SERVICE 重启失败${NC}"
exit 1
fi
print_footer
;;
*)
echo "${RED}错误: 未知命令 '$COMMAND'${NC}"
show_usage
exit 1
;;
esac
if [ $VERBOSE -eq 1 ]; then
echo -e "\n${YELLOW}[详细模式] 操作完成于 $(date)${NC}"
fi
七、最佳实践清单
7.1 参数处理检查表
| 类别 | 检查项 | 状态 |
|---|---|---|
| 输入验证 | □ 检查参数个数 $# | ☐ |
| □ 验证必需参数是否存在 | ☐ | |
| □ 检查文件/目录是否存在 | ☐ | |
| □ 验证数值范围 | ☐ | |
| 安全性 | □ 使用双引号包裹变量 "$var" | ☐ |
□ 使用 "$@" 而不是 $* | ☐ | |
| □ 避免 eval 执行动态命令 | ☐ | |
| □ 清理和验证用户输入 | ☐ | |
| 用户体验 | □ 提供 -h/--help 帮助选项 | ☐ |
□ 提供 -v/--version 版本信息 | ☐ | |
| □ 使用有意义的错误信息 | ☐ | |
□ 支持 -v/--verbose 详细模式 | ☐ | |
| □ 使用颜色和图标增强可读性 | ☐ | |
| 健壮性 | □ 设置默认值使用 ${var:-default} | ☐ |
□ 检查退出状态码 $? | ☐ | |
□ 使用 set -e 或手动错误处理 | ☐ | |
| □ 处理特殊字符和空格 | ☐ |
7.2 常见错误与解决方案表
| 错误代码 | 问题描述 | 错误示例 | 正确写法 |
|---|---|---|---|
| E001 | 未引用变量导致空格分割 | for f in $@ | for f in "$@" |
| E002 | 未使用花括号访问第10+参数 | echo $10 | echo ${10} |
| E003 | 忘记 shift 导致无限循环 | while [ $# -gt 0 ] 无shift | 添加 shift |
| E004 | 错误使用 $* 导致参数合并 | cp $* /dest/ | cp "$@" /dest/ |
| E005 | 未处理可选参数 | ${1} | ${1:-默认值} |
| E006 | 未检查 getopts 返回值 | 直接使用 $opt | 检查 case $opt in |
| E007 | 忘记重置 OPTIND | 多次调用 getopts | OPTIND=1 重置 |
| E008 | 未验证必需参数存在 | 直接使用 $2 | : ${2:?错误信息} |
7.3 选择决策树

7.4 快速参考卡片
# ═════════════════════════════════════════════════════════
# Bash 参数处理快速参考
# ═════════════════════════════════════════════════════════
#
# 📌 位置参数
# $0 ──→ 脚本名称
# $1-$9 ──→ 第1-9个参数
# ${10}+ ──→ 第10个及以上参数
# $# ──→ 参数个数
# $@ ──→ 所有参数(数组形式)
# $* ──→ 所有参数(字符串形式)
#
# 📌 常用模式
# 默认值: ${1:-default}
# 必需参数: : ${1:?错误信息}
# 参数个数: if [ $# -lt 2 ]; then usage; fi
# 遍历参数: for arg in "$@"; do echo "$arg"; done
#
# 📌 shift 用法
# shift # 丢弃 $1,$2→$1
# shift 2 # 丢弃 $1,$2,$3→$1
#
# 📌 getopts 模板
# while getopts "ab:c:" opt; do
# case $opt in
# a) action="a" ;;
# b) value="$OPTARG" ;;
# c) flag=1 ;;
# \?) usage ;;
# esac
# done
# shift $((OPTIND-1))
#
# 📌 参数扩展技巧
# ${#var} # 字符串长度
# ${var#pattern} # 删除最短前缀
# ${var##pattern} # 删除最长前缀
# ${var%pattern} # 删除最短后缀
# ${var%%pattern} # 删除最长后缀
# ${var/old/new} # 替换第一个
# ${var//old/new} # 替换全部
#
# ═════════════════════════════════════════════════════════
总结
本文全面介绍了Bash命令行参数处理的各种技术和最佳实践。从基础的位置参数到高级的getopts解析,从简单的参数校验到复杂的选项组合,我们通过大量表格、图表和实例代码进行了详细说明。
核心要点回顾:
- 位置参数 – 最基础的参数传递方式,注意10个以上参数需用花括号
- 特殊变量 –
$#统计个数,$@保留边界,"$@"是最安全的用法 - 参数扩展 – 提供默认值、参数检查、字符串操作等强大功能
- shift命令 – 灵活处理未知数量的参数,支持位置参数解析
- getopts – 标准化的选项解析,自动支持选项合并和错误处理
选择合适的参数处理方式取决于脚本的复杂度。对于简单脚本,位置参数就足够了;对于需要多个选项的工具脚本,getopts是最佳选择;而对于需要长选项或特殊格式的情况,手动解析+shift提供了最大的灵活性。
掌握这些技术将帮助你编写出更加专业、健壮和用户友好的Bash脚本。