如何解决为什么根据我调用 BindPFlag 的位置会出现 nil 指针错误?
我最近才开始使用 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())
}
在 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"))
}
此代码将在最终调用时出现 nil 指针引用而导致恐慌
fmt.Printf
:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x50 pc=0x6a90e5]
如果我将呼叫从 config.BindPFlag
函数到 NewCmdRoot
命令的顶部,一切运行
没有问题。
这是怎么回事?根据 Viper 文档关于使用
initConfig
:
和BindEnv一样,绑定方法是不设置值 调用,但是当它被访问时。这意味着您可以尽早绑定 你想要,即使在 init() 函数中。
这几乎就是我在这里所做的。当时我打电话
BindPFlags
,config.BindPflag
非零,config
非零,并且
cmd
参数已注册。
我认为我在使用 name
config
中的闭包,但我不知道为什么会这样
导致此故障。
解决方法
我觉得这很有趣,所以我做了一些挖掘和found your exact problem documented in an issue。有问题的线路是这样的:
config.BindPFlag("name",cmd.Flags().Lookup("name"))
// ^^^^^^^
您创建了一个持久性标志,但将该标志绑定到 Flags
属性。如果您将代码更改为绑定到 PersistentFlags
,即使在 NewCmdRoot
中使用此行,一切都会按预期工作:
config.BindPFlag("name",cmd.PersistentFlags().Lookup("name"))
,
如果我使用 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)
考虑到您刚刚注册了 persistent flags(标志将可用于分配给它的命令以及该命令下的每个命令),调用 cmd.PersistentFlags().Lookup("name")
而不是 {{1} 更安全}.
后者返回 cmd.Flags().Lookup("name")
,因为只有在调用 nil
时才调用 PersistentPreRun
,即 after rootCmd.Execute()
。
在 cmd.NewCmdRoot()
级别,标志尚未初始化,即使在某些标志被声明为“持久”之后也是如此。
这最终会比乍一看更复杂一些,所以虽然这里的其他答案帮助我解决了问题,但我想添加一些细节。
如果您刚开始使用 Cobra,文档中有一些细微差别并不是特别清楚。让我们从 PersistentFlags
方法的文档开始:
PersistentFlags 返回当前命令中专门设置的持久化 FlagSet。
密钥在...在当前命令中。在我的 NewCmdRoot
root 方法中,我们可以使用 cmd.PersistentFlags()
,因为 root 命令是当前命令。我们甚至可以在 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)
},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,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,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
这是因为子命令从根继承了 PersistentPreRun
命令(这就是 Persistent
部分的意思),但是当这个方法运行时,cmd
参数传递给 {{ 1}} 不再是根命令;这是 PersistentPreRun
命令。当我们尝试调用 subcommand
时,它失败了,因为当前命令没有任何与之关联的持久标志。
在这种情况下,我们需要改用 Flags
方法:
Flags 返回适用于该命令的完整 FlagSet(在此处和所有父级声明的本地和持久性)。
这使我们可以访问父母声明的持久标志。
文档中似乎没有明确指出的另一个问题是 cmd.PersistentFlags()
仅在运行命令处理后才可用(即,在您调用 Flags()
之后命令或父母)。这意味着我们可以在 cmd.Execute()
中使用它,但我们不能在 PersistentPreRun
中使用它(因为该方法在我们处理命令行之前完成)。
TL;博士
- 我们必须在
NewCmdRoot
中使用cmd.PersistentFlags()
,因为我们正在寻找应用于当前命令的持久标志,并且NewCmdRoot
中的值将获胜尚不可用。 - 我们需要在
Flags()
(和其他持久命令方法)中使用cmd.Flags()
因为在处理子命令时,PersistentPreRun
只会在 当前命令上查找持久标志,但不会遍历父母。我们需要改用PersistentFlags
,它将汇总父母声明的持久标志。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。