为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
沧海一幻觉 2022-09-05 09:46:38
我最近刚开始与Go合作,我遇到了一些与Cobra和Viper合作的行为,我不确定我是否理解。这是您通过运行 获得的示例代码的略微修改版本。在我有:cobra initmain.gopackage mainimport (    "github.com/larsks/example/cmd"    "github.com/spf13/cobra")func main() {    rootCmd := cmd.NewCmdRoot()    cobra.CheckErr(rootCmd.Execute())}在我有:cmd/root.gopackage cmdimport (    "fmt"    "os"    "github.com/spf13/cobra"    "github.com/spf13/viper")var cfgFile stringfunc 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.Printfpanic: 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
查看完整描述

2 回答

?
神不在的星期二

TA贡献1963条经验 获得超6个赞


我认为这很有趣,所以我做了一些挖掘,发现你的确切问题记录在一个问题中。有问题的一行是这样的:


config.BindPFlag("name", cmd.Flags().Lookup("name"))

//                           ^^^^^^^

您创建了一个持久性标志,但将该标志绑定到该属性。如果将代码更改为 bind to ,则即使使用中的此行,一切也将按预期工作:FlagsPersistentFlagsNewCmdRoot


config.BindPFlag("name", cmd.PersistentFlags().Lookup("name"))


查看完整回答
反对 回复 2022-09-05
?
慕勒3428872

TA贡献1848条经验 获得超6个赞

这最终比乍一看要复杂一些,所以虽然这里的其他答案帮助我解决了问题,但我想添加一些细节。


文档中有一些细微差别,如果您刚刚开始使用Cobra,这些细微差别并不是特别清楚。让我们从 PersistentFlags 方法的文档开始:


PersistentFlags 返回在当前命令中专门设置的持久性 FlagSet。


关键在于...在当前命令中。在我的根方法中,我们可以使用,因为root命令是当前命令。我们甚至可以在方法中使用,只要我们不处理子命令。NewCmdRootcmd.PersistentFlags()cmd.PersistentFlags()PersistentPreRun


如果我们要从示例中重写,以便它包含一个子命令,就像这样......cmd/root.go


package cmd


import (

    "fmt"

    "os"


    "github.com/spf13/cobra"

    "github.com/spf13/viper"

)


var cfgFile string


func 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命令时有效:


$ ./example

name =

name is

Hello, world

但是当我们运行子命令时,它失败了:


[lars@madhatter go]$ ./example subcommand

panic: flag accessed but not defined: name


goroutine 1 [running]:

example/cmd.initConfig(0xc000172000, 0xc0001227e0)

        /home/lars/tmp/go/cmd/root.go:55 +0x368

example/cmd.NewCmdRoot.func1(0xc000172000, 0x96eca0, 0x0, 0x0)

        /home/lars/tmp/go/cmd/root.go:32 +0x34

github.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 +0x231

github.com/spf13/cobra.(*Command).ExecuteC(0xc00011db80, 0x0, 0xffffffff, 0xc0000240b8)

        /home/lars/go/pkg/mod/github.com/spf13/cobra@v1.1.3/command.go:960 +0x375

github.com/spf13/cobra.(*Command).Execute(...)

        /home/lars/go/pkg/mod/github.com/spf13/cobra@v1.1.3/command.go:897

main.main()

        /home/lars/tmp/go/main.go:11 +0x2a

这是因为子命令从根继承命令(这是部分的意思),但是当此方法运行时,传递给的参数不再是根命令;这是命令。当我们尝试调用 时,它会失败,因为当前命令没有任何与之关联的持久标志。PersistentPreRunPersistentcmdPersistentPreRunsubcommandcmd.PersistentFlags()


在这种情况下,我们需要改用 Flags 方法:


Flags 返回适用于此命令的完整 FlagSet(此处和所有父级声明的本地和持久性)。


这使我们能够访问父级声明的持久标志。


另一个问题(似乎没有在文档中明确说明)是,只有在命令处理运行后(即,在调用命令或父级之后)才可用。这意味着我们可以在 中使用它,但我们不能在 中使用它(因为该方法在我们处理命令行之前完成)。Flags()cmd.Execute()PersistentPreRunNewCmdRoot


TL;DR


我们必须使用 in,因为我们正在寻找应用于当前命令的持久标志,并且 from 的值尚不可用。cmd.PersistentFlags()NewCmdRootFlags()

我们需要使用 in(和其他持久命令方法),因为在处理子命令时,只会在当前命令上查找持久标志,但不会遍历父级。我们需要使用,这将汇总父级声明的持久标志。cmd.Flags()PersistentPreRunPersistentFlagscmd.Flags()


查看完整回答
反对 回复 2022-09-05
  • 2 回答
  • 0 关注
  • 83 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号