getopt

getopt #

用来定义选项,然后从参数列表中提取选项。

bash
# 简单示例:
getopt -o vhf:u:p:: -l help,user:,password:: -- -vf FILE --user USER --password=PASSWORD -- TEXT1 TEXT2
# 执行后输出字符串:
 -v -f 'FILE' --user 'USER' --password 'PASSWORD' -- 'TEXT1' 'TEXT2'
# 上面这段输出内容经过修改后是可被 bash 程序 eval 执行的,比如可以下面这条命令用来替换当前参数列表:
eval set -- "-v -f 'FILE' --user 'USER' --password 'PASSWORD' -- 'TEXT1' 'TEXT2'"
# 简单合并一下命令,变成这样,注意一定要加双引号(不然上面的单引号被解释器移除了,可能造成空格、分号等字符被 shell 特殊处理):
eval set -- "$(getopt ......)"
# 又比如下面这条命令用来设置一个参数数组,注意不能加双引号(不然参数列表变成一整个字符串了):
arr=( $(getopt ......) )


# 解释:
# 1) "-o vhf:u:p::" 用来定义单横杠选项 (-v, -h, -f xxx, -u xxx, -p[xxx])
# 2) "-l help,user:,password::" 用来定义双横杠选项 (--help, --user xxx, --password[=xxx])
# 3) 单冒号用来定义一个带值的选项,比如 "f:" 和 "user:" 分别匹配 "-f xxx" 和 "--user xxx"
# 4) 双冒号用来定义一个可带值的选项,比如 "p::" 可以匹配 "-p"(空值)或者 "-pxxx"(有值xxx但中间不能有空格),又比如 "password::" 可以匹配 "--password"(空值)或者 "--password=xxx"(有值xxx但中间必须用等号连接)
# 5)第一个双横杠表示选项定义结束,是必须要有的,后面出现的参数就是需要被处理的参数列表
# 6) 第二个双横杠将被处理的参数列表分割成两组,双横杠之前的参数列表会被提取成选项,双横杠之后的参数列表不会被提取成选项
# 7) 第二个双横杠本身也是可选的,不是必须的,如果没有双横杠,那么参数列表中所有非横杠开头的参数都会被保留放到最后
# 8) 参数列表中,无值的单横杠选项可以合并,比如 "-v -h" 相当于 "-vh",又比如 "-h -v -f xxx" 相当于 "-hvf xxx"(但不能写成 "-hfv xxx",因为这里带值的选项是 f 不是 v)


# 错误示例:
getopt -o vhf:u:p:: -l help,user:,password:: -- -x
# 退出码1,并输出错误信息:
getopt: invalid option -- 'x'       ### 这一行由 StdErr 管道输出 ###
 --         ### 这一行由 StdOut 管道输出 ###

Bash脚本案例 #

bash
# 定义一个 function 名为 foo:
foo() {
    local verbose=0
    local file=
    local user=
    local password=
    # 定义 opts 变量。注意:这里的定义语句和后面的赋值语句不要合并成一条语句,不然退出码永远是0,无法识别到异常退出。
    local opts=
    # 从传入本方法的参数列表中提取想要的选项(注意这里的 $@ 两边双引号不可省略):
    opts="$(getopt -o vhf:u:p:: -l help,user:,password:: -- "$@")"
    # 当 getopt 异常退出时(一般是输入了非法选项),本方法不再执行直接退出
    if [ $? != 0 ]; then
        return 1
    fi
    # 用提取后的参数列表覆盖原来的参数列表
    eval set -- "$opts"
    while true; do
        case "$1" in
            -h|--help)
                echo "Help"; return;;
            -v)
                verbose=1; shift;;
            -f)
                file="$2"; shift 2;;
            -u|--user)
                user="$2"; shift 2;;
            -p|--password)
                # 可选项没有值时是空串
                password="$2"; shift 2;;
            --)
                # 读取到双横杠时表示选项已处理完毕
                shift; break;;
            *)
                # 这里不会执行,因为 case 被已知的所有选项完全覆盖
                echo "Invalid option: '$1'"; return 1;;
        esac
    done
    # 剩下的是未被提取的参数:
    echo "TEXT args: $@"
    # Do something ...
}

# 执行 foo 方法,传入参数:
foo -vf FILE --user USER --password=PASSWORD TEXT

# TEXT 可以放在选项前面或选项中间,比如下面的效果和上面的一样:
foo TEXT -vf FILE --user USER --password=PASSWORD

# 如果 TEXT 部分,存在横杠开头的参数,则该参数会被 getopt 当做一个选项提取,会导致意外错误,
# 因此可以在 TEXT 之前添加两个横杠来告诉 getopt 停止提取选项,像这样:
foo -vf FILE --user USER --password=PASSWORD -- TEXT --TEXT2 --TEXT3
2025年7月25日