跳转至

Shell 内嵌 Python 片段 import 自检(scan-py-snippets.py)

位置:deploy/nfos/tools/scan-py-snippets.py | 语言:python3 | 是否需要 root:否

工具性质:离线静态自检(只读源码,不写任何文件) 运行前提:仓库检出在场即可;本工具不自动定位仓库根,路径按当前工作目录解析 部署状态:随仓库检出运行,不部署到 /opt/Cy/services/system.d/nfos/


用途

deploy/nfos/security-guard.sh 里有十几处 python3 -c '...' 与 python3 - <<'PY' 形式的内嵌 Python 片段。片段里一旦用了 os.environ 却忘了 import os,运行期就是 NameError:

  • 守卫会静默走 WARN 分支(“推送参数解析失败”)→ 告警推不到人;
  • bash -n 完全看不出来(那是 shell 语法检查,不看片段内部)。

本工具把 shell 脚本里的 Python 片段抽取出来,用 ast 解析,找出「被读取(ast.Load)但既不是内建、也没在片段里赋值/导入/定义」的名字,逐个片段打印结果:

输出行 含义
ok L<n>(python3 -c) / ok L<n>(heredoc) 片段内无缺失 import
!! L<n>(...) 缺 import:os(L42) 该片段缺 stdlib import(计入结论)
?? L<n>(...) 无法解析:SyntaxError: ... 片段解析失败(不计入结论,也不改变退出码)

末尾固定打印一行结论:结论:<N> 个片段缺 stdlib import(文件 <path>)。

识别范围的重要边界:只有当缺失的名字落在源码内 STDLIB 白名单(23 个常用标准库名,如 os/sys/json/re/time/socket/subprocess/hashlib/ipaddress 等)时才会被报出;第三方库名不在白名单,不会被检出。

典型场景

  • 改完 security-guard.sh(新增/修改 python3 -c 或 heredoc 片段)后,在提交前跑一遍,确认没有漏 import;
  • 巡检脚本“改了却没有任何告警推送”时,作为 NameError 类静默失败的第一道排查手段;
  • 批量迁移守卫脚本里内嵌片段时,作为机械化的回归检查。

用法与参数

源码 docstring 给出的用法(逐字引用,其中 .scratch/ 是升级前的旧路径):

用法:python .scratch/scan-py-snippets.py deploy/nfos/security-guard.sh

本页统一按新位置书写为 python deploy/nfos/tools/scan-py-snippets.py <脚本>。位置参数只有一个:

参数 类型 默认值 说明
sys.argv[1] 字符串(文件路径) deploy/nfos/security-guard.sh 待扫描的 shell 脚本;相对当前工作目录解析(不是相对仓库根)

无参数时的默认行为:扫描 deploy/nfos/security-guard.sh(相对当前工作目录)。因此在仓库根目录执行时可无参数运行;在别处执行必须显式传路径。

示例

在仓库根目录执行(自检类工具的推荐用法——从任何位置都能运行,但仓库检出必须在场):

python deploy/nfos/tools/scan-py-snippets.py deploy/nfos/security-guard.sh

期望输出形态:

  ok L120(python3 -c)
  ok L204(heredoc)
  ...
结论:0 个片段缺 stdlib import(文件 deploy/nfos/security-guard.sh)
情形 期望退出码
全部片段通过 0
至少 1 个片段缺 stdlib import 1
目标文件不存在 / 不可读(异常未捕获) 1(Python traceback,非自检语义)

依赖与权限

  • 语言/版本:Python 3.7+(源码含 from __future__ import annotations)。
  • 第三方依赖:无,仅标准库 ast / builtins / re / sys。
  • 仓库依赖:无(不给路径时不读仓库任何固定文件;给相对路径则依赖当前工作目录)。
  • 权限:不需要 root,不需要写权限(纯只读)。

风险与注意事项

  • 纯离线只读:只 open(<path>) 读取目标脚本,不写文件、不联网、不执行被扫描的片段(只用 ast.parse 解析,不 eval/exec)。
  • 不是完备的 lint:只覆盖 STDLIB 白名单里的名字;json、os 之外的自定义名或第三方名缺失不会报。
  • ??(无法解析)的片段被跳过,不计入 bad,因此片段被误抽取(例如引号嵌套导致截断)可能表现为“全 ok”的假通过——出现 ?? 行时要人工看一眼。
  • 抽取逻辑是行级启发式(匹配 python3 + -c ' 或 python3 + - <<'TAG'),非常规写法(如 python -c、变量拼接、<<- 缩进 heredoc)不会被抽取。

回滚

本工具不部署到线上、不改线上系统。它不产生任何产物:

  • 无输出文件、无临时目录、无状态文件;
  • 不改动被扫描的 shell 脚本(只读打开);
  • 无需清理。若只是跑过本工具,git/文件系统的比对结果应当为空。