行時根命令的 PersistentPostRun 為什么沒有被調(diào)用?)
Cobra 子命令執(zhí)行時根命令的 PersistentPostRun 為什么沒有被調(diào)用【免費(fèi)下載鏈接】cobraA Commander for modern Go CLI interactions項(xiàng)目地址: https://gitcode.com/GitHub_Trending/co/cobra用 Cobragithub.com/spf13/cobra寫 Go CLI 時一個常見的困惑是把清理、收尾或統(tǒng)一后處理的邏輯放在根命令的PersistentPostRun里結(jié)果直接執(zhí)行根命令時它能跑一旦用戶執(zhí)行的是子命令這段邏輯就悄悄消失了。這不是 bug而是 Cobra 文檔明確說明的默認(rèn)行為命令鏈中只執(zhí)行找到的第一個 persistent 鉤子。本文基于 Cobra 倉庫自帶的 用戶指南 和源碼說明這個默認(rèn)規(guī)則是什么、如何在你自己的程序里復(fù)現(xiàn)并確認(rèn)原因以及兩種讓根命令的PersistentPostRun真正執(zhí)行的改法。先理清鉤子的執(zhí)行順序與兩條默認(rèn)規(guī)則用戶指南 的 “PreRun and PostRun Hooks” 一節(jié)給出了鉤子的執(zhí)行順序PersistentPreRunPreRunRunPostRunPersistentPostRun同一節(jié)里還有兩條直接決定你問題現(xiàn)象的規(guī)則繼承規(guī)則Persistent*Run函數(shù)會被子命令繼承執(zhí)行前提是子命令自己沒有聲明同類鉤子“ThePersistent*Runfunctions will be inherited by children if they do not declare their own”。只取第一個規(guī)則默認(rèn)情況下命令鏈上只執(zhí)行找到的第一個 persistent 鉤子“By default, only the first persistent hook found in the command chain is executed”。文檔原話點(diǎn)出了你遇到的現(xiàn)象子命令執(zhí)行時會跑根命令的PersistentPreRun但不會跑根命令的PersistentPostRun。另外注意一個前提條件文檔說明*PreRun和*PostRun只有在當(dāng)前命令聲明了Run函數(shù)時才會執(zhí)行。一個沒有Run/RunE的不可執(zhí)行命令不會觸發(fā)這條鉤子鏈。用最小示例復(fù)現(xiàn)這個現(xiàn)象下面這份示例完整取自 用戶指南是一個可直接編譯運(yùn)行的單文件程序需引入github.com/spf13/cobra。它定義了一個帶全套五個鉤子的rootCmd以及一個帶PreRun/Run/PostRun/PersistentPostRun的subCmd然后在同一進(jìn)程里先后執(zhí)行根命令和子命令各一次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() }文檔給出的示例輸出如下注意這是文檔示例用于對照現(xiàn)象不是你必須逐字得到的結(jié)果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第二段輸出里就不會出現(xiàn)rootCmd PersistentPostRun這正是你觀察到的現(xiàn)象。為什么會跳過根命令的 PersistentPostRun原因在 command.go 的 post 階段遍歷邏輯里執(zhí)行完當(dāng)前命令的Run/PostRun后Cobra 從實(shí)際執(zhí)行的那個命令開始沿p.Parent()逐級向根命令方向查找PersistentPostRunE/PersistentPostRun一旦在某一級找到非 nil 的鉤子就執(zhí)行它然后break結(jié)束遍歷——除非全局開關(guān)EnableTraverseRunHooks打開。把這條機(jī)制和“只取第一個”規(guī)則合起來看現(xiàn)象就可以完全解釋查找起點(diǎn)是子命令本身而不是根命令。子命令聲明了自己的PersistentPostRun它就是鏈上第一個被找到的鉤子執(zhí)行后遍歷即止根命令的鉤子根本輪不到。預(yù)處理側(cè)的遍歷command.go方向相同但上面示例的subCmd沒有聲明PersistentPreRun所以鏈上第一個被找到的PersistentPreRun是根命令的——這就是文檔示例里rootCmd PersistentPreRun能出現(xiàn)、而rootCmd PersistentPostRun不出現(xiàn)的原因不是 Pre/Post 兩套規(guī)則不對稱而是兩側(cè)各自命中的“第一個鉤子”不同。對應(yīng)的全局開關(guān)聲明在 cobra.goEnableTraverseRunHooks默認(rèn)值defaultTraverseRunHooks false見 cobra.go。另外提醒一個排查時的前置檢查如果你的“子命令”根本沒有Run/RunE它不可執(zhí)行*PreRun與*PostRun鉤子包括 persistent 的不會按上面的鏈條運(yùn)行——先確認(rèn)報錯或幫助輸出來自哪個命令再談鉤子順序。兩種讓根命令的 PersistentPostRun 執(zhí)行的方式方式一子命令不聲明自己的 PersistentPostRun利用繼承無全局副作用按繼承規(guī)則如果子命令自己沒有聲明PersistentPostRun它繼承并執(zhí)行父命令的。把示例中subCmd的PersistentPostRun字段刪掉后執(zhí)行root sub arg1 arg2時鏈上第一個也是唯一一個被找到的PersistentPostRun就是根命令的你的收尾邏輯會正常運(yùn)行。適用條件子命令不需要在Run之后做與父命令不同的收尾。如果每個層級都要有自己的 post 邏輯這種方式放不下用方式二。方式二打開 EnableTraverseRunHooks執(zhí)行所有父命令的持久鉤子用戶指南 給出的官方說法是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 斷言驗(yàn)證父、子命令都聲明了全套鉤子時執(zhí)行子命令child one two的鉤子執(zhí)行順序?yàn)閜arent PersistentPreRun child PersistentPreRun child PreRun child Run child PostRun child PersistentPostRun parent PersistentPostRun即persistent 預(yù)處理鉤子從根到葉依次執(zhí)行persistent 后處理鉤子從葉到根依次執(zhí)行根命令的PersistentPostRun在子命令的所有鉤子之后運(yùn)行。關(guān)掉開關(guān)時同一測試斷言的順序只剩child PersistentPreRun、child PreRun、child Run、child PostRun、child PersistentPostRun五項(xiàng)父命令的兩個 persistent 鉤子都不出現(xiàn)——這正好是你排查時應(yīng)該先確認(rèn)的“默認(rèn)行為基線”。驗(yàn)證與邊界確認(rèn)修復(fù)是否生效的方法就是跑一遍上面的復(fù)現(xiàn)程序檢查輸出中是否出現(xiàn)Inside rootCmd PersistentPostRun ...這一行開啟EnableTraverseRunHooks后進(jìn)一步對照 command_test.go 斷言的七項(xiàng)順序即可。幾個邊界需要注意EnableTraverseRunHooks是包級全局變量進(jìn)程內(nèi)所有命令共享。打開后父命令的 persistent 鉤子會全部執(zhí)行如果你的多層命令樹里各級鉤子都有副作用重復(fù)初始化、重復(fù)關(guān)閉資源等需要先審視這些鉤子本身是否冪等。同樣的“找到第一個即停止”遍歷對PersistentPreRun側(cè)同樣生效PersistentPostRunE/PersistentPreRunE變體走的也是同一段遍歷代碼見 command.go 與 command.go規(guī)則一致。不要把“子命令不跑PersistentPostRun”和“命令不可執(zhí)行”混為一談后者是命令缺少Run聲明整條鉤子鏈都不會按上述順序運(yùn)行。【免費(fèi)下載鏈接】cobraA Commander for modern Go CLI interactions項(xiàng)目地址: https://gitcode.com/GitHub_Trending/co/cobra創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考