6. 持久化命令与Go引用类型的使用
如果在 公众号 文章发现状态为 已更新, 建议点击 查看原文 查看最新内容。
状态: 未更新
原文链接: https://typonotes.com/posts/2023/02/19/devopscamp-cobra-06-persistent-run-and-flags/
嗯, 在 cobra 中提供了一种叫做 Persistent
的 状态, 定向支持 函数 与 参数。
下面这段代码是是使用时的定义。
var root = &cobra.Command{
Use: "root",
// Persistent Run (1)
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("PersistentPreRun in root")
},
Run: func(cmd *cobra.Command, args []string) {
_ = cmd.Help()
},
}
var config string
func init() {
// Persistent Flag (2)
root.PersistentFlags().StringVarP(&config, "config", "c", "~/.config.json", "配置文件")
}
凡是定义了 Persistent
Run(1) 和 Flag(2) 的节点, 其子孙节点都会 继承 这种状态。 这种状态也可以被子孙节点的自定义状态覆盖。
注意: 这种状态是继承自 其父节点, 而非 上级节点 。
这个的问题的发生原因, 还是在 xxx.AddCommand
的时候造成的。 因为 &cobra.Command{}
是指针对象/引用对象, 因此在不同的地方修改是会全局影响的。 这个是基础知识, 也是重要的 坑 点, 需要牢记。
下面是一个代码案例, 帮助理解。
案例代码在在 Github: https://github.com/tangx-labs/cobra06-demo
代码中实现了一个如下图所示的 命令树 结构。 其中 sub2
同时挂载到了 root
和 sub1
节点。
以下是代码执行结果执行结果, 注意看
--config
的参数值PersistentPreRun
的执行结果sub2
的Usage
路径, 无论从哪里进入, 都是root sub1 sub2
。
./cobra-demo6
# ./cobra-demo6
PersistentPreRun in root
Flags:
-c, --config string 配置文件 (default "~/.config.json")
./cobra-demo6 sub1
# ./cobra-demo6 sub1
PersistentPreRun in sub1
Flags:
-c, --config string 配置文件 (default "$HOME/.config.json")
./cobra06-demo sub2
# ./cobra06-demo sub2 # (1)
PersistentPreRun in sub1 # (2)
Usage:
root sub1 sub2 [flags] # (3)
Global Flags:
-c, --config string 配置文件 (default "$HOME/.config.json") # (4)
./cobra06-demo sub1 sub2
# ./cobra06-demo sub1 sub2 #(1)
PersistentPreRun in sub1 #(2)
Usage:
root sub1 sub2 [flags] #(3)
Global Flags:
-c, --config string 配置文件 (default "$HOME/.config.json") #(4)
首先, 我们再来回顾一下一下 Cobra Command
的结构体
type Command struct {
Use string
parent *Command // 父命令
commands []*Command // 子命令
}
可以看到, parent *Command
是指针类型。 这意味着, 在任何地方修改都会影响全局引用。
这里简单的回顾一下 引用对象。 A, B, C 都指向 指针地址, 而 指针地址 纸箱 真实数据地址。 当因为某个外力修改了 真实数据地址 中的内容的时候, 虽然 A, B, C 都没变化, 但是他们 取 到的东西发生了变化。
举个例子,
- 你去温泉之前寄存物品, 商家会给你一个 手牌(指针地址), 你拿着手牌将 名贵手表(数据) 放入对应的 100号柜子(真实地址)。
- 在你泡温泉的时候, 某个人(外力) 打开了柜子, 把你的手表换成了 塑料手表(数据修改)。
- 等你回来的时候, 虽然你的手牌没变, 但是你打开柜子的时候, 你拿到的不再你期望的东西了。
而正好, 每次执行 xxx.AddCommand(children)
添加子命令的时候, children
节点的 parent
字段都会被修改。
func (c *Command) AddCommand(cmds ...*Command) {
for i, x := range cmds {
if cmds[i] == c {
panic("Command can't be a child of itself")
}
cmds[i].parent = c // child 的父节点会被修改
// 省略
}
}
当一个节点, 重复被假如到其他节点的时候, 会出现这个问题。
下面, 我们对命令树的实现过程进行拆分。 注意 每个节点分为上下两层, 上层 表示父节点的名称, 下层 表示当前节点。
parent
----------
node
当执行第一条命令时时候, 此时创建的命令树就有所差异了。 左侧时从 root
开始, 右侧是从 sub1
开始。
当执行的第二条命令的时候, 都实现了相同结构的命令树(不看父节点差异的话)。
但仔细分析其内部节点, 可以知道, 相同位置的节点, 其父节点不一样。