在编写Bash脚本时,处理命令行参数是不可避免的任务。无论是像TypeScript、Python、C++还是JavaScript这样的语言,都有成熟的库来解析参数,但Bash脚本却需要开发者手动实现。本文将深入探讨Bash中两种主流的参数解析方法——shift命令和getopts命令,帮助你写出更健壮、更易维护的脚本。
命令行参数的基本约定
Unix命令通常遵循特定的参数语法。以常见的ls命令为例,其参数结构如下:
tar -x -f archive.tar -v -- file1 file2 file3
这里的约定很重要:选项必须出现在非选项参数之前。选项分为两类:
- ✅ 独立选项:如
-l,只需存在或不存在,无需额外参数 - ✅ 带参选项:如
-o,后面必须跟一个参数值
选项处理的核心是编写一个循环,遍历参数列表,识别并处理每个选项,最终将参数列表缩减为仅包含非选项参数(如文件名)。
shift命令:移动位置参数的基础工具
shift是Bash内置命令,用于向左移动位置参数列表。每次执行shift(默认移动1位),$1被删除,$2变为新的$1,以此类推。这就像从队列中出队元素。
基本用法示例:
#!/bin/bash
# test_shift.sh
echo "初始参数: $@"
echo "参数个数: $#"
echo "\$1 = $1"
shift
echo "--- shift 后 ---"
echo "剩余参数: $@"
echo "参数个数: $#"
echo "\$1 = $1"
运行结果:
$ ./test_shift.sh a b c d
初始参数: a b c d
参数个数: 4
$1 = a
--- shift 后 ---
剩余参数: b c d
参数个数: 3
$1 = b
你可以指定移动位数:
#!/bin/bash
echo "原始参数: $@"
shift 3
echo "shift 3 后: $@"
常见应用场景:
- 遍历所有参数:使用
while [ $# -gt 0 ]循环,每次处理$1后执行shift - 解析带参选项:当选项需要参数时,
shift次数比不带参选项多1 - 分离第一个参数:先用
$1保存特殊值,再shift处理剩余参数
#!/bin/bash
# 逐个处理所有参数(使用shift会修改参数列表)
count=1
while [ $# -gt 0 ]; do
echo "参数 $count: $1"
shift
((count++))
done
手动循环解析选项
手动解析选项是最灵活的方法,可以处理单字母选项、长选项、带或不带参数的选项。这里使用while循环配合case语句:
#!/bin/sh
die() {
printf '%s\n' "$1" >&2
exit 1
}
#初始化可能要用到的选项变量,防止环境中已有的同名变量对其产生干扰
# Initialize all the option variables.
# This ensures we are not contaminated by variables from the environment.
file= #默认文件名为空
verbose=0
# 编写选项处理的循环:手动循环
# 这里我们在while内部,esac后面放置一个shift语句,让各个选项解析分支内,少写或者不写shift,尤其是不带参数的命令行选项.
while :; do
case $1 in
-h|-\?|--help)
show_help # Display a usage synopsis.
exit
;;
-f|--file)
if [[ -n "$2" && "$2" != -* ]]; then
file="$2"
shift
else
die 'ERROR: "--file" requires a non-empty option argument.'
fi
;;
-f=*|--file=*)
# 将等号以及前面的部分移除,剩下的就是文件名(路径)了
file=${1#*=} # Delete everything up to "=" and assign the remainder.
# 为了防止用户传入的文件名是空的,这里做检查
[[ -z "$file" ]] && die 'ERROR: "--file" requires an argument.'
shift
;;
-v|--verbose)
# 将日志的详细信息设置为多级,并且允许叠加使用此选项来增加详细程度(数值),-v -v 就是2级
verbose=$((verbose + 1)) # Each -v adds 1 to verbosity.
;;
-vv*)
# 提取 - 后面的部分
flags="${1#-}"
# 检查flags是否全为 v
if [[ "$flags" =~ ^v+$ ]]; then
# 计算 v 的数量
verbose=$((verbose + ${#flags}))
else
echo "未知选项: $1"
exit 1
fi
;;
--) # End of all options.
shift
break
;;
-?*)
printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
;;
*) # Default case: No more options, so break out of the loop.
break
esac
shift
done
# Rest of the program here.
# If there are input files (for example) that follow the options, they
# will remain in the "$@" positional parameters.
注意事项:
- 选项带参数时,
shift的位数比不带参时多1 - 支持
-o value和-ovalue两种风格 - 长选项如
--output也可处理,但通常不与单字母选项拆分同时支持
这种方法的优点在于完全可控,适合复杂需求。但缺点是代码量较大,且容易出错。
getopts命令:标准化的选项解析方案
getopts是Bash内置的选项解析命令,适用于简单到中等复杂度的脚本。其基本语法如下:
getopts optstring name [arg ...]
核心概念:
optstring:定义可接受的选项字母,后面跟:表示该选项需要参数name:存储当前解析到的选项字母的变量名$OPTARG:存储当前选项的参数值$OPTIND:存储下一个待处理参数的索引,初始为1
| 变量 | 描述 |
|---|---|
| 选项值变量,存储当前选项的参数值(如果该选项需要参数)。在错误报告模式是静默模式时,也可能保存到是当前未被正确处理的选项的名字(字母) | |
| 选项索引变量,存储下一个要处理的参数(命令行中传递给脚本的参数)的索引值(从 1 开始按需增加)。 | |
| 选项错误变量,控制是否显示错误信息(默认为 1 开启,设为 0 关闭)。如果变量 OPTERR 的值为 0,getopts 会禁止打印错误信息,即使 OPTSTRING 的第一个字符不是冒号(非静默模式)。 |
最简单的用例:
#!/bin/bash
while getopts ":a" opt; do
case $opt in
a)
# 将消息打印到stdout指向stderr,默认都输出到屏幕上
echo "-a was triggered!"
;;
\?)
# 例如使用选项-b,由于未在optstring中定义,会触此分支(非法选项b会被记录到变量OPTARG中)
echo "Invalid option: -$OPTARG"
;;
esac
done
这个例子中,getopts放在while循环的条件测试部分,每次循环解析一个选项。选项字母存储在opt变量中,参数值存储在$OPTARG中。
getopts的错误处理模式
getopts支持两种错误处理模式:
- 静默模式:
optstring以:开头,不输出错误消息,$OPTARG存储错误选项字母 - 详细模式:默认模式,输出错误消息,
$OPTARG被置空
示例(静默模式):
# (base) cxxu@CxxuDesk 15:28:18> <~>
$ bash test_getopts.sh -a
-a was triggered!
# 错误处理:
# (base) cxxu@CxxuDesk 15:31:23> <~>
$ bash test_getopts.sh -b
Invalid option: -b
$ bash test_getopts.sh -b -c
Invalid option: -b
Invalid option: -c
退出状态:当所有选项解析完毕或遇到错误时,getopts返回非零值,循环结束。
最佳实践:在开发阶段使用详细模式便于调试,生产环境使用静默模式并提供自定义错误消息。
getopts与shift的协同使用
在getopts循环结束后,通常需要手动shift掉已处理的选项,使剩余参数变为非选项参数:
# (base) cxxu@CxxuDesk 17:53:26> <~>
$ bash ./test_getopts.sh normal -a
normal mode.
args for getopts: [-a]
arg 1: -a
optstring=[a:x]
./test_getopts.sh: option requires an argument -- a
Invalid option: (opt=?) [OPTARG=]
(opt=?) [OPTARG=]
这种组合方式非常实用:getopts负责解析选项,shift负责清理参数列表,最终$@中只剩下文件名等非选项参数。
[AFFILIATE_SLOT_1]
总结与选择建议
选择哪种方法取决于你的需求:
- ✅ 简单脚本:使用
getopts,代码简洁,标准化 - ✅ 复杂需求:手动循环解析,灵活性更高
- ✅ 跨平台兼容:优先使用
getopts,它遵循POSIX标准
无论选择哪种方法,记住:选项必须在非选项参数之前,并且始终使用--作为选项结束指示符。对于更复杂的脚本,考虑使用Python、TypeScript或JavaScript等语言,它们提供了更完善的参数解析库。
[AFFILIATE_SLOT_2]
OPTARGOPTINDOPTERR
浙公网安备 33010602011771号