TT Lab
开始
学习 学习路径 课程

失败了,退出码却是 0

让退出码说真话

在 TT Lab 中继续学习

目标

把 shell 里一行的检查做成“运维工具”——退出码 0、1、2 的约定,标准输出与标准错误的分离,日志级别,JSON 输出,可导入的 main()。

为什么重要

cron 和 CI 不读屏幕,只看退出码。如果诊断信息混入标准输出,流水线的下一个步骤就会出错。traceback 对创建者来说是信息,对凌晨收到告警的人来说却是噪音。本实验的工具虽小,但在这里学到的形态,会原样成为几百行运维工具的骨架。只使用标准库(argparse、logging、json、pathlib)。

步骤

  1. 创建 /root/pyops/cli/dircheck.py。用 argparse 定义位置参数 path,以及选项 --min-files(int,默认 1)、--max-age-hours(float,默认 24)、--json、-v/--verbose。python3 dircheck.py --help 必须以退出码 0 结束。
  2. 测量目录中的文件数和最旧文件的年龄(小时),向标准输出输出一行 files=<n> oldest_age_hours=<x> ok=<true|false>(占位符依次为文件数、年龄、是否通过)。条件是文件数 ≥ --min-files,并且最旧文件的年龄 ≤ --max-age-hours。
  3. 满足条件时以退出码 0 结束,违反时以 1 结束。空目录是 files=0,在 --min-files 1 的情况下就属于违反。
  4. 如果路径不存在或不是目录,不要输出 traceback,而是往标准错误写一行,并以退出码 2 结束。这时不要往标准输出写任何内容。
  5. 加上 logging。默认为 INFO,有 -v 时为 DEBUG。用 -v 运行时,包含 DEBUG 的行只应出现在标准错误,标准输出只应留下一行结果。
  6. 有 --json 时,只向标准输出输出一个 JSON 对象。键是 path、files、oldest_age_hours、ok,ok 是布尔值,files 是整数。退出码约定保持不变。
  7. 接收多个路径(nargs="+")。对每个目录输出一行结果(有 JSON 时输出一个数组),退出码以最坏的那个(2 > 1 > 0)结束。
  8. 整理成 main(argv=None) -> int 函数,只在文件最底部调用 sys.exit(main())。仅 import dircheck 不应该执行任何东西,并且 dircheck.main(["<디렉터리>"])(占位符为目录)必须返回整数。

参考

参数定义与 --help

创建 /root/pyops/cli/dircheck.py,用 argparse 定义 path、--min-files、--max-age-hours、--json、-v。--help 以退出码 0 结束。

对 argparse.ArgumentParser() 调用五次 add_argument 就行。--help 由 argparse 自动生成,并以退出码 0 结束。请区分 type=int、type=float、action="store_true"。

测量文件数和最旧的年龄

测量目录中的文件数和最旧文件的年龄(小时),向标准输出输出一行 files=<n> oldest_age_hours=<x> ok=<true|false>(占位符依次为文件数、年龄、是否通过)。

对 Path(path).iterdir() 只统计 is_file() 的项,年龄是 (time.time() - p.stat().st_mtime) / 3600。空目录的 max() 请给出 default=0.0。

0 和 1 的约定

满足条件时以退出码 0 结束,违反(文件不足或太旧)时以 1 结束。

让 main() 返回整数,再用 sys.exit(main()) 传出去。ok 为 False 就是 1。空目录在 --min-files 1 的情况下就属于违反。

工具错误是 2,而且不输出 traceback

如果路径不存在或不是目录,就往标准错误写一行,并以退出码 2 结束。标准输出必须为空,也不能出现 traceback。

先用 Path.is_dir() 过滤,再捕获 stat() 抛出的 OSError,把它变成一行信息。信息用 print(..., file=sys.stderr) 或 logging 输出。

诊断走标准错误,用 -v 调节级别

用 logging.basicConfig 设置默认 INFO,有 -v 时为 DEBUG。用 -v 运行时,DEBUG 行只出现在标准错误,标准输出只留下一行结果。

basicConfig 的默认目的地是 sys.stderr。在 format 中加入 %(levelname)s,就会打印出 DEBUG 这个词。请在检查开始的位置放一个 log.debug()。

供机器读取的结果用 JSON

有 --json 时,只向标准输出输出一个 JSON 对象。键是 path、files、oldest_age_hours、ok,ok 是布尔值,files 是整数。保持退出码约定。

把 json.dumps(dict) 用 print 输出。如果同时输出给人看的一行和 JSON,解析就会出错——只能二选一。

多个目录,取最坏的退出码

用 nargs="+" 接收多个 path。每个目录输出一行(有 JSON 时输出数组),退出码以最坏的那个(2 > 1 > 0)结束。

对每个目录更新 worst = max(worst, code),并且不要因为一个不存在的路径就跳过其余的检查(continue)。

即使被导入也不会执行的工具

整理成 main(argv=None) -> int,只在文件最底部的 if __name__ == "__main__": 之内调用 sys.exit(main())。import dircheck 不执行任何东西,并且 dircheck.main(["<目录>"]) 返回整数。

把 argv 传给 parse_args(argv),它就不会读取 sys.argv。如果模块最顶层还留着 parse_args() 或 sys.exit(),导入的那一刻就会被执行。