开发工具【免费下载链接】shPython process launching项目地址https://gitcode.com/gh_mirrors/sh/sh点击查看免费下载本指南以 docs/source/sections/migration.rst 为骨架系统梳理 sh 1.x 升级到 2.x 的四大破坏性变更sh.cd移除、执行上下文execution contexts语法重构、返回值类型变化、进程会话默认值调整。读者将掌握每项变更的精确替换写法包括bake迁移、_return_cmd、_in、_new_session的用法并理解这些变更背后的设计动机与源码实现逻辑可直接据此完成存量代码的平滑迁移。迁移前的背景为什么会发生这些破坏性变更sh 是一个通过 Python 语法直接调用系统命令的进程启动库1.x 时代长期提供了一些魔法特性如可导入命令的定制模块但它们实现脆弱、语法与后续引入的 baking烘烤概念不一致。2.x 版本统一了这些语义代价是少量 API 不兼容。本文对应官方文档的迁移章节逐条说明被移除/变更了什么、为什么变更、如何替换。源码层面这些变更集中在 src/sh/init.py 的Command.bake约第 1367 行、pushd约第 3384 行、特殊参数表_call_args约第 1171 行等实现中下文会结合具体代码展开。sh.cd内建命令被移除变更内容2.x 中不再存在sh.cd命令。在 1.x 中cd是一个由 sh 内部实现的命令sh.cd原因是不同系统对cd的提供方式不一致有些系统把它作为 shell 内建有些则提供真实二进制文件。但无论哪种来源它们都无法在多次sh调用之间持久化目录变更——这正是 sh 自行实现它的原因。替换方案改用with sh.pushd(dir)如果你原来写的是sh.cd(dir)请改用上下文管理器with sh.pushd(dir)import sh with sh.pushd(/path/to/dir): # 该 with 块内的所有 sh 命令都会在正确的目录下执行 sh.ls(-l)所有位于受管上下文managed context中的命令都会获得正确的目录。从源码看pushd 实现 在进入上下文时保存原始目录并执行os.chdir(path)退出时通过finally恢复原目录with_lock(PUSHD_LOCK) def pushd(path): pushd changes the actual working directory for the duration of the context, unlike the _cwd arg this will work with other built-ins such as sh.glob correctly orig_path os.getcwd() os.chdir(path) try: yield finally: os.chdir(orig_path)需要注意两点有源码佐证与_cwd的区别pushd 真正改变的是 Python 进程的当前工作目录因此它对sh.glob等同样依赖进程工作目录的库内建功能也生效而命令级的_cwd特殊参数只在单条命令执行时生效参见 tests/sh_test.py 中test_pushd对进入后目录正确、退出后目录还原的断言。线程安全pushd 使用了模块级的可重入锁PUSHD_LOCKsrc/sh/init.py多个线程同时使用 pushd 时都能在 with 上下文期间看到一致的当前工作目录测试用例test_pushd_thread_safetytests/sh_test.py专门验证了这一行为。执行上下文 / 默认参数机制被移除变更内容在 1.x 中你可以从sh模块孵化出一个新的模块该模块带有定制化的特殊关键字参数special keyword arguments默认值。这个新模块可以像sh一样被访问甚至可以直接从它导入命令sh2 sh(_tty_outFalse) sh2.ls() sh2 sh(_tty_outFalse) from sh2 import ls ls()不幸的是支撑这种能力所需的魔法magic很脆弱而且其语法与类似的 baking 概念不一致。因此 2.x 做了两项改变语法统一为 bakingsh(...)孵化模块的写法废弃改为sh.bake(...)移除直接导入能力不能再from sh2 import ls因为从烘焙baked执行上下文导入命令已不再被支持。替换方案情况一只需要调用不需要导入# 1.x 写法 sh2 sh(_tty_outFalse) sh2.ls() # 2.x 写法 sh2 sh.bake(_tty_outFalse) sh2.ls()情况二原来从新模块导入命令# 1.x 写法 sh2 sh.bake(_tty_outFalse) from sh2 import ls ls() # 2.x 写法 sh2 sh.bake(_tty_outFalse) ls sh2.ls ls()bake方法在 Command.bake 中实现它返回一个带有_partial标记的新Command实例将本次传入的普通参数与特殊参数分别冻结进_partial_baked_args参数层和_partial_call_args特殊参数默认值后续每次调用时这些冻结值会自动参与参数编译见_compile_baked_argssrc/sh/init.py且调用时传入的同名参数可以覆盖烘焙值。这意味着sh2 sh.bake(_tty_outFalse)之后所有从sh2派生的命令都默认不向终端输出直到你在单次调用时用_tty_outTrue显式覆盖。补充一个与此相关的实用细节来自 CHANGELOG.md2.2.5 起布尔参数支持在后续 bake 层中覆盖先前的烘焙值见 CHANGELOG.md 的 Allow boolean arguments to override baked arguments对应测试test_bake_boolean_overridetests/sh_test.py。返回值从 RunningCommand 变为真正的字符串变更内容在 2.x 中执行 sh 命令的返回值大多数情况下从RunningCommand对象变为 unicode 字符串这让直接使用命令输出更加自然# 2.x直接得到字符串 out sh.ls(-l) print(out) # 输出 ls -l 的结果文本替换方案用_return_cmdTrue恢复旧行为如果你确实需要继续得到RunningCommand对象请使用_return_cmdTrue特殊关键字参数。最简单的做法是在每个使用 sh 的文件顶部统一设置import sh sh sh.bake(_return_cmdTrue)之后该文件中所有命令默认返回RunningCommand。源码侧的实现依据return_cmd是Command._call_args特殊参数表的一项src/sh/init.py注释明确写着 return an instance of RunningCommand always. if this isnt True, then sometimes we may return just a plain unicode string默认值为False。实际分支逻辑位于命令执行返回处src/sh/init.pyrc self.__class__.RunningCommandCls(cmd, call_args, stdin, stdout, stderr) if rc._spawned_and_waited and not call_args[return_cmd]: return str(rc) else: return rc即命令已同步执行完毕且未设置return_cmd时返回str(rc)真正的 unicode 字符串否则返回RunningCommand。这也解释了文档中在大多数情况下的措辞——当你使用迭代、管道等需要对象语义的场景时返回值仍可能是RunningCommand。管道到 STDIN 不再自动发生变更内容在 1.x 中如果 sh 命令的第一个参数是RunningCommand实例它会自动被送入进程的 STDIN。2.x 起这一自动行为被移除必须显式使用_in指定输入。替换方案直接替换为_in推荐# 1.x 写法 from sh import wc, ls print(wc(ls(/home/user, -l), -l)) # 2.x 写法 from sh import wc, ls print(wc(-l, _inls(/home/user, -l)))需要 RunningCommand 语义时配合_return_cmdfrom sh import wc, ls print(wc(-l, _inls(/home/user, -l, _return_cmdTrue)))官方文档对这项变更给出的 Workaround 是 None.——即没有捷径必须显式写出_in。这并非疏漏而是刻意消除隐式魔法1.x 依赖首个参数是 RunningCommand 就自动作为 STDIN的隐式推断容易在混合位置参数时产生歧义2.x 要求数据流向一目了然。_in参数在特殊参数表中对应in: Nonesrc/sh/init.py它接受文件对象、文件描述符、字节串等作为进程标准输入源。新进程默认不再启动新会话变更内容1.x 中_new_session默认值为True2.x 改为False。变更动机是让启动的进程默认处于 Python 脚本所在的进程组中更合理这样它们能够正确接收 SIGINT例如按下 CtrlC 时前台进程组的所有成员都会被信号打断并正确退出。替换方案用_new_sessionTrue保留旧行为import sh sh sh.bake(_new_sessionTrue)与_return_cmd一样用bake一次性设置默认值即可让整个文件中所有命令保持 1.x 的会话行为。源码侧的实现依据new_session在特殊参数表中默认值为Falsesrc/sh/init.py。在 fork 之后的子进程分支中src/sh/init.py当new_session为真时调用os.setsid()使子进程成为新会话的领导者否则仅在new_group为真时调用os.setpgid(0, 0)建立新进程组。另外有一个容易忽略的关联行为当使用 TTY 作为标准输入tty_in而需要控制终端controlling terminal时代码会强制new_session Truesrc/sh/init.py因为只有会话领导者才能获取控制终端——这是官方文档未明说、但迁移时可能遇到的隐含行为。测试用例test_new_session_new_grouptests/sh_test.py验证了 pid/pgid/sid 三者的对应关系可作为理解该行为的参考。迁移清单与验证建议将上述变更整理为一份可直接对照执行的清单1.x 用法2.x 用法sh.cd(dir)with sh.pushd(dir):上下文内所有命令在目标目录执行sh2 sh(_tty_outFalse)sh2 sh.bake(_tty_outFalse)from sh2 import lsls sh2.ls依赖RunningCommand返回值import sh; sh sh.bake(_return_cmdTrue)隐式管道wc(ls(...), -l)显式wc(-l, _inls(...))依赖默认新会话import sh; sh sh.bake(_new_sessionTrue)迁移后建议立即验证的要点目录相关代码检查所有sh.cd调用点是否已全部替换为with sh.pushd(...)并确认上下文内命令输出的路径确实位于目标目录可参照 test_pushd 的断言方式自查。返回值类型如果你的代码对命令输出调用过RunningCommand专属方法如.exit_code、.pid、迭代器等迁移后必须加上sh sh.bake(_return_cmdTrue)否则返回值已变为字符串。管道数据流搜索形如cmd1(cmd2(...), ...)且cmd2原本作为首参的调用全部改为cmd1(args..., _incmd2(...))。信号行为如果原程序依赖子进程独立会话例如希望脱离终端存活请保留sh sh.bake(_new_sessionTrue)否则保持默认False让子进程与 Python 脚本同组接收 SIGINT。完成以上替换后结合当前仓库的 tests/sh_test.py共 3887 行、覆盖 bake/pushd/返回值/会话等场景运行你的测试套件即可确认迁移无回归。赞分享开发工具【免费下载链接】shPython process launching项目地址https://gitcode.com/gh_mirrors/sh/sh点击查看免费下载相关推荐Vux 2.0 升级指南从 Vue 1.x 迁移到 Vue 2.x 的完整实操手册Vux 2.0 升级指南从 Vue 1.x 迁移到 Vue 2.x 的完整实操手册 本指南以 Vux 官方《upgrade to 2.md》为核心系统梳理从UI组件前端ant-design-vue 3.x 升级迁移指南从 2.x 平滑升级的完整实操手册ant design vue 3.x 升级迁移指南从 2.x 平滑升级的完整实操手册 本篇指南以官方迁移文档《migration v3.zh CN.md》为骨前端UI组件设计系统IGListKit 迁移指南从 1.x 升级到 3.x 的完整实战手册IGListKit 迁移指南从 1.x 升级到 3.x 的完整实战手册 本篇指南基于 IGListKit 官方迁移文档 Guides/Migration.m移动开发UI组件上一篇Legacy iOS Kit终极指南如何让旧款iPhone/iPad焕发新生下一篇RabbitMQ 3.9.11 维护版本全解析核心服务器修复与 Prometheus 集群级指标增强创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考