跳至正文
老丹的足迹 —— 代码写给机器,游记写给自己,感悟写给时间
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面

Bash命令行参数完全指南

在Linux系统管理和Shell脚本编程中,命令行参数的处理是一项基础而重要的技能。Bash作为最常用的Shell环境,提供了一套完整而灵活的机制来处理脚本运行时接收的参数。本文将通过大量表格和图表示例,系统性地介绍Bash命令行参数的各种用法和技巧。

一、位置参数速查表

1.1 位置参数映射表

参数变量含义示例值注意事项
$0脚本名称/路径./script.sh 或 /home/user/script.sh取决于执行方式
$1第1个参数hello0-9可直接使用
$2第2个参数world0-9可直接使用
$3第3个参数20240-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}defaultdefaultvalue
echo ${VAR-default}default""value
echo ${VAR:=default}赋值后default赋值后defaultvalue
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 $10echo ${10}
E003忘记 shift 导致无限循环while [ $# -gt 0 ] 无shift添加 shift
E004错误使用 $* 导致参数合并cp $* /dest/cp "$@" /dest/
E005未处理可选参数${1}${1:-默认值}
E006未检查 getopts 返回值直接使用 $opt检查 case $opt in
E007忘记重置 OPTIND多次调用 getoptsOPTIND=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解析,从简单的参数校验到复杂的选项组合,我们通过大量表格、图表和实例代码进行了详细说明。

核心要点回顾:

  1. 位置参数 – 最基础的参数传递方式,注意10个以上参数需用花括号
  2. 特殊变量 – $#统计个数,$@保留边界,"$@"是最安全的用法
  3. 参数扩展 – 提供默认值、参数检查、字符串操作等强大功能
  4. shift命令 – 灵活处理未知数量的参数,支持位置参数解析
  5. getopts – 标准化的选项解析,自动支持选项合并和错误处理

选择合适的参数处理方式取决于脚本的复杂度。对于简单脚本,位置参数就足够了;对于需要多个选项的工具脚本,getopts是最佳选择;而对于需要长选项或特殊格式的情况,手动解析+shift提供了最大的灵活性。

掌握这些技术将帮助你编写出更加专业、健壮和用户友好的Bash脚本。

作者

老丹

关注我
其他文章
上一个

Bash 数组完全指南:从设计思想到实战应用

关于博主

    老丹是一名C/C++后台开发工程师,信奉“无抽象不设计,无性能不生产”。

  • 技术栈:Modern C++、Linux环境编程、多线程/并发、网络编程等。
  • 信条:能用constexpr解决的问题绝不拖到运行时,能靠RAII避免的泄漏绝不写析构。
  • 正在填坑:从解封装到渲染的C++全链路实现,正在驯服FFmpeg与H.264/H.265。
  • 输出原则:这里的每一段代码都经过-Wall -Wextra -Werror -O2的洗礼。

搜索

近期文章

  • Bash命令行参数完全指南 2026年6月17日
  • Bash 数组完全指南:从设计思想到实战应用 2026年6月16日
  • Bash 变量内容操作完全指南 2026年6月16日
  • usbutils:Linux下USB设备查看与调试的完整指南 2026年6月16日
  • MQTT协议完全指南:从核心概念到实践应用 2026年6月16日
  • ICO文件格式完全解析 2026年6月15日

文章分类

  • C/C++开发 (10)
  • Linux工具包 (6)
  • Linux服务配置 (7)
  • Shell脚本 (3)
  • 计算机理论 (9)
联系我们:📍 地址:中国·广东省深圳市   |   ✉️ 邮箱:support@tanglinux.com   |   💬 QQ:870866607
版权所有:老丹的足迹粤ICP备2026061170号-1       公安备案图标 粤公网安备44030002013274号