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.BindPFlag从NewCmdRoot
函数移动到initConfig命令的顶部,一切运行都没有问题。
这里发生了什么?根据有关使用的 Viper 文档
BindPFlags:
和 BindEnv 一样,该值不是在调用绑定方法时设置,而是在访问时设置。这意味着您可以根据需要尽早绑定,即使在 init() 函数中也是如此。
这几乎就是我在这里所做的。在我调用时
config.BindPflag,config非零,cmd非零,并且
name参数已注册。
我假设我config在 in 的闭包中使用了一些东西PersistentPreRun,但我不知道为什么会导致这个失败。
如果我使用的话,没有任何问题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()级别上,标志尚未初始化,即使在某些标志被声明为“持久”之后也是如此。