shell上的gflags - shflags介绍

    #gflags #bash

    在处理命令行参数的选择上,很多 C++ 项目里都可以见到 gflags 的使用,之前的笔记也简单介绍过。

    在具体工作我们做项目时,各个功能使用合适的语言才是最佳选择,因此执行下来往往是多语言的场景。

    例如在 shell 脚本里调用 C++ 编译出的二进制( hadoop-job 使用 hadoop-streaming 时很常见),就会出现这样的场景: shell 脚本使用getopt处理命令行参数,二进制使用 gflags 处理,如果两者参数有重复,代码往往会变成这样:

    # analyse param for C++_compile_bin
    while getopts "..." opt
    do
        case "$opt" in
            "I")
            input_path=$OPTARG
            ;;
    do_sth_with_input_path ${input_path}
    ...
    
    ${C++_compile_bin} --input_path=${input_path}
    

    多了一层参数解析的环节,出于统一以及易维护的角度,我们会希望在 shell 里拥有类似 gflags 的处理方式,也就是我们今天要介绍的 shflags

    1. 简介

    如果用一句话描述清楚 shflags 的话,那就是

    shFlags is a port of the Google gflags library for Unix shell.

    使用上跟 gflags 极像,看个例子:

    #!/bin/sh
    #
    # This is the proverbial 'Hello, world!' script to demonstrate the most basic
    # functionality of shFlags.
    #
    # This script demonstrates accepts a single command-line flag of '-n' (or
    # '--name'). If a name is given, it is output, otherwise the default of 'world'
    # is output.
    
    # Source shflags.
    . ../shflags
    
    # Define a 'name' command-line string flag.
    DEFINE_string 'name' 'world' 'name to say hello to' 'n'
    
    # Parse the command-line.
    FLAGS "$@" || exit 1
    eval set -- "${FLAGS_ARGV}"
    
    echo "Hello, ${FLAGS_name}!"
    

    输出为

    $ sh hello_world.sh
    Hello, world!
    $ sh hello_world.sh  --name ying
    Hello, ying!
    

    解释一下hello_world.sh这个脚本

    . ../shflags导入 shflags 脚本.

    DEFINE_string 'name' 'world' 'name to say hello to' 'n'定义了一个 flags 变量,其中:

    1. 变量名: name,使用 --name 可以指定变量值FLAGS_name.
    2. 变量默认值: world,即没有指定时的默认值
    3. 变量描述: name to say hello to
    4. short变量名: n,使用 -n 可以指定变量值FLAGS_name

    FLAGS "$@" || exit $? 这句类似于 google::ParseCommandLineFlagsFLAGS是内置函数

    # Parse the flags.
    #
    # Args:
    #   unnamed: list: command-line flags to parse
    # Returns:
    #   integer: success of operation, or error
    FLAGS() {
        ...
    }
    

    eval set -- "${FLAGS_ARGV}" 重新设置了$@,留下未解析的参数 ${FLAGS_ARGV}.

    echo "Hello, ${FLAGS_name}!" 输出${FLAGS_name}.

    2. 类型

    shflags 支持多种类型,当然,底层都是 string.

    DEFINE_string 'name' 'world' 'name to say hello to' 'n'
    DEFINE_boolean 'force' false 'force overwriting' 'f'
    DEFINE_integer 'limit' 10 'number of items retuned' 'l'
    DEFINE_float 'time' '10.5' 'number of seconds to run' 't'
    

    boolen类型提前预定义了FLAGS_TRUE/FLAGS_FALSE用于比较。

    # 0
    echo ${FLAGS_TRUE}
    # 1
    echo ${FLAGS_FALSE}
    

    注意判断时都使用-eq -ne -le -lt -ge -gt,例如

    if [ ${FLAGS_force} -eq ${FLAGS_FALSE} ] ; then
    
    [ ${FLAGS_debug} -eq ${FLAGS_TRUE} ] || return
    

    3. FLAGS_HELP

    使用 shflags 后,可以自动生成 help 文档,例如

    $ sh hello_world.sh  --help
    USAGE: hello_world.sh [flags] args
    flags:
      -n,--name:  name to say hello to (default: 'world')
      -h,--help:  show this help (default: false)
    

    如果想要自定义 help,可以重新定义FLAGS_HELP

    FLAGS_HELP=`cat <<EOF
    commands:
      speak:  say something
      sing:   sing something
    EOF`
    

    此外还有很多内置变量,可以用于获取 flags 信息

    # Shared attributes:
    #   flags_error:  last error message
    #   flags_output: last function output (rarely valid)
    #   flags_return: last return value
    #
    #   __flags_longNames: list of long names for all flags
    #   __flags_shortNames: list of short names for all flags
    #   __flags_boolNames: list of boolean flag names
    #
    #   __flags_opts: options parsed by getopt
    #
    # Per-flag attributes:
    #   FLAGS_<flag_name>: contains value of flag named 'flag_name'
    #   __flags_<flag_name>_default: the default flag value
    #   __flags_<flag_name>_help: the flag help string
    #   __flags_<flag_name>_short: the flag short name
    #   __flags_<flag_name>_type: the flag type
    

    4. Notes

    熟悉 gflags 后, shflags 基本是属于上手即用的,介绍就到这了。建议了解下实现,包括函数如何组织、变量命名方式,甚至单测也很完备🐮,不懂的参考下之前的bash笔记

    尽管这样统一了命令行参数的处理方式,在我看来还是不够完美,因为还是要在不同的地方( shell/c++ )分别定义同名 flags,或许可以有一种通用的配置,类似于 proto 定义的方式,不同语言生成不同的 lib 文件,或者 shflags 能够支持 gflags 里的 –fromenv,都是不错的解决方法。

    5. References

    wiki of shflags