为什么我收到一个 nil 指针错误,这取决于我调用 BindPFlag 的位置?

我最近刚开始与Go合作,我遇到了一些与Cobra和Viper合作的行为,我不确定我是否理解。


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


package main


import (

    "github.com/larsks/example/cmd"

    "github.com/spf13/cobra"

)


func main() {

    rootCmd := cmd.NewCmdRoot()

    cobra.CheckErr(rootCmd.Execute())

}

在我有: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")

    }

}

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


panic: runtime error: invalid memory address or nil pointer dereference

[signal SIGSEGV: segmentation violation code=0x1 addr=0x50 pc=0x6a90e5]

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


这是怎么回事?根据蝰蛇关于使用的文档:BindPFlags


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


这几乎就是我在这里所做的。在我调用时,是非 nil,是非 nil,并且参数已被注册。config.BindPflagconfigcmdname


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


沧海一幻觉
浏览 84回答 2
2回答

神不在的星期二

我认为这很有趣,所以我做了一些挖掘,发现你的确切问题记录在一个问题中。有问题的一行是这样的:config.BindPFlag("name", cmd.Flags().Lookup("name"))//                           ^^^^^^^您创建了一个持久性标志,但将该标志绑定到该属性。如果将代码更改为 bind to ,则即使使用中的此行,一切也将按预期工作:FlagsPersistentFlagsNewCmdRootconfig.BindPFlag("name", cmd.PersistentFlags().Lookup("name"))

慕勒3428872

这最终比乍一看要复杂一些,所以虽然这里的其他答案帮助我解决了问题,但我想添加一些细节。文档中有一些细微差别,如果您刚刚开始使用Cobra,这些细微差别并不是特别清楚。让我们从 PersistentFlags 方法的文档开始:PersistentFlags 返回在当前命令中专门设置的持久性 FlagSet。关键在于...在当前命令中。在我的根方法中,我们可以使用,因为root命令是当前命令。我们甚至可以在方法中使用,只要我们不处理子命令。NewCmdRootcmd.PersistentFlags()cmd.PersistentFlags()PersistentPreRun如果我们要从示例中重写,以便它包含一个子命令,就像这样......cmd/root.gopackage cmdimport (    "fmt"    "os"    "github.com/spf13/cobra"    "github.com/spf13/viper")var cfgFile stringfunc NewCmdSubcommand() *cobra.Command {    var cmd = &cobra.Command{        Use:   "subcommand",        Short: "An example subcommand",        Run: func(cmd *cobra.Command, args []string) {            fmt.Printf("This is an example subcommand\n")        },    }    return cmd}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("Hello, world\n")        },    }    cmd.PersistentFlags().StringVar(    &cfgFile, "config", "", "config file (default is $HOME/.example.yaml)")    cmd.PersistentFlags().String("name", "", "a name")    cmd.AddCommand(NewCmdSubcommand())    err := config.BindPFlag("name", cmd.PersistentFlags().Lookup("name"))    if err != nil {        panic(err)    }    return cmd}func initConfig(cmd *cobra.Command, config *viper.Viper) {    name, err := cmd.PersistentFlags().GetString("name")    if err != nil {        panic(err)    }    fmt.Printf("name = %s\n", name)    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"))}...我们会发现它在执行root命令时有效:$ ./examplename =name isHello, world但是当我们运行子命令时,它失败了:[lars@madhatter go]$ ./example subcommandpanic: flag accessed but not defined: namegoroutine 1 [running]:example/cmd.initConfig(0xc000172000, 0xc0001227e0)        /home/lars/tmp/go/cmd/root.go:55 +0x368example/cmd.NewCmdRoot.func1(0xc000172000, 0x96eca0, 0x0, 0x0)        /home/lars/tmp/go/cmd/root.go:32 +0x34github.com/spf13/cobra.(*Command).execute(0xc000172000, 0x96eca0, 0x0, 0x0, 0xc000172000, 0x96eca0)        /home/lars/go/pkg/mod/github.com/spf13/cobra@v1.1.3/command.go:836 +0x231github.com/spf13/cobra.(*Command).ExecuteC(0xc00011db80, 0x0, 0xffffffff, 0xc0000240b8)        /home/lars/go/pkg/mod/github.com/spf13/cobra@v1.1.3/command.go:960 +0x375github.com/spf13/cobra.(*Command).Execute(...)        /home/lars/go/pkg/mod/github.com/spf13/cobra@v1.1.3/command.go:897main.main()        /home/lars/tmp/go/main.go:11 +0x2a这是因为子命令从根继承命令(这是部分的意思),但是当此方法运行时,传递给的参数不再是根命令;这是命令。当我们尝试调用 时,它会失败,因为当前命令没有任何与之关联的持久标志。PersistentPreRunPersistentcmdPersistentPreRunsubcommandcmd.PersistentFlags()在这种情况下,我们需要改用 Flags 方法:Flags 返回适用于此命令的完整 FlagSet(此处和所有父级声明的本地和持久性)。这使我们能够访问父级声明的持久标志。另一个问题(似乎没有在文档中明确说明)是,只有在命令处理运行后(即,在调用命令或父级之后)才可用。这意味着我们可以在 中使用它,但我们不能在 中使用它(因为该方法在我们处理命令行之前完成)。Flags()cmd.Execute()PersistentPreRunNewCmdRootTL;DR我们必须使用 in,因为我们正在寻找应用于当前命令的持久标志,并且 from 的值尚不可用。cmd.PersistentFlags()NewCmdRootFlags()我们需要使用 in(和其他持久命令方法),因为在处理子命令时,只会在当前命令上查找持久标志,但不会遍历父级。我们需要使用,这将汇总父级声明的持久标志。cmd.Flags()PersistentPreRunPersistentFlagscmd.Flags()
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go