尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Cobra 子命令执行时根命令的 PersistentPostRun 为什么没有被调用?

Cobra 子命令执行时根命令的 PersistentPostRun 为什么没有被调用? Cobra 子命令执行时根命令的 PersistentPostRun 为什么没有被调用【免费下载链接】cobraA Commander for modern Go CLI interactions项目地址: https://gitcode.com/GitHub_Trending/co/cobra用 Cobragithub.com/spf13/cobra写 Go CLI 时一个常见的困惑是把清理、收尾或统一后处理的逻辑放在根命令的PersistentPostRun里结果直接执行根命令时它能跑一旦用户执行的是子命令这段逻辑就悄悄消失了。这不是 bug而是 Cobra 文档明确说明的默认行为命令链中只执行找到的第一个 persistent 钩子。本文基于 Cobra 仓库自带的 用户指南 和源码说明这个默认规则是什么、如何在你自己的程序里复现并确认原因以及两种让根命令的PersistentPostRun真正执行的改法。先理清钩子的执行顺序与两条默认规则用户指南 的 “PreRun and PostRun Hooks” 一节给出了钩子的执行顺序PersistentPreRunPreRunRunPostRunPersistentPostRun同一节里还有两条直接决定你问题现象的规则继承规则Persistent*Run函数会被子命令继承执行前提是子命令自己没有声明同类钩子“ThePersistent*Runfunctions will be inherited by children if they do not declare their own”。只取第一个规则默认情况下命令链上只执行找到的第一个 persistent 钩子“By default, only the first persistent hook found in the command chain is executed”。文档原话点出了你遇到的现象子命令执行时会跑根命令的PersistentPreRun但不会跑根命令的PersistentPostRun。另外注意一个前提条件文档说明*PreRun和*PostRun只有在当前命令声明了Run函数时才会执行。一个没有Run/RunE的不可执行命令不会触发这条钩子链。用最小示例复现这个现象下面这份示例完整取自 用户指南是一个可直接编译运行的单文件程序需引入github.com/spf13/cobra。它定义了一个带全套五个钩子的rootCmd以及一个带PreRun/Run/PostRun/PersistentPostRun的subCmd然后在同一进程里先后执行根命令和子命令各一次package main import ( fmt github.com/spf13/cobra ) func main() { var rootCmd cobra.Command{ Use: root [sub], Short: My root command, PersistentPreRun: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside rootCmd PersistentPreRun with args: %v\n, args) }, PreRun: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside rootCmd PreRun with args: %v\n, args) }, Run: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside rootCmd Run with args: %v\n, args) }, PostRun: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside rootCmd PostRun with args: %v\n, args) }, PersistentPostRun: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside rootCmd PersistentPostRun with args: %v\n, args) }, } var subCmd cobra.Command{ Use: sub [no options!], Short: My subcommand, PreRun: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside subCmd PreRun with args: %v\n, args) }, Run: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside subCmd Run with args: %v\n, args) }, PostRun: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside subCmd PostRun with args: %v\n, args) }, PersistentPostRun: func(cmd *cobra.Command, args []string) { fmt.Printf(Inside subCmd PersistentPostRun with args: %v\n, args) }, } rootCmd.AddCommand(subCmd) rootCmd.SetArgs([]string{}) rootCmd.Execute() fmt.Println() rootCmd.SetArgs([]string{sub, arg1, arg2}) rootCmd.Execute() }文档给出的示例输出如下注意这是文档示例用于对照现象不是你必须逐字得到的结果Inside rootCmd PersistentPreRun with args: [] Inside rootCmd PreRun with args: [] Inside rootCmd Run with args: [] Inside rootCmd PostRun with args: [] Inside rootCmd PersistentPostRun with args: [] Inside rootCmd PersistentPreRun with args: [arg1 arg2] Inside subCmd PreRun with args: [arg1 arg2] Inside subCmd Run with args: [arg1 arg2] Inside subCmd PostRun with args: [arg1 arg2] Inside subCmd PersistentPostRun with args: [arg1 arg2]对照你自己的程序如果子命令声明了自己的PersistentPostRun第二段输出里就不会出现rootCmd PersistentPostRun这正是你观察到的现象。为什么会跳过根命令的 PersistentPostRun原因在 command.go 的 post 阶段遍历逻辑里执行完当前命令的Run/PostRun后Cobra 从实际执行的那个命令开始沿p.Parent()逐级向根命令方向查找PersistentPostRunE/PersistentPostRun一旦在某一级找到非 nil 的钩子就执行它然后break结束遍历——除非全局开关EnableTraverseRunHooks打开。把这条机制和“只取第一个”规则合起来看现象就可以完全解释查找起点是子命令本身而不是根命令。子命令声明了自己的PersistentPostRun它就是链上第一个被找到的钩子执行后遍历即止根命令的钩子根本轮不到。预处理侧的遍历command.go方向相同但上面示例的subCmd没有声明PersistentPreRun所以链上第一个被找到的PersistentPreRun是根命令的——这就是文档示例里rootCmd PersistentPreRun能出现、而rootCmd PersistentPostRun不出现的原因不是 Pre/Post 两套规则不对称而是两侧各自命中的“第一个钩子”不同。对应的全局开关声明在 cobra.goEnableTraverseRunHooks默认值defaultTraverseRunHooks false见 cobra.go。另外提醒一个排查时的前置检查如果你的“子命令”根本没有Run/RunE它不可执行*PreRun与*PostRun钩子包括 persistent 的不会按上面的链条运行——先确认报错或帮助输出来自哪个命令再谈钩子顺序。两种让根命令的 PersistentPostRun 执行的方式方式一子命令不声明自己的 PersistentPostRun利用继承无全局副作用按继承规则如果子命令自己没有声明PersistentPostRun它继承并执行父命令的。把示例中subCmd的PersistentPostRun字段删掉后执行root sub arg1 arg2时链上第一个也是唯一一个被找到的PersistentPostRun就是根命令的你的收尾逻辑会正常运行。适用条件子命令不需要在Run之后做与父命令不同的收尾。如果每个层级都要有自己的 post 逻辑这种方式放不下用方式二。方式二打开 EnableTraverseRunHooks执行所有父命令的持久钩子用户指南 给出的官方说法是By default, only the first persistent hook found in the command chain is executed. That is why in the above output, therootCmd PersistentPostRunwas not called for a child command. SetEnableTraverseRunHooksglobal variable totrueif you want to execute all parents persistent hooks.在Execute()之前打开这个包级全局变量import github.com/spf13/cobra func main() { cobra.EnableTraverseRunHooks true rootCmd.Execute() }打开后的效果由仓库自带测试 TestPersistentHooks 断言验证父、子命令都声明了全套钩子时执行子命令child one two的钩子执行顺序为parent PersistentPreRun child PersistentPreRun child PreRun child Run child PostRun child PersistentPostRun parent PersistentPostRun即persistent 预处理钩子从根到叶依次执行persistent 后处理钩子从叶到根依次执行根命令的PersistentPostRun在子命令的所有钩子之后运行。关掉开关时同一测试断言的顺序只剩child PersistentPreRun、child PreRun、child Run、child PostRun、child PersistentPostRun五项父命令的两个 persistent 钩子都不出现——这正好是你排查时应该先确认的“默认行为基线”。验证与边界确认修复是否生效的方法就是跑一遍上面的复现程序检查输出中是否出现Inside rootCmd PersistentPostRun ...这一行开启EnableTraverseRunHooks后进一步对照 command_test.go 断言的七项顺序即可。几个边界需要注意EnableTraverseRunHooks是包级全局变量进程内所有命令共享。打开后父命令的 persistent 钩子会全部执行如果你的多层命令树里各级钩子都有副作用重复初始化、重复关闭资源等需要先审视这些钩子本身是否幂等。同样的“找到第一个即停止”遍历对PersistentPreRun侧同样生效PersistentPostRunE/PersistentPreRunE变体走的也是同一段遍历代码见 command.go 与 command.go规则一致。不要把“子命令不跑PersistentPostRun”和“命令不可执行”混为一谈后者是命令缺少Run声明整条钩子链都不会按上述顺序运行。【免费下载链接】cobraA Commander for modern Go CLI interactions项目地址: https://gitcode.com/GitHub_Trending/co/cobra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表