使用 getopts 解析标志与参数
使用 getopts 实现专业的命令行界面,支持短选项、必需参数和用法提示。
使用 getopts 解析标志与参数 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
getopts 存在的原因
每个真实项目中的脚本最终都需要接受诸如 -v、-o output.txt 或 -n 5 这样的选项。使用 $1、$2……手动解析很快就会变得脆弱。
getopts 是符合 POSIX 标准的内置工具,能够可靠地处理短选项(-a、-b),包括需要参数的选项。它内置于所有 POSIX shell 中,因此不需要任何依赖。
- 处理组合标志:
-vn 5=-v -n 5 - 妥善报告未知选项
- 自动设置标准变量
OPTIND和OPTARG
本课程将从头开始,使用 getopts 构建一个完整、专业的命令行界面。
getopts 语法
核心语法是一个在每次迭代中调用 getopts 的 while 循环:
while getopts "optstring" varname; do
case "$varname" in
...
esac
done- optstring — 列出每个可接受选项字母的字符串。字母后的冒号表示该选项需要参数。
- varname — 在每次迭代中接收当前选项字母。
- OPTARG — 当字母后跟冒号时,自动设置为参数值。
- OPTIND — 下一个待处理参数的索引;循环结束后使用
shift $((OPTIND - 1)),以显示剩余的位置参数。
#!/usr/bin/env bash
# Minimal skeleton — shows the loop structure
while getopts 'vn:' opt; do
case "$opt" in
v) echo "Verbose mode on" ;;
n) echo "Count = $OPTARG" ;;
?) echo "Unknown option: -$OPTARG" >&2; exit 1 ;;
esac
done定义选项字符串
optstring 是对命令行界面约定的简洁声明。每个字符代表一个可接受的标志。
'abc'— 接受-a、-b、-c(不带参数)'a:bc'—-a需要参数;-b和-c不需要':abc'— 开头的冒号启用静默错误模式(由脚本处理错误选项,而不是由 shell 打印消息)
生产脚本更推荐使用静默模式,因为这样可以完全控制错误消息和退出代码。
#!/usr/bin/env bash
# optstring ':o:vq'
# -o requires an argument (output file)
# -v verbose flag (no argument)
# -q quiet flag (no argument)
# Leading ':' = silent error mode
while getopts ':o:vq' opt; do
case "$opt" in
o) OUTPUT="$OPTARG" ;;
v) VERBOSE=1 ;;
q) QUIET=1 ;;
:) echo "Error: -$OPTARG requires an argument" >&2; exit 1 ;;
\?) echo "Error: unknown option -$OPTARG" >&2; exit 1 ;;
esac
done
echo "OUTPUT=$OUTPUT VERBOSE=$VERBOSE QUIET=$QUIET"OPTARG 与必需参数
当选项字符串中的某个标志后跟冒号时,getopts 会将其值存储在 OPTARG 中。用户可以在参数前加空格,也可以不加:
-o report.txt-oreport.txt
这两种形式的解析结果完全相同。这是它相较于手动使用 $1/shift 解析的主要优势之一。
在静默模式下(选项字符串以 ':' 开头),缺少参数时,getopts 会将 varname 设置为 :,并将 OPTARG 设置为选项字母,非常适合生成针对性的错误消息。
#!/usr/bin/env bash
# Demonstrate OPTARG with a file-processing script
while getopts ':i:o:' opt; do
case "$opt" in
i) INPUT="$OPTARG" ;;
o) OUTPUT="$OPTARG" ;;
:) echo "Error: option -$OPTARG needs a value" >&2; exit 1 ;;
\?) echo "Error: unknown flag -$OPTARG" >&2; exit 1 ;;
esac
done
echo "Input : ${INPUT:-<not set>}"
echo "Output : ${OUTPUT:-<not set>}"使用 OPTIND 跳过选项
getopts 完成后,OPTIND 保存第一个非选项参数的索引。使用 shift 移除所有已处理的选项,这样 $1、$2……就会指向剩余的位置参数(例如文件名)。
惯用写法始终是:
shift $((OPTIND - 1))执行移位后,$@ 只包含不是标志的参数,即命令的操作数。
#!/usr/bin/env bash
# Shows OPTIND shift and leftover positional args
VERBOSE=0
while getopts ':vn:' opt; do
case "$opt" in
v) VERBOSE=1 ;;
n) COUNT="$OPTARG" ;;
:) echo "Error: -$OPTARG requires an argument" >&2; exit 1 ;;
\?) echo "Error: unknown option -$OPTARG" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
echo "VERBOSE=$VERBOSE COUNT=${COUNT:-1}"
echo "Remaining args: $*"编写用法函数
专业脚本始终会提供一个用于打印帮助消息并退出的 usage() 函数。约定如下:
- 打印到 stderr(文件描述符 2),避免污染管道输出
- 对于
-h/--help,以代码 0 退出;对于无效用法,以代码 1 退出 - 在错误路径中调用
usage 1,在-h处理分支中调用usage 0
#!/usr/bin/env bash
usage() {
cat >&2 <<EOF
Usage: $(basename "$0") [-v] [-n COUNT] [-o FILE] [FILE...]
Options:
-v Verbose output
-n COUNT Repeat COUNT times (default: 1)
-o FILE Write output to FILE
-h Show this help
EOF
exit "${1:-0}"
}
while getopts ':vn:o:h' opt; do
case "$opt" in
v) VERBOSE=1 ;;
n) COUNT="$OPTARG" ;;
o) OUTFILE="$OPTARG" ;;
h) usage 0 ;;
:) echo "Error: -$OPTARG requires a value" >&2; usage 1 ;;
\?) echo "Error: unknown option -$OPTARG" >&2; usage 1 ;;
esac
done
shift $((OPTIND - 1))
echo "Parsed OK — verbose=${VERBOSE:-0} count=${COUNT:-1} out=${OUTFILE:--}"默认值与验证
解析完成后,应先验证并设置默认值,再执行任何实际工作。将解析阶段(循环)与逻辑阶段分开。这样两个部分都更易阅读和测试。
- 使用
${VAR:-default}设置内联默认值 - 使用正则表达式或算术检查验证数字参数
- 验证必需选项确实已提供
#!/usr/bin/env bash
usage() { echo "Usage: $(basename "$0") -n COUNT [-v]" >&2; exit 1; }
VERBOSE=0
COUNT=''
while getopts ':n:v' opt; do
case "$opt" in
n) COUNT="$OPTARG" ;;
v) VERBOSE=1 ;;
:) echo "Error: -$OPTARG needs a value" >&2; usage ;;
\?) echo "Error: -$OPTARG unknown" >&2; usage ;;
esac
done
shift $((OPTIND - 1))
# Validation phase
[[ -z "$COUNT" ]] && { echo "Error: -n COUNT is required" >&2; usage; }
[[ "$COUNT" =~ ^[0-9]+$ ]] || { echo "Error: COUNT must be a positive integer" >&2; usage; }
for (( i=1; i<=COUNT; i++ )); do
[[ $VERBOSE -eq 1 ]] && echo "Iteration $i of $COUNT"
echo "Hello, world!"
done在命令行中组合标志
getopts 会自动处理不带空格的组合短标志,这符合标准 Unix 约定:
-v -q等同于-vq-n 5 -v等同于-n5 -v或-vn5
您无需编写额外代码来支持这种形式——getopts 会自动遍历组合选项字符串中的每个字符。这也是应使用 getopts 而不是手动解析的另一项重要原因。
#!/usr/bin/env bash
# Test combined flag parsing
# Run as: bash script.sh -vq -n3
VERBOSE=0; QUIET=0; COUNT=1
while getopts ':vqn:' opt; do
case "$opt" in
v) VERBOSE=1 ;;
q) QUIET=1 ;;
n) COUNT="$OPTARG" ;;
:) echo "Error: -$OPTARG needs value" >&2; exit 1 ;;
\?) echo "Error: unknown -$OPTARG" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
echo "verbose=$VERBOSE quiet=$QUIET count=$COUNT"处理双连字符分隔符
Unix 命令接受 --(双连字符)作为停止处理选项的明确信号。-- 后面的所有内容都会被视为位置参数,即使它看起来像标志也是如此。
getopts 遇到 -- 时会自动停止。执行 shift $((OPTIND - 1)) 后,双连字符会被移除,而 $@ 只包含操作数。
对于处理文件名的脚本来说,这一点很重要,因为文件名可能以连字符开头,例如:
myscript.sh -v -- -strangefile.txt#!/usr/bin/env bash
# Demonstrate -- separator
# Run as: bash script.sh -v -- file1.txt -oddname.txt
VERBOSE=0
while getopts ':v' opt; do
case "$opt" in
v) VERBOSE=1 ;;
\?) echo "Unknown option -$OPTARG" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1)) # removes -v and the '--' separator
echo "Verbose: $VERBOSE"
echo "Files to process:"
for f in "$@"; do
echo " -> $f"
done将 getopts 封装在库函数中
在模块化脚本中,您可以将 getopts 封装在 parse_args() 函数中,由该函数设置全局变量(或 nameref 变量)。这样可以保持 main() 简洁,也能从其他脚本加载解析器。
此模式的主要规则:
- 在调用函数之前声明选项变量
- 使用
global变量,或通过 nameref(declare -n)传递值 - 输入无效时返回非零退出代码,以便
main做出相应处理
#!/usr/bin/env bash
# Global option variables
VERBOSE=0; OUTPUT=''; COUNT=1
parse_args() {
local opt
while getopts ':vn:o:h' opt; do
case "$opt" in
v) VERBOSE=1 ;;
n) COUNT="$OPTARG" ;;
o) OUTPUT="$OPTARG" ;;
h) echo "Usage: $(basename "$0") [-v] [-n N] [-o FILE]"; exit 0 ;;
:) echo "Error: -$OPTARG needs a value" >&2; return 1 ;;
\?) echo "Error: unknown option -$OPTARG" >&2; return 1 ;;
esac
done
shift $((OPTIND - 1))
ARGS=("$@") # leftover positional args stored in array
}
main() {
parse_args "$@" || exit 1
echo "verbose=$VERBOSE count=$COUNT output=${OUTPUT:--} args=${ARGS[*]}"
}
main "$@"完整的真实示例:日志归档器
下面是一个完整的真实脚本,它使用了本课程涵盖的所有内容:带必需参数的选项字符串、静默错误模式、用法函数、默认值、验证以及 OPTIND 移位。
请仔细学习其结构——对于您编写的任何需要命令行界面的脚本,都应以此为模板。
#!/usr/bin/env bash
# archive_logs.sh — compress and move logs older than N days
set -euo pipefail
DESTDIR='/tmp/log_archive'
DAYS=30
VERBOSE=0
usage() {
cat >&2 <<EOF
Usage: $(basename "$0") [-v] [-d DAYS] [-o DIR] SOURCE_DIR
-d DAYS Archive logs older than DAYS (default: 30)
-o DIR Destination directory (default: /tmp/log_archive)
-v Verbose output
-h Show this help
EOF
exit "${1:-0}"
}
while getopts ':d:o:vh' opt; do
case "$opt" in
d) DAYS="$OPTARG" ;;
o) DESTDIR="$OPTARG" ;;
v) VERBOSE=1 ;;
h) usage 0 ;;
:) echo "Error: -$OPTARG requires a value" >&2; usage 1 ;;
\?) echo "Error: unknown option -$OPTARG" >&2; usage 1 ;;
esac
done
shift $((OPTIND - 1))
# Validation
[[ $# -lt 1 ]] && { echo "Error: SOURCE_DIR is required" >&2; usage 1; }
[[ "$DAYS" =~ ^[0-9]+$ ]] || { echo "Error: DAYS must be numeric" >&2; exit 1; }
SOURCE="$1"
[[ -d "$SOURCE" ]] || { echo "Error: '$SOURCE' is not a directory" >&2; exit 1; }
mkdir -p "$DESTDIR"
[[ $VERBOSE -eq 1 ]] && echo "Archiving logs older than $DAYS days from $SOURCE to $DESTDIR"
find "$SOURCE" -name '*.log' -mtime "+$DAYS" -print | while read -r f; do
gzip -c "$f" > "$DESTDIR/$(basename "$f").gz"
[[ $VERBOSE -eq 1 ]] && echo " archived: $f"
done
echo "Done."快速检查:getopts 选项字符串
阅读下面的 getopts 调用,并选择对其行为的正确描述:
while getopts ':f:vq' opt; do课程回顾:掌握 getopts
您已经学会了如何在 Bash 中使用 getopts 构建专业的命令行界面。以下是关键要点总结:
- 选项字符串语法 — 不带冒号的字母是布尔标志;字母后的冒号表示该选项需要参数;开头的冒号启用静默错误模式。
- OPTARG — 自动保存需要参数的选项的参数值。
- OPTIND — 循环结束后使用
shift $((OPTIND - 1)),以便在$@中显示剩余的位置参数。 - 静默错误模式 — 生产环境中更推荐使用;请自行处理
:(缺少参数)和\?(未知选项)情况,以获得完全的控制。 - usage() 函数 — 始终编写一个;打印到 stderr,
-h时以 0 退出,发生错误时以 1 退出。 - 组合标志 — getopts 无需额外代码即可自动处理
-vq和-n5。 - 模块化模式 — 将 getopts 封装在
parse_args()函数中,以编写简洁且可复用的脚本。
掌握 getopts 后,您的脚本将从单一用途的工具转变为可靠、易用并遵循 Unix 约定的命令行程序。
常见问题解答
「使用 getopts 解析标志与参数」课时是免费的吗?
是的 — 「使用 getopts 解析标志与参数」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
「使用 getopts 解析标志与参数」这节课中我会学到什么?
使用 getopts 实现专业的命令行界面,支持短选项、必需参数和用法提示。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「使用 getopts 解析标志与参数」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?
能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用局部作用域与返回码设计函数
- 构建并引入可复用的 Bash 库
- 使用 getopts 解析标志与参数
- 在函数之间传递数组与关联映射