Shell 脚本传递参数详解方法

Shell 脚本传递参数详解:getopts、while + case 与其他方法

适用于 Bash 脚本。文中的“getops”通常是“getopts”的笔误;Bash 内置命令是 getopts,没有常用的 getops。

1. 为什么要解析脚本参数

脚本参数来自命令行,例如:

./backup.sh -s /data -d /backup --compress

脚本通常需要区分:

  • 选项:-s、-d、--compress
  • 选项值:/data、/backup
  • 位置参数:没有选项名、按顺序传入的参数
  • 参数结束标记:--

先看 Bash 自动提供的变量:

变量 含义
$0 脚本名称
$1、$2 第 1、2 个位置参数
$# 参数个数
"$@" 保留边界的全部参数,最常用
"$*" 将全部参数拼成一个字符串
$? 上一条命令的退出状态

处理参数时通常使用 "$@",避免参数中含空格时被错误拆分。

2. 方法一:直接使用位置参数

适合参数固定、数量少的脚本:

#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 2 ]]; then
    echo "用法:$0 <源目录> <目标目录>" >&2
    exit 64
fi

source_dir=$1
target_dir=$2

printf '源目录:%s\n目标目录:%s\n' "$source_dir" "$target_dir"

调用:

./copy.sh /data /backup

这种写法简单,但调用者必须记住参数顺序,扩展性较弱。

3. 方法二:while + case 手动解析短选项

3.1 基本思路

  1. 用 while 判断是否还有参数。
  2. 用 case "$1" 判断当前参数类型。
  3. 用 shift 移除已经处理的参数。
  4. 选项带值时,读取下一个参数 $2,再额外 shift 一次。

3.2 完整示例

#!/usr/bin/env bash
set -euo pipefail

src=''
dst=''
verbose=false

usage() {
    cat <<'USAGE'
用法:backup.sh -s <源目录> -d <目标目录> [-v] [-h]

选项:
  -s DIR   源目录
  -d DIR   目标目录
  -v       显示详细信息
  -h       显示帮助
USAGE
}

while (($# > 0)); do
    case "$1" in
        -s|--source)
            if (($# < 2)); then
                echo "错误:$1 缺少参数" >&2
                exit 64
            fi
            src=$2
            shift 2
            ;;
        -d|--destination)
            if (($# < 2)); then
                echo "错误:$1 缺少参数" >&2
                exit 64
            fi
            dst=$2
            shift 2
            ;;
        -v|--verbose)
            verbose=true
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        --)
            shift
            break
            ;;
        -* )
            echo "错误:未知选项:$1" >&2
            usage >&2
            exit 64
            ;;
        *)
            echo "错误:无法识别的普通参数:$1" >&2
            exit 64
            ;;
    esac
done

if [[ -z $src || -z $dst ]]; then
    echo '错误:必须同时提供 -s 和 -d' >&2
    usage >&2
    exit 64
fi

if [[ $verbose == true ]]; then
    printf '准备处理:%s -> %s\n' "$src" "$dst"
fi

调用示例:

./backup.sh -s /data -d /backup
./backup.sh --source /data --destination /backup --verbose

3.3 shift 的关键规律

假设当前参数是:

$1=-s  $2=/data  $3=-v

处理 -s 后执行 shift 2,结果变成:

$1=-v

只有布尔开关时执行 shift;选项和选项值成对出现时执行 shift 2。忘记移动参数会造成死循环或重复处理。

3.4 支持短选项合并

手写解析可以自行支持 -v -f 或 -vf,但需要额外拆分:

case "$1" in
    -v|-f)
        # 单个开关
        shift
        ;;
    -vf|-fv)
        verbose=true
        force=true
        shift
        ;;
esac

选项数量增加后,维护成本会明显上升,通常改用 getopts。

4. 方法三:getopts 解析短选项

getopts 是 Bash/POSIX shell 常用的短选项解析工具,能够处理选项值、非法选项和缺少参数等情况。

4.1 基本语法

getopts optstring name

例如:

while getopts ':s:d:vh' opt; do
    case "$opt" in
        s) src=$OPTARG ;;
        d) dst=$OPTARG ;;
        v) verbose=true ;;
        h) usage; exit 0 ;;
        :) echo "选项 -$OPTARG 缺少参数" >&2; exit 64 ;;
        \?) echo "未知选项:-$OPTARG" >&2; exit 64 ;;
    esac
done
shift "$((OPTIND - 1))"

4.2 选项字符串规则

选项字符串 :s:d:vh 的含义:

写法 含义
s -s 不需要参数
s: -s 必须有一个参数
:s:d:vh 开头的 : 静默错误模式,交给脚本自行处理

getopts 相关变量:

变量 含义
$opt 当前解析到的选项字母
$OPTARG 当前选项的参数值
$OPTIND 下一个待解析参数的位置

4.3 完整示例

#!/usr/bin/env bash
set -euo pipefail

src=''
dst=''
verbose=false

