为什么根据我调用 BindPFlag 的位置会出现 nil 指针错误?

lar*_*sks 6 go viper-go go-cobra

我最近才开始使用 Go,并且在使用 Cobra 和 Viper 时遇到了一些我不确定我是否理解的行为。

这是您通过运行获得的示例代码的略微修改版本cobra init。在main.go我有:

package main

import (
    "github.com/larsks/example/cmd"
    "github.com/spf13/cobra"
)

func main() {
    rootCmd := cmd.NewCmdRoot()
    cobra.CheckErr(rootCmd.Execute())
}
Run Code Online (Sandbox Code Playgroud)

cmd/root.go我有:

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"

    "github.com/spf13/viper"
)

var cfgFile string

func NewCmdRoot() *cobra.Command {
    config := viper.New()

    var cmd = &cobra.Command{
        Use:   "example",
        Short: "A brief description of your application",
        PersistentPreRun: func(cmd *cobra.Command, args []string) {
            initConfig(cmd, config)
        },
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("This is a test\n")
        },
    }

    cmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.example.yaml)")
    cmd.PersistentFlags().String("name", "", "a name")

  // *** If I move this to the top of initConfig
  // *** the code runs correctly.
    config.BindPFlag("name", cmd.Flags().Lookup("name"))

    return cmd
}

func initConfig(cmd *cobra.Command, config *viper.Viper) {
    if cfgFile != "" {
        // Use config file from the flag.
        config.SetConfigFile(cfgFile)
    } else {
        config.AddConfigPath(".")
        config.SetConfigName(".example")
    }

    config.AutomaticEnv() // read in environment variables that match

    // If a config file is found, read it in.
    if err := config.ReadInConfig(); err == nil {
        fmt.Fprintln(os.Stderr, "Using config file:", config.ConfigFileUsed())
    }

  // *** This line triggers a nil pointer reference.
    fmt.Printf("name is %s\n", config.GetString("name"))
}
Run Code Online (Sandbox Code Playgroud)

此代码将在最终调用时出现 nil 指针引用而导致恐慌 fmt.Printf

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x50 pc=0x6a90e5]
Run Code Online (Sandbox Code Playgroud)

如果我将调用config.BindPFlagNewCmdRoot 函数移动到initConfig命令的顶部,一切运行都没有问题。

这里发生了什么?根据有关使用的 Viper 文档 BindPFlags

和 BindEnv 一样,该值不是在调用绑定方法时设置,而是在访问时设置。这意味着您可以根据需要尽早绑定,即使在 init() 函数中也是如此。

这几乎就是我在这里所做的。在我调用时 config.BindPflagconfig非零,cmd非零,并且 name参数已注册。

我假设我config在 in 的闭包中使用了一些东西PersistentPreRun,但我不知道为什么会导致这个失败。

Von*_*onC 1

如果我使用的话,没有任何问题cmd.PersistentFlags().Lookup("name")

    // *** If I move this to the top of initConfig
    // *** the code runs correctly.
    pflag := cmd.PersistentFlags().Lookup("name")
    config.BindPFlag("name", pflag)
Run Code Online (Sandbox Code Playgroud)

考虑到您刚刚注册了持久标志(标志将可用于分配给它的命令以及该命令下的每个命令),调用cmd.PersistentFlags().Lookup("name"),而不是 更安全cmd.Flags().Lookup("name")

后者返回nil,因为PersistentPreRun仅在rootCmd.Execute()调用时才调用 ,即在之后 cmd.NewCmdRoot()调用。
cmd.NewCmdRoot()级别上,标志尚未初始化,即使在某些标志被声明为“持久”之后也是如此。