【问题标题】:golang flag stops parsing after the first non-optiongolang 标志在第一个非选项后停止解析
【发布时间】:2018-09-16 05:42:44
【问题描述】:

我正在构建一个小 cli 工具,用于在开发或生产中引导我的应用程序。

我希望它的工作方式是这样的:

app run --dev or app run --prod

Atm 它不会在我的命令之后但仅在我的命令之前解析标志。所以这行得通

app --dev run or app --prod run

任何想法如何解决这个问题,以便我可以在我的命令后使用它?这是我的代码

func main() {
    //flag.Usage := usage
    flag.Parse()
    args := flag.Args()
    if len(args) == 0 {
        Usage()
        os.Exit(0)
    }

    if *dev {
        os.Setenv("ENV", "development")
    }

    if *prod {
        os.Setenv("ENV", "production")
    }

    switch {
    // Run
    case args[0] == "run" && len(args) == 1:
        os.Setenv("port", *port)
        log.Printf("Booting in %s", os.Getenv("ENV"))
        Run()

    // Help
    case args[0] == "help" && len(args) == 1:
        Usage()
    }
}

【问题讨论】:

  • 为什么不直接打电话给ENV=dev yourapp run - 这样可以避免你正在进行的奇怪的 SetEnv 舞蹈,直接设置它(并且只用于那个运行)。

标签: go flags command-line-interface


【解决方案1】:

传统上,UNIX 选项解析器getopt() 在第一个非选项之后停止解析。 glibc 改变了这种行为以支持任意位置的选项,这是一个有争议的决定。 flag 包实现了传统的行为。

您可以做的一件事是在解析标志之前排列参数数组。这就是 glibc 所做的:

func permutateArgs(args []string) int {
    args = args[1:]
    optind := 0

    for i := range args {
        if args[i][0] == '-' {
            tmp := args[i]
            args[i] = args[optind]
            args[optind] = tmp
            optind++
        }
    }

    return optind + 1
}

此代码对args 进行置换,使得选项位于前面,而程序名称保持不变。 permutateArgs 返回排列后第一个非选项的索引。像这样使用这个代码:

optind := permutateArgs(os.Args)
flags.Parse()

// process non-options
for i := range os.Args[optind:] {
    // ...
}

【讨论】:

  • @AnthonyDeMeulemeester 请再试一次。代码现在应该可以工作了。
  • 这是一个很棒的答案——尽管它不保留非标志参数的顺序。例如,给定参数a -x b c -y,您将得到-x -y b c a 而不是-x -y a b c。保留排序的解决方案会很棒!
  • 也仅适用于 Bool 标志。对于带值的标志,例如-o result.txt,此方法会将“result.txt”视为参数而不是选项标志。
  • @CodeExpress 是的,确实如此。调整代码以同时考虑其他类型的选项应该不会太难。
  • 谢谢。您还可以使用flag.Args() 更轻松地处理非选项
【解决方案2】:

这只是标志包处理参数的方式。参数处理总是有些约定俗成的,flag 包绝对不遵循许多人习惯的 gnu 风格。

一种方法是在命令之前对选项进行排序:

// example argument sorting using sort.Stable
type ArgSlice []string

func (p ArgSlice) Len() int      { return len(p) }
func (p ArgSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

func (p ArgSlice) Less(i, j int) bool {
    if len(p[i]) == 0 {
        return false
    }
    if len(p[j]) == 0 {
        return true
    }
    return p[i][0] == '-' && p[j][0] != '-'
}

func main() {

    args := []string{"cmd", "-a", "arg", "-b", "-c"}
    sort.Stable(ArgSlice(args))
    // if sorting os.Args, make sure to omit the first argument
    // sort.Stable(ArgSlice(os.Args[1:]))

    fmt.Println(args)
}

许多包对子命令使用单独的标志集,这提供了很好的分离,但是当子命令之间可能存在不同的标志时,这将不起作用。唯一的解决方案是在每个级别复制标志。

但是,最好的答案仍然是遵循 flag 包使用的约定,而不是试图与之抗争。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-04
    • 2012-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多