0Pricing
Linux Command Line & Bash Scripting Mastery · 课时

使用 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用局部作用域与返回码设计函数
  2. 构建并引入可复用的 Bash 库
  3. 使用 getopts 解析标志与参数
  4. 在函数之间传递数组与关联映射
← 返回 Linux Command Line & Bash Scripting Mastery