usage() {
    echo "用法:$0 -s <源目录> -d <目标目录> [-v] [-h]"
}

while getopts ':s:d:vh' opt; do
    case "$opt" in
        s) src=$OPTARG ;;
        d) dst=$OPTARG ;;
        v) verbose=true ;;
        h) usage; exit 0 ;;
        :) echo "错误:选项 -$OPTARG 缺少参数" >&2; usage >&2; exit 64 ;;
        \?) echo "错误:未知选项 -$OPTARG" >&2; usage >&2; exit 64 ;;
    esac
done

shift "$((OPTIND - 1))"

if (($# > 0)); then
    echo "错误:多余的位置参数:$*" >&2
    exit 64
fi

if [[ -z $src || -z $dst ]]; then
    echo '错误:-s 和 -d 都是必需的' >&2
    usage >&2
    exit 64
fi

printf '源目录:%s\n目标目录:%s\n详细模式:%s\n' "$src" "$dst" "$verbose"

调用:

./backup.sh -s /data -d /backup
./backup.sh -s/data -d/backup -v
./backup.sh -s /data -d /backup -- extra-file.txt

getopts 通常支持 -v、-s value、-svalue 等短选项形式。它主要处理短选项,长选项需要手写或使用其他工具。

5. getopts 与 while + case 对比

项目 getopts 手写 while + case
短选项 支持完善 需要自己实现
缺少参数检测 内置基础能力 自己检查 $#
长选项 通常需额外处理 容易直接支持
代码量 较少 较多
可控性 规范、统一 灵活
适用场景 标准短选项 CLI 长选项、特殊语法、复杂参数

6. 长选项的常见写法

6.1 手写 while + case

while (($# > 0)); do
    case "$1" in
        --name)
            (($# >= 2)) || { echo '--name 缺少值' >&2; exit 64; }
            name=$2
            shift 2
            ;;
        --name=*)
            name=${1#*=}
            shift
            ;;
        --dry-run)
            dry_run=true
            shift
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "未知参数:$1" >&2
            exit 64
            ;;
    esac
done

${1#*=} 表示删除第一个 = 及其左侧内容。例如 --name=alice 会得到 alice。

6.2 Bash 数组保存剩余位置参数

-- 后的参数通常作为文件名、命令或其他输入保存:

files=()

while (($# > 0)); do
    case "$1" in
        -v) verbose=true; shift ;;
        --) shift; files+=("$@"); break ;;
        *) files+=("$1"); shift ;;
    esac
done

printf '文件:%s\n' "${files[@]}"

7. 其他方法

7.1 bash 的 [[ ... ]] 与数组

参数很少时,可以直接判断:

if [[ ${1:-} == '--version' ]]; then
    echo '1.0.0'
    exit 0
fi

${1:-} 可以避免未设置参数时触发错误。

7.2 read 交互式输入

参数适合自动化调用;read 适合交互式脚本:

read -r -p '请输入环境名:' environment
printf '环境:%s\n' "$environment"

生产脚本通常同时提供参数和交互模式,避免无人值守执行时卡住。

7.3 docopt、argbash、第三方 CLI 框架

当参数规则非常复杂时,可以使用项目允许的第三方工具生成或统一解析帮助信息。但要注意目标机器是否安装依赖、脚本是否仍需 POSIX 兼容,以及部署时如何携带依赖。

8. 参数校验与安全习惯

必做检查

[[ -n ${src:-} ]] || { echo 'src 不能为空' >&2; exit 64; }
[[ -d $src ]] || { echo "目录不存在:$src" >&2; exit 66; }

不要直接拼接未引用参数

错误:

rm -rf $target

正确:

rm -rf -- "$target"

-- 可以防止以 - 开头的路径被误认为选项;双引号可以保留空格和通配符。

不要用 eval 解析普通参数

eval 会再次执行字符串中的 shell 语法,容易产生命令注入。优先使用数组、case、参数展开和明确的校验。

退出码建议

常见约定:

  • 0:成功
  • 1:一般失败
  • 2:命令用法错误(也可使用更明确的 64)
  • 64:参数错误
  • 66:输入文件或目录不存在

9. 调试和验证

查看 Bash 语法:

bash -n script.sh

跟踪执行过程:

bash -x script.sh -s /data -d /backup

建议至少验证:

./script.sh -h
./script.sh                         # 缺少参数
./script.sh -x                      # 未知选项
./script.sh -s                      # 选项缺少值
./script.sh -s '/path/with space'   # 路径含空格
./script.sh -- -file.txt           # 以短横线开头的普通参数

10. 推荐选择

  • 参数固定且很少:位置参数 $1、$2。
  • 标准短选项:getopts + while + case。
  • 同时需要 --source、--source=value 等长选项:手写 while + case。
  • 参数规则复杂:考虑 Bash 数组配合手写解析,或引入经过评估的 CLI 工具。
  • 面向生产:提供 -h,检查缺少值和未知选项,使用 --、双引号、明确退出码,并先做语法和边界验证。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容