命令類完全指南:Command / RunningCommand / OProc 與異常體系深度解析)
開(kāi)發(fā)工具【免費(fèi)下載鏈接】shPython process launching項(xiàng)目地址https://gitcode.com/gh_mirrors/sh/sh點(diǎn)擊查看免費(fèi)下載本篇指南以 sh 官方文檔中的 Command Class 章節(jié)為骨架系統(tǒng)講解 sh 的三大核心類——Command待執(zhí)行的程序、RunningCommand執(zhí)行中的實(shí)例、OProc底層進(jìn)程包裝器——以及配套的異常體系ErrorReturnCode、SignalException、TimeoutException、CommandNotFound與which、pushd兩個(gè)輔助函數(shù)。讀完你不僅會(huì)正確使用sh.Command與動(dòng)態(tài)屬性調(diào)用還能掌握后臺(tái)任務(wù)的wait()語(yǔ)義、進(jìn)程信號(hào)控制與超時(shí)處理并能用源碼證據(jù)理解每一個(gè) API 的底層行為。1. Command 類代表系統(tǒng)上存在的程序Command類表示系統(tǒng)上存在、可以在某個(gè)時(shí)間點(diǎn)被運(yùn)行的程序。它本身永遠(yuǎn)處于未運(yùn)行狀態(tài)——當(dāng)你執(zhí)行它時(shí)sh 會(huì)另外生成一個(gè)RunningCommand實(shí)例來(lái)代表正在執(zhí)行這件事詳見(jiàn) src/sh/init.py 中的類注釋。一個(gè)Command實(shí)例既可以手動(dòng)實(shí)例化也可以通過(guò)動(dòng)態(tài)查找自動(dòng)創(chuàng)建import sh ls1 sh.Command(ls) ls2 sh.ls assert ls1 ls2上面的sh.ls之所以能工作是因?yàn)?sh 在模塊層實(shí)現(xiàn)了動(dòng)態(tài)屬性解析PEP 562 的__getattr__見(jiàn) src/sh/init.pyi 中_SelfWrapper的說(shuō)明任何在模塊作用域里找不到的名字都會(huì)被當(dāng)作系統(tǒng)程序名去$PATH中查找。測(cè)試 tests/sh_test.py 也驗(yàn)證了兩個(gè)指向同一程序的Command實(shí)例彼此相等。1.1 構(gòu)造函數(shù)Command(name, search_pathsNone)from sh import Command ifconfig Command(ifconfig) # 在 $PATH 中查找 ifconfig Command(/sbin/ifconfig) # 直接使用完整路徑參數(shù)說(shuō)明name要么是$PATH中存在的程序名要么是一個(gè)完整的文件路徑。search_paths可選如果指定則必須是查找程序名時(shí)要搜索的所有路徑組成的列表指定后$PATH環(huán)境變量不再參與查找。從源碼看構(gòu)造過(guò)程實(shí)際調(diào)用了_which()src/sh/init.py它先判斷name是否含路徑分隔符——含路徑時(shí)直接測(cè)試該文件是否可執(zhí)行不含路徑時(shí)依次遍歷搜索路徑用is_exe()檢查文件存在 可執(zhí)行位os.X_OK 是真實(shí)文件三個(gè)條件。如果最終沒(méi)找到構(gòu)造函數(shù)會(huì)直接拋出CommandNotFound(path)src/sh/init.py。1.2 易錯(cuò)點(diǎn)不要把參數(shù)拼進(jìn)命令名字里與 Bash 不同傳給sh.Command的參數(shù)必須保持分離。下面兩種寫(xiě)法都會(huì)失敗即使你給出了正確的完整路徑lscmd sh.Command(/bin/ls -l) # 錯(cuò)誤 tarcmd sh.Command(/bin/tar cvf /tmp/test.tar /my/home/directory/) # 錯(cuò)誤這會(huì)觸發(fā)CommandNotFound(path)異?!?yàn)?sh 會(huì)把/bin/ls -l當(dāng)成一個(gè)完整的文件名去查找。正確做法分兩步只用二進(jìn)制可執(zhí)行文件本身構(gòu)建Command對(duì)象在調(diào)用時(shí)傳入?yún)?shù)。lscmd sh.Command(/bin/ls) lscmd(-l) tarcmd sh.Command(/bin/tar) tarcmd(cvf, /tmp/test.tar, /my/home/directory/)順帶一提resolve_command_path()src/sh/init.py還做了一件事當(dāng)程序名查找失敗時(shí)會(huì)嘗試把下劃線替換成連字符再查一次例如sh.gcc_4_8這類 Python 中無(wú)法直接用連字符的命名。1.3 烘焙參數(shù)Command.bake(*args, **kwargs)bake()返回一個(gè)新的Command把*args和**kwargs分別烘焙為位置參數(shù)和關(guān)鍵字參數(shù)。之后任何對(duì)返回對(duì)象的調(diào)用都會(huì)自動(dòng)帶上這些參數(shù)from sh import ls long_ls ls.bake(-l) print(ls(/var)) print(ls(/tmp))bake()內(nèi)部通過(guò)type(self)(self._path)構(gòu)建同類型的新實(shí)例并把烘焙的參數(shù)緩存在_partial_baked_args里直到真正調(diào)用__call__時(shí)才編譯成命令行字符串見(jiàn) src/sh/init.py。同時(shí)它支持烘焙特殊關(guān)鍵字參數(shù)如_env并且這些參數(shù)可以在后續(xù)調(diào)用或再次bake時(shí)覆蓋本質(zhì)是設(shè)置默認(rèn)值。測(cè)試 tests/sh_test.py 還驗(yàn)證了烘焙參數(shù)總是排在調(diào)用時(shí)傳入的參數(shù)之前。有關(guān)烘焙的完整語(yǔ)義含特殊參數(shù)的優(yōu)先級(jí)請(qǐng)參閱 baking 章節(jié)。2. RunningCommand 類正在執(zhí)行的 CommandRunningCommand代表一個(gè)已經(jīng)或正在被執(zhí)行的Command實(shí)例。它是對(duì)底層OProc的包裝sh 的使用者日常打交道最多的就是它。只有當(dāng)你以_return_cmdTrue執(zhí)行命令時(shí)才會(huì)返回該類的實(shí)例。import sh p sh.sleep(10, _return_cmdTrue) # p 是 RunningCommand 實(shí)例需要說(shuō)明的是sh 默認(rèn)返回的是類字符串結(jié)果str(p)會(huì)輸出進(jìn)程的 stdout見(jiàn) src/sh/init.py這也正是文檔中下述警告的由來(lái)。2.1 警告它像字符串但不是字符串該類的對(duì)象在行為上非常像字符串。這是有意的設(shè)計(jì)決策目的是讓正在執(zhí)行命令的輸出表現(xiàn)得更直觀。但請(qǐng)務(wù)必注意只接受真正的字符串的函數(shù)例如json.dumps無(wú)法直接處理RunningCommand實(shí)例盡管它看起來(lái)像字符串。從類型聲明看RunningCommand繼承自strsrc/sh/init.pyi并實(shí)現(xiàn)了__contains__、__float__、__int__、__eq__等字符串友好接口src/sh/init.py但并未真正把輸出傳給第三方字符串解析函數(shù)。需要原始輸出時(shí)應(yīng)使用str(p)或讀取p.stdout。2.2 核心方法wait(timeoutNone)p.wait() # 阻塞直到命令完成 p.wait(timeout1) # 最多等 1 秒行為要點(diǎn)timeout可選的非負(fù)秒數(shù)。若到點(diǎn)仍未完成拋出TimeoutException傳入負(fù)數(shù)會(huì)直接拋出RuntimeError(timeout cannot be negative)。阻塞等待命令完成并獲得退出碼如果退出碼代表失敗則拋出對(duì)應(yīng)的異常見(jiàn)第 4 節(jié)異常體系。該方法多次調(diào)用只在第一次拋異常后續(xù)調(diào)用返回self因?yàn)閮?nèi)部由_waited_until_completion標(biāo)志保護(hù)src/sh/init.py。普通命令由 sh自動(dòng)調(diào)用wait()只有命令以異步方式_asyncTrue或_bgTrue執(zhí)行時(shí)才需要你手動(dòng)調(diào)用以保證完成。若一個(gè)Command實(shí)例被用作 stdin 參數(shù)管道場(chǎng)景wait()也會(huì)被調(diào)用在該實(shí)例上其產(chǎn)生的任何異常都會(huì)向上傳播。從源碼看帶timeout的wait()采用每 0.1 秒輪詢一次is_alive()的循環(huán)實(shí)現(xiàn)src/sh/init.py超時(shí)后拋出TimeoutException(None, self.ran)若命令本身帶_timeout且超時(shí)被殺則拋出攜帶被信號(hào)殺死的退出碼的TimeoutExceptionsrc/sh/init.py。對(duì)應(yīng)測(cè)試覆蓋了超時(shí)、超時(shí)越過(guò)、負(fù)數(shù)超時(shí)等場(chǎng)景tests/sh_test.py。2.3 只讀屬性process、stdout、stderr、exit_codeprocess底層的OProc實(shí)例。stdout/stderrproperty先調(diào)用wait()再返回進(jìn)程寫(xiě)出的 stdout / stderr 內(nèi)容src/sh/init.py。這正是后臺(tái)命令訪問(wèn)輸出時(shí)能自動(dòng)等待完成的實(shí)現(xiàn)。exit_codeproperty先wait()再返回進(jìn)程退出碼src/sh/init.py。2.4 進(jìn)程標(biāo)識(shí)屬性pid、sid、pgid、cttypid進(jìn)程 ID。sid會(huì)話 ID。通常與當(dāng)前 Python 進(jìn)程的會(huì)話不同除非指定了_new_sessionFalse。pgid進(jìn)程組 ID。ctty控制終端設(shè)備如果存在。這些屬性通過(guò)RunningCommand._OProc_attr_allowlist白名單直接透?jìng)鹘o底層OProcsrc/sh/init.py。2.5 信號(hào)與生命周期方法方法說(shuō)明signal(sig_num)向進(jìn)程發(fā)送信號(hào)sig_num通常配合signal模塊使用如signal.SIGHUP參見(jiàn)signal(7)signal_group(sig_num)向進(jìn)程組內(nèi)的每一個(gè)進(jìn)程發(fā)送信號(hào)terminate()快捷方式等價(jià)于signal(signal.SIGTERM)kill()快捷方式等價(jià)于signal(signal.SIGKILL)kill_group()快捷方式等價(jià)于signal_group(signal.SIGKILL)is_alive()返回進(jìn)程是否仍然存活類型為bool源碼中這些方法最終都落在OProc的同名方法上src/sh/init.py 的屬性透?jìng)?OProc的方法實(shí)現(xiàn)而is_alive()是通過(guò)輪詢os.waitpid的活體檢測(cè)實(shí)現(xiàn)的src/sh/init.py。2.6 作為上下文管理器與可迭代對(duì)象RunningCommand還實(shí)現(xiàn)了上下文管理器with sh.cd(/tmp): ...這類場(chǎng)景中命令對(duì)象會(huì)被壓入前置棧退出時(shí)彈出__enter__/__exit__src/sh/init.py??傻敵鰂or line in p:可逐行迭代進(jìn)程輸出__iter__/__next__按_iter配置從管道隊(duì)列讀取塊并解碼。異步支持await p等待完成async for chunk in p以非阻塞方式逐塊消費(fèi)輸出__await__/__aiter__src/sh/init.py。3. OProc 類底層進(jìn)程包裝器??警告不要直接使用該類的實(shí)例。在此記錄它只是為了傳之后世for posterity而非供直接調(diào)用。OProc是 sh 對(duì)被 exec 的底層進(jìn)程的低級(jí)包裝RunningCommand正是圍繞它構(gòu)建的src/sh/init.py。它負(fù)責(zé)真正的 fork/exec、輸入輸出線程、信號(hào)與超時(shí)處理等臟活。3.1 OProc 的成員wait()阻塞直到進(jìn)程完成聚合輸出并填充OProc.exit_codesrc/sh/init.py 對(duì)應(yīng)實(shí)現(xiàn)區(qū)。stdout一個(gè)collections.deque大小由_internal_bufsize決定保存進(jìn)程的 STDOUT。stderr同樣是一個(gè)collections.deque大小由_internal_bufsize決定保存進(jìn)程的 STDERR。exit_code進(jìn)程退出碼進(jìn)程尚未退出時(shí)為None。pid/sid/pgid/ctty與RunningCommand同名屬性的含義一致sid同樣默認(rèn)是新的會(huì)話除非_new_sessionFalse。signal(sig_num)/signal_group(sig_num)/terminate()/kill()/kill_group()與RunningCommand的對(duì)應(yīng)方法行為相同terminate是signal(SIGTERM)的快捷方式kill是signal(SIGKILL)的快捷方式kill_group是signal_group(SIGKILL)的快捷方式。3.2_internal_bufsize內(nèi)部緩沖區(qū)大小_internal_bufsize決定OProc.stdout/OProc.stderr這兩個(gè) deque 的大小。注意它不是字節(jié)數(shù)而是塊chunk數(shù)——例如你在 1024 字節(jié)的塊粒度下緩沖輸出內(nèi)部緩沖區(qū)就是internal_bufsize個(gè) 1024 字節(jié)的塊。因?yàn)榈讓邮?deque一旦溢出最早的數(shù)據(jù)會(huì)被擠出隊(duì)列見(jiàn) src/sh/init.py 的源碼注釋。默認(rèn)值是3 * 1024**2約 314 萬(wàn)塊可通過(guò)_internal_bufsize特殊參數(shù)調(diào)整。4. 異常體系sh 的異常設(shè)計(jì)讓命令失敗成為一等公民你可以精確捕獲每一種失敗。4.1ErrorReturnCode錯(cuò)誤返回碼的基類作為所有錯(cuò)誤返回碼異常的基類它繼承自Exception。它的特殊之處在于sh 會(huì)按退出碼動(dòng)態(tài)生成子類命名格式為ErrorReturnCode_NNNNNN為退出碼數(shù)字例如ErrorReturnCode_2。源碼中由get_rc_exc()通過(guò)元類動(dòng)態(tài)創(chuàng)建并緩存src/sh/init.py這樣你可以寫(xiě)try: sh.ls(/doesnt/exist) except sh.ErrorReturnCode_2: print(directory doesnt exist)實(shí)例屬性full_cmd實(shí)際執(zhí)行的完整命令行字符串形式方便你直接拿到命令行去復(fù)現(xiàn)。stdout進(jìn)程聚合后的完整 STDOUT。stderr進(jìn)程聚合后的完整 STDERR。exit_code進(jìn)程調(diào)整后的退出碼參見(jiàn)架構(gòu)文檔中的 exit code 約定。另外異常的字符串表示__init__中的消息構(gòu)造src/sh/init.py會(huì)帶上RAN:執(zhí)行的命令、STDOUT:、STDERR:三段信息且輸出默認(rèn)截?cái)嗟絫runcate_cap 750字符可用_truncate_excFalse關(guān)閉截?cái)唷?.2SignalException被信號(hào)終止繼承自ErrorReturnCode當(dāng)命令收到信號(hào)而退出時(shí)拋出。動(dòng)態(tài)子類命名格式為SignalException_SIGxxx如SignalException_SIGHUP見(jiàn) src/sh/init.py。SIGNALS_THAT_SHOULD_THROW_EXCEPTION集合定義了哪些信號(hào)應(yīng)該被當(dāng)作異常拋出src/sh/init.py覆蓋SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGINT、SIGKILL、SIGPIPE、SIGQUIT、SIGSEGV、SIGTERM、SIGSYS。4.3TimeoutException超時(shí)命令指定了非空timeout即_timeout且超時(shí)時(shí)拋出import sh try: sh.sleep(10, _timeout1) except sh.TimeoutException: print(we timed out, as expected)對(duì)RunningCommand.wait(timeout...)指定超時(shí)也會(huì)拋出import sh p sh.sleep(10, _bgTrue) try: p.wait(timeout1) except sh.TimeoutException: print(we timed out waiting) p.kill()TimeoutException持有exit_code超時(shí)被殺時(shí)對(duì)應(yīng)信號(hào)的負(fù)退出碼取正與full_cmdsrc/sh/init.py。默認(rèn)超時(shí)信號(hào)是SIGKILL可通過(guò)_timeout_signal修改。4.4CommandNotFound找不到命令在以下任一條件下拋出程序在$PATH中找不到你沒(méi)有執(zhí)行該程序的權(quán)限程序沒(méi)有被標(biāo)記為可執(zhí)行。后兩條乍看有些奇怪但這與 Bash 查找待執(zhí)行程序時(shí)的行為一致——sh 的is_exe()確實(shí)同時(shí)檢查存在性 X_OK可執(zhí)行位 是文件src/sh/init.py。注意CommandNotFound繼承自AttributeError因此它的repr就是缺失屬性的名字本身。這背后有歷史原因——它需要在 IPython 自動(dòng)補(bǔ)全等場(chǎng)景下表現(xiàn)為屬性缺失源碼注釋引用了 ipython/ipython#2577。測(cè)試 tests/sh_test.py 驗(yàn)證了導(dǎo)入不存在的程序名與構(gòu)造不存在的命令都會(huì)拋出它。5. 輔助函數(shù)5.1which(name, search_pathsNone)把name解析為程序的絕對(duì)路徑找不到時(shí)返回None。若search_paths是路徑列表則用該列表查找否則使用環(huán)境變量$PATH。import sh path sh.which(ifconfig) # 例如 /sbin/ifconfig path sh.which(foo, [/opt/bin, /usr/bin]) # 只在給定列表里找在 sh 的模塊命名空間里它對(duì)應(yīng)_SelfWrapper提供的b_which內(nèi)建實(shí)現(xiàn)直接委托給_which()src/sh/init.py與Command構(gòu)造時(shí)的查找邏輯完全一致。測(cè)試 tests/sh_test.py 驗(yàn)證了不存在的程序返回None指定search_paths后能找到$PATH之外的可執(zhí)行文件。5.2pushd(directory)提供一個(gè)類似 Bashpushd的with上下文進(jìn)入指定目錄并在上下文結(jié)束時(shí)退出。與_cwd參數(shù)不同pushd會(huì)真正改變進(jìn)程的工作目錄因此對(duì)sh.glob等依賴真實(shí) cwd 的內(nèi)建功能同樣有效import sh with sh.pushd(/tmp): sh.touch(a_file)它內(nèi)部使用os.chdir()保存并在finally中恢復(fù)原目錄src/sh/init.py。線程安全它被包裝在可重入鎖PUSHD_LOCK threading.RLock()src/sh/init.py 與with_lock裝飾器中不同線程在各自的with上下文內(nèi)會(huì)得到正確的目錄行為——這點(diǎn)由測(cè)試test_pushd_thread_safetytests/sh_test.py專門(mén)驗(yàn)證。6. 實(shí)戰(zhàn)組合一個(gè)完整示例把上述知識(shí)串起來(lái)下面的腳本演示了Command構(gòu)造、烘焙、后臺(tái)運(yùn)行、等待、超時(shí)與異常捕獲的完整用法import sh # 1) 手動(dòng)構(gòu)建 調(diào)用時(shí)傳參正確姿勢(shì) ls sh.Command(/bin/ls) print(ls(-l, /tmp, colornever)) # 2) 動(dòng)態(tài)查找 烘焙默認(rèn)參數(shù) long_ls sh.ls.bake(-l) # 等價(jià)于 sh.ls(-l) print(long_ls(/var)) # 3) 后臺(tái)運(yùn)行 手動(dòng) wait 超時(shí)保護(hù) p sh.sleep(10, _bgTrue) try: p.wait(timeout1) except sh.TimeoutException: print(timed out, killing) p.kill() # 4) 精確捕獲失敗退出碼 try: sh.ls(/doesnt/exist) except sh.ErrorReturnCode_2: print(directory doesnt exist) # 5) 利用 RunningCommand 的進(jìn)程標(biāo)識(shí)與信號(hào)能力 proc sh.sleep(30, _return_cmdTrue) print(proc.pid, proc.sid, proc.pgid) proc.terminate() # SIGTERM print(alive?, proc.is_alive())7. 更多參考參數(shù)傳遞與特殊關(guān)鍵字參數(shù)見(jiàn) passing_arguments 章節(jié)、special_arguments 章節(jié)烘焙細(xì)節(jié)見(jiàn) baking 章節(jié)管道與重定向wait()在 stdin 場(chǎng)景的傳播行為見(jiàn) piping 章節(jié)、redirection 章節(jié)異步與后臺(tái)執(zhí)行見(jiàn) asynchronous_execution 章節(jié)、tutorials/interacting_with_processes退出碼的架構(gòu)約定exit_code的調(diào)整后語(yǔ)義見(jiàn) architecture 章節(jié)完整 API 參考見(jiàn) reference 頁(yè)面贊分享開(kāi)發(fā)工具【免費(fèi)下載鏈接】shPython process launching項(xiàng)目地址https://gitcode.com/gh_mirrors/sh/sh點(diǎn)擊查看免費(fèi)下載相關(guān)推薦PX4-Autopilot 命令行工具完全指南Modules Reference: Command 深度解析PX4 Autopilot 命令行工具完全指南Modules Reference: Command 深度解析 PX4 飛控系統(tǒng)在控制臺(tái)NSH Shell中嵌入式物聯(lián)網(wǎng)機(jī)器人自動(dòng)駕駛智能硬件Terminal.Gui 輸入體系完全指南Keyboard、Mouse 與 Command 命令框架Terminal.Gui 輸入體系完全指南Keyboard、Mouse 與 Command 命令框架 Terminal.Gui.Input 是跨平臺(tái)終端 UIUI組件跨平臺(tái)桌面應(yīng)用AutoGPT Forge 命令系統(tǒng)深度解析Command 對(duì)象、command 裝飾器與 CommandProvider 協(xié)議AutoGPT Forge 命令系統(tǒng)深度解析Command 對(duì)象、command 裝飾器與 CommandProvider 協(xié)議 在 AutoGPT 的 Fo人工智能AI Agent自主智能體Agent 工作流工作流自動(dòng)化后端前端上一篇Ant Design Checkbox 組件基礎(chǔ)使用指南從入門(mén)示例到 Group 全選與受控實(shí)戰(zhàn)下一篇終極窗口大小調(diào)整工具3分鐘強(qiáng)制縮放那些拖不動(dòng)的頑固窗口創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考