Golang中Cobra/Viper绑定Flags到RootCmd时的异常行为解析

Golang中Cobra/Viper绑定Flags到RootCmd时的异常行为解析 以下内容(main.go 中的所有内容)可以正常工作

var rootCmd = &cobra.Command{
	Use:           "myapp",
	Short:         "Myapp",
	PreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("binding variables")
		if err := viper.BindPFlags(cmd.Flags()); err != nil {
			return err
		}
		fmt.Println(viper.GetString("allow-origin"))
		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		return viper.BindPFlags(cmd.Flags())
	},
}

func main() {

	rootCmd.PersistentFlags().StringP("allow-origin", "", "", "help")

	rootCmd.Execute()
▶ go run web/main.go myapp --allow-origin https://111.222.33.4
binding variables
https://111.222.33.4

但是,当 rootCmd 的定义和标志的添加被移到另一个包(cmd)时,以下代码无法正常工作

package main

func main() {

	cmd.RootCmd.PersistentFlags().StringP("allow-origin", "", "", "help")


	cmd.RootCmd.Execute()
package cmd

var RootCmd = &cobra.Command{
	Use:           "myapp",
	Short:         "Myapp",
	PreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("binding variables")
		if err := viper.BindPFlags(cmd.Flags()); err != nil {
			return err
		}
		fmt.Println(viper.GetString("allow-origin"))
		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		return viper.BindPFlags(cmd.Flags())
	},
}
▶ go run web/main.go myapp --allow-origin https://111.222.33.4
binding variables

也就是说,在第二种情况下,标志 allow-origin 没有被绑定(!)

我遗漏了什么?


更多关于Golang中Cobra/Viper绑定Flags到RootCmd时的异常行为解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中Cobra/Viper绑定Flags到RootCmd时的异常行为解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题在于标志的添加时机。当 rootCmd 被定义在 cmd 包中,而标志在 main 包中添加时,PreRunE 执行时标志尚未附加到命令上。

在 Cobra 中,标志需要在命令执行前添加。当你在 main 包中添加标志时,PreRunE 在标志添加之前就被调用了,导致 cmd.Flags() 不包含 allow-origin 标志。

以下是修正后的代码:

main.go

package main

import (
	"yourproject/cmd"
)

func main() {
	cmd.Execute()
}

cmd/root.go

package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var RootCmd = &cobra.Command{
	Use:   "myapp",
	Short: "Myapp",
	PreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("binding variables")
		if err := viper.BindPFlags(cmd.Flags()); err != nil {
			return err
		}
		fmt.Println(viper.GetString("allow-origin"))
		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		return viper.BindPFlags(cmd.Flags())
	},
}

func init() {
	RootCmd.PersistentFlags().StringP("allow-origin", "", "", "help")
}

func Execute() error {
	return RootCmd.Execute()
}

关键改动:

  1. 将标志添加移到 cmd 包的 init() 函数中,确保在命令执行前标志已附加
  2. 创建 Execute() 函数来封装命令执行

这样就能正确绑定标志了:

▶ go run main.go myapp --allow-origin https://111.222.33.4
binding variables
https://111.222.33.4

如果需要在 main 包中配置标志,可以改为:

main.go

package main

import (
	"yourproject/cmd"
)

func main() {
	cmd.RootCmd.PersistentFlags().StringP("allow-origin", "", "", "help")
	cmd.Execute()
}

cmd/root.go

package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var RootCmd = &cobra.Command{
	Use:   "myapp",
	Short: "Myapp",
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("binding variables")
		if err := viper.BindPFlags(cmd.Flags()); err != nil {
			return err
		}
		fmt.Println(viper.GetString("allow-origin"))
		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		return viper.BindPFlags(cmd.Flags())
	},
}

func Execute() error {
	return RootCmd.Execute()
}

使用 PersistentPreRunE 而不是 PreRunE,确保在子命令中也能正确绑定标志。

回到顶部