逍遥云初 | 2026.04.04

10 个 Python CLI 小工具,覆盖日常开发高频场景。每个工具都是单文件、零依赖、即拿即用。复制下方源码保存为 .py 文件即可使用。


1. JSON 工具箱 (json_fmt.py)

JSON 格式化、压缩、验证、转义、key 路径展示、双文件 diff

使用方法

echo '{"a":1}' | python3 json_fmt.py          # 格式化
echo '{"a":1}' | python3 json_fmt.py --compact # 压缩
python3 json_fmt.py --diff old.json new.json   # JSON diff
echo '{"a":1}' | python3 json_fmt.py --keys     # 展示 key 路径

完整源码 (复制保存为 json_fmt.py 即可使用)

#!/usr/bin/env python3
"""
JSON 工具箱 - 格式化、压缩、验证、转义
用法:
  echo '{"a":1}' | python3 json_fmt.py              # 格式化
  echo '{"a":1}' | python3 json_fmt.py --compact     # 压缩
  echo '{"a":1}' | python3 json_fmt.py --validate    # 仅验证
  echo '{"a":1}' | python3 json_fmt.py --escape      # 转义为字符串
  echo '{"a":1}' | python3 json_fmt.py --unescape    # 反转义
  python3 json_fmt.py --file data.json               # 从文件读取
  python3 json_fmt.py --keys '{"a":{"b":1}}'         # 展示 key 路径
  python3 json_fmt.py --diff old.json new.json        # 对比两个 JSON 差异
"""

import json
import sys
import argparse
from pathlib import Path


def format_json(data: str, indent: int = 2) -> str:
    """格式化 JSON"""
    obj = json.loads(data)
    return json.dumps(obj, ensure_ascii=False, indent=indent)


def compact_json(data: str) -> str:
    """压缩 JSON"""
    obj = json.loads(data)
    return json.dumps(obj, ensure_ascii=False, separators=(',', ':'))


def validate_json(data: str) -> str:
    """验证 JSON 并返回结构信息"""
    obj = json.loads(data)
    info = {
        "valid": True,
        "type": type(obj).__name__,
    }
    if isinstance(obj, dict):
        info["keys"] = len(obj)
        info["depth"] = _get_depth(obj)
    elif isinstance(obj, list):
        info["length"] = len(obj)
        info["depth"] = _get_depth(obj)
    return json.dumps(info, ensure_ascii=False, indent=2)


def escape_json(data: str) -> str:
    """将 JSON 转为转义字符串"""
    return json.dumps(data.strip())


def unescape_json(data: str) -> str:
    """将转义字符串还原"""
    obj = json.loads(data)
    if isinstance(obj, str):
        return json.dumps(json.loads(obj), ensure_ascii=False, indent=2)
    return json.dumps(obj, ensure_ascii=False, indent=2)


def show_keys(data: str, prefix: str = "$") -> str:
    """展示 JSON 的所有 key 路径"""
    obj = json.loads(data)
    paths = []
    _collect_paths(obj, prefix, paths)
    return "\n".join(paths)


def diff_json(file1: str, file2: str) -> str:
    """对比两个 JSON 文件的差异"""
    obj1 = json.loads(Path(file1).read_text(encoding='utf-8'))
    obj2 = json.loads(Path(file2).read_text(encoding='utf-8'))
    diffs = _diff_objects(obj1, obj2, "$")
    if not diffs:
        return "✅ 两个 JSON 完全相同"
    return "\n".join(diffs)


def _get_depth(obj, current=0):
    if isinstance(obj, dict):
        if not obj:
            return current
        return max(_get_depth(v, current + 1) for v in obj.values())
    elif isinstance(obj, list):
        if not obj:
            return current
        return max(_get_depth(v, current + 1) for v in obj)
    return current


def _collect_paths(obj, prefix, paths):
    if isinstance(obj, dict):
        for k, v in obj.items():
            path = f"{prefix}.{k}"
            paths.append(f"{path} ({type(v).__name__})")
            _collect_paths(v, path, paths)
    elif isinstance(obj, list) and obj:
        paths.append(f"{prefix}[*] ({len(obj)} items)")
        _collect_paths(obj[0], f"{prefix}[0]", paths)


def _diff_objects(obj1, obj2, path):
    diffs = []
    if type(obj1) != type(obj2):
        diffs.append(f"❌ {path}: 类型不同 ({type(obj1).__name__} vs {type(obj2).__name__})")
        return diffs

    if isinstance(obj1, dict):
        all_keys = set(list(obj1.keys()) + list(obj2.keys()))
        for k in sorted(all_keys):
            p = f"{path}.{k}"
            if k not in obj1:
                diffs.append(f"➕ {p}: 新增 = {json.dumps(obj2[k], ensure_ascii=False)}")
            elif k not in obj2:
                diffs.append(f"➖ {p}: 删除 = {json.dumps(obj1[k], ensure_ascii=False)}")
            else:
                diffs.extend(_diff_objects(obj1[k], obj2[k], p))
    elif isinstance(obj1, list):
        min_len = min(len(obj1), len(obj2))
        for i in range(min_len):
            diffs.extend(_diff_objects(obj1[i], obj2[i], f"{path}[
{i}]"))
        if len(obj1) > len(obj2):
            for i in range(min_len, len(obj1)):
                diffs.append(f"➖ {path}[{i}]: 删除 = {json.dumps(obj1[i], ensure_ascii=False)}")
        elif len(obj2) > len(obj1):
            for i in range(min_len, len(obj2)):
                diffs.append(f"➕ {path}[{i}]: 新增 = {json.dumps(obj2[i], ensure_ascii=False)}")
    else:
        if obj1 != obj2:
            diffs.append(f"✏️ {path}: {json.dumps(obj1, ensure_ascii=False)} → {json.dumps(obj2, ensure_ascii=False)}")
    return diffs


def main():
    parser = argparse.ArgumentParser(description="JSON 工具箱")
    parser.add_argument("--compact", "-c", action="store_true", help="压缩 JSON")
    parser.add_argument("--validate", "-v", action="store_true", help="验证 JSON")
    parser.add_argument("--escape", "-e", action="store_true", help="转义为字符串")
    parser.add_argument("--unescape", "-u", action="store_true", help="反转义")
    parser.add_argument("--keys", "-k", action="store_true", help="展示 key 路径")
    parser.add_argument("--diff", "-d", nargs=2, metavar=("FILE1", "FILE2"), help="对比两个 JSON")
    parser.add_argument("--file", "-f", help="从文件读取")
    parser.add_argument("--indent", "-i", type=int, default=2, help="缩进空格数")
    args = parser.parse_args()

    # diff 模式
    if args.diff:
        print(diff_json(args.diff[0], args.diff[1]))
        return

    # 读取输入
    if args.file:
        data = Path(args.file).read_text(encoding='utf-8')
    elif not sys.stdin.isatty():
        data = sys.stdin.read()
    else:
        parser.print_help()
        return

    data = data.strip()
    if not data:
        print("❌ 输入为空")
        sys.exit(1)

    try:
        if args.compact:
            print(compact_json(data))
        elif args.validate:
            print(validate_json(data))
        elif args.escape:
            print(escape_json(data))
        elif args.unescape:
            
print(unescape_json(data))
        elif args.keys:
            print(show_keys(data))
        else:
            print(format_json(data, args.indent))
    except json.JSONDecodeError as e:
        print(f"❌ JSON 格式错误: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()

2. 时间戳转换 (ts_conv.py)

Unix 时间戳与可读时间互转,多时区支持

使用方法

python3 ts_conv.py now                    # 当前时间(多时区)
python3 ts_conv.py 1709539200             # 时间戳→可读
python3 ts_conv.py "2026-04-04 08:30:00"  # 可读→时间戳
python3 ts_conv.py 1709539200 --tz jp      # 指定时区

完整源码 (复制保存为 ts_conv.py 即可使用)

#!/usr/bin/env python3
"""
时间戳转换工具 - Unix 时间戳 ↔ 可读时间,多时区支持
用法:
  python3 ts_conv.py 1709539200              # 时间戳 → 可读时间
  python3 ts_conv.py "2024-03-04 12:00:00"  # 可读时间 → 时间戳
  python3 ts_conv.py now                     # 当前时间
  python3 ts_conv.py now --tz US/Eastern     # 当前时间(美东)
  python3 ts_conv.py 1709539200 --tz Asia/Tokyo  # 指定时区
  python3 ts_conv.py --batch 1709539200 1709625600  # 批量转换
"""

import sys
import argparse
from datetime import datetime, timezone, timedelta

# 常用时区快捷方式
TZ_SHORTCUTS = {
    "cn": "Asia/Shanghai",
    "jp": "Asia/Tokyo",
    "us": "America/New_York",
    "uk": "Europe/London",
    "utc": "UTC",
    "pst": "America/Los_Angeles",
    "est": "America/New_York",
    "cet": "Europe/Berlin",
    "ist": "Asia/Kolkata",
    "aest": "Australia/Sydney",
}


def get_tz(tz_str: str):
    """获取时区对象"""
    tz_str = TZ_SHORTCUTS.get(tz_str.lower(), tz_str)
    try:
        from zoneinfo import ZoneInfo
        return ZoneInfo(tz_str)
    except Exception:
        pass
    # 手动偏移
    if tz_str.startswith("UTC+") or tz_str.startswith("UTC-"):
        hours = int(tz_str[3:])
        return timezone(timedelta(hours=hours))
    return timezone.utc


def ts_to_readable(timestamp: float, tz_str: str = "Asia/Shanghai") -> str:
    """时间戳 → 可读时间"""
    tz = get_tz(tz_str)
    dt = datetime.fromtimestamp(timestamp, tz=tz)
    ms = int((timestamp % 1) * 1000)
    return f"{dt.strftime('%Y-%m-%d %H:%M:%S')}.{ms:03d} {tz_str}"


def readable_to_ts(dt_str: str, tz_str: str = "Asia/Shanghai") -> float:
    """可读时间 → 时间戳"""
    tz = get_tz(tz_str)
    formats = [
        "%Y-%m-%d %H:%M:%S",
        "%Y-%m-%d %H:%M",
        "%Y-%m-%dT%H:%M:%S",
        "%Y-%m-%dT%H:%M:%S.%f",
        "%Y-%m-%d",
        "%Y/%m/%d %H:%M:%S",
        "%Y/%m/%d",
    ]
    for fmt in formats:
        try:
            dt = datetime.strptime(dt_str.strip(), fmt)
            dt = dt.
replace(tzinfo=tz)
            return dt.timestamp()
        except ValueError:
            continue
    raise ValueError(f"无法解析时间格式: {dt_str}")


def convert(input_str: str, tz_str: str = "Asia/Shanghai") -> str:
    """自动判断输入类型并转换"""
    input_str = input_str.strip()

    if input_str.lower() == "now":
        ts = datetime.now(timezone.utc).timestamp()
        result = []
        for tz_name in ["Asia/Shanghai", "UTC", "America/New_York", "Asia/Tokyo"]:
            result.append(f"  {tz_name}: {ts_to_readable(ts, tz_name)}")
        return f"⏰ 当前时间戳: {ts:.3f}\n" + "\n".join(result)

    # 尝试解析为时间戳
    try:
        ts = float(input_str)
        readable = ts_to_readable(ts, tz_str)
        # 额外信息
        dt = datetime.fromtimestamp(ts, tz=get_tz(tz_str))
        weekday = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][dt.weekday()]
        return f"📌 时间戳: {ts}\n📅 {readable} ({weekday})\n📏 毫秒: {int(ts * 1000)}\n📏 微秒: {int(ts * 1000000)}"
    except ValueError:
        pass

    # 尝试解析为可读时间
    try:
        ts = readable_to_ts(input_str, tz_str)
        readable = ts_to_readable(ts, tz_str)
        return f"📅 输入: {input_str} ({tz_str})\n📌 时间戳: {ts:.3f}\n📏 毫秒: {int(ts * 1000)}"
    except ValueError as e:
        return f"❌ {e}"


def main():
    parser = argparse.ArgumentParser(description="时间戳转换工具")
    parser.add_argument("input", nargs="?", help="时间戳、可读时间、或 'now'")
    parser.add_argument("--tz", "-t", default="Asia/Shanghai", help="时区 (cn/jp/us/uk/UTC 或 IANA 名)")
    parser.add_argument("--batch", "-b", nargs="+", help="批量转换时间戳")
    args = parser.parse_args()

    if args.batch:
        for ts_str in args.batch:
            print(convert(ts_str, args.tz))
            print()
    elif args.input:
        print(convert(args.input, args.tz))
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

3. 文本 Diff 比对 (text_diff.py)

两段文本差异高亮,支持并排对比、unified diff、JSON 输出

使用方法

python3 text_diff.py a.txt b.txt          # 并排对比
python3 text_diff.py a.txt b.txt --unified # git 风格
python3 text_diff.py a.txt b.txt --json    # JSON 统计
python3 text_diff.py a.txt b.txt --stats   # 仅统计

完整源码 (复制保存为 text_diff.py 即可使用)

#!/usr/bin/env python3
"""
文本 Diff 比对工具 - 两段文本差异高亮
用法:
  python3 text_diff.py file1.txt file2.txt          # 比对两个文件
  python3 text_diff.py --text "abc" --text2 "abd"   # 比对两段文本
  python3 text_diff.py file1.txt file2.txt --json    # JSON 格式输出
  python3 text_diff.py file1.txt file2.txt --unified  # 类 git diff 输出
"""

import sys
import json
import argparse
import difflib
from pathlib import Path


def diff_side_by_side(text1: str, text2: str) -> str:
    """并排高亮差异"""
    lines1 = text1.splitlines()
    lines2 = text2.splitlines()

    matcher = difflib.SequenceMatcher(None, lines1, lines2)
    result = []

    for op, i1, i2, j1, j2 in matcher.get_opcodes():
        if op == "equal":
            for i in range(i1, i2):
                result.append(f"  {lines1[i]}")
        elif op == "replace":
            for i in range(i1, i2):
                result.append(f"\033[91m- {lines1[i]}\033[0m")
            for j in range(j1, j2):
                result.append(f"\033[92m+ {lines2[j]}\033[0m")
        elif op == "delete":
            for i in range(i1, i2):
                result.append(f"\033[91m- {lines1[i]}\033[0m")
        elif op == "insert":
            for j in range(j1, j2):
                result.append(f"\033[92m+ {lines2[j]}\033[0m")

    return "\n".join(result)


def diff_unified(text1: str, text2: str, file1: str = "a", file2: str = "b") -> str:
    """Git 风格 unified diff"""
    lines1 = text1.splitlines(keepends=True)
    lines2 = text2.splitlines(keepends=True)
    return "".join(difflib.unified_diff(lines1, lines2, fromfile=file1, tofile=file2))


def diff_json(text1: str, text2: str) -> str:
    """JSON 格式输出差异详情"""
    lines1 = text1.splitlines()
    lines2 = text2.splitlines()
    matcher = difflib.SequenceMatcher(None, lines1, lines2)

    changes = []
    for op, i1, i2, j1, j2 in matcher.get_opcodes():
        if op == "equal":
            continue
    
    change = {
            "type": op,
            "old_lines": list(range(i1 + 1, i2 + 1)) if i1 < i2 else [],
            "new_lines": list(range(j1 + 1, j2 + 1)) if j1 < j2 else [],
        }
        if op in ("replace", "delete"):
            change["old_text"] = "\n".join(lines1[i1:i2])
        if op in ("replace", "insert"):
            change["new_text"] = "\n".join(lines2[j1:j2])
        changes.append(change)

    stats = {
        "old_lines": len(lines1),
        "new_lines": len(lines2),
        "additions": sum(1 for op, *_ in matcher.get_opcodes() if op in ("insert", "replace")),
        "deletions": sum(1 for op, *_ in matcher.get_opcodes() if op in ("delete", "replace")),
        "similarity": round(matcher.ratio() * 100, 1),
        "changes": changes,
    }
    return json.dumps(stats, ensure_ascii=False, indent=2)


def diff_stats(text1: str, text2: str) -> str:
    """统计差异概况"""
    lines1 = text1.splitlines()
    lines2 = text2.splitlines()
    matcher = difflib.SequenceMatcher(None, lines1, lines2)

    adds = dels = mods = 0
    for op, i1, i2, j1, j2 in matcher.get_opcodes():
        if op == "insert":
            adds += j2 - j1
        elif op == "delete":
            dels += i2 - i1
        elif op == "replace":
            mods += max(i2 - i1, j2 - j1)

    return (
        f"📊 文本比对统计\n"
        f"  原文: {len(lines1)} 行\n"
        f"  新文: {len(lines2)} 行\n"
        f"  相似度: {matcher.ratio() * 100:.1f}%\n"
        f"  新增: {adds} 行 | 删除: {dels} 行 | 修改: {mods} 行"
    )


def main():
    parser = argparse.ArgumentParser(description="文本 Diff 比对工具")
    parser.add_argument("file1", nargs="?", help="文件1")
    parser.add_argument("file2", nargs="?", help="文件2")
    parser.add_argument("--text", help="文本1")
    parser.add_argument("--text2", help="文本2")
    parser.add_argument("--json", "-j", action="store_true", help="JSON 格式输出")
    parser.add_argum
ent("--unified", "-u", action="store_true", help="Unified diff 输出")
    parser.add_argument("--stats", "-s", action="store_true", help="仅显示统计")
    args = parser.parse_args()

    # 读取文本
    if args.text and args.text2:
        text1, text2 = args.text, args.text2
        name1, name2 = "text1", "text2"
    elif args.file1 and args.file2:
        text1 = Path(args.file1).read_text(encoding='utf-8')
        text2 = Path(args.file2).read_text(encoding='utf-8')
        name1, name2 = args.file1, args.file2
    else:
        parser.print_help()
        return

    if args.json:
        print(diff_json(text1, text2))
    elif args.unified:
        print(diff_unified(text1, text2, name1, name2))
    elif args.stats:
        print(diff_stats(text1, text2))
    else:
        print(diff_stats(text1, text2))
        print()
        print(diff_side_by_side(text1, text2))


if __name__ == "__main__":
    main()

4. Base64 编解码 (b64.py)

文本/文件的 Base64 编解码,URL-safe,自动检测

使用方法

python3 b64.py -e "hello world"           # 编码
python3 b64.py -d "aGVsbG8gd29ybGQ="      # 解码
python3 b64.py --encode-file image.png     # 文件编码
python3 b64.py --url-safe -e "a+b"         # URL-safe

完整源码 (复制保存为 b64.py 即可使用)

#!/usr/bin/env python3
"""
Base64 编解码工具
用法:
  echo "hello" | python3 b64.py                  # 编码
  echo "aGVsbG8=" | python3 b64.py -d            # 解码
  python3 b64.py -e "hello world"                 # 直接编码
  python3 b64.py -d "aGVsbG8gd29ybGQ="           # 直接解码
  python3 b64.py --encode-file image.png          # 文件编码为 base64
  python3 b64.py --decode-file output.png < b64.txt  # base64 还原文件
  python3 b64.py --url-safe -e "hello+world/"     # URL-safe 编码
"""

import sys
import base64
import argparse


def encode(text: str, url_safe: bool = False) -> str:
    encoded = text.encode('utf-8')
    if url_safe:
        return base64.urlsafe_b64encode(encoded).decode('ascii')
    return base64.b64encode(encoded).decode('ascii')


def decode(data: str) -> str:
    data = data.strip()
    try:
        return base64.b64decode(data).decode('utf-8')
    except Exception:
        try:
            return base64.urlsafe_b64decode(data).decode('utf-8')
        except Exception as e:
            return f"❌ 解码失败: {e}"


def encode_file(filepath: str) -> str:
    with open(filepath, 'rb') as f:
        return base64.b64encode(f.read()).decode('ascii')


def decode_to_file(data: str, output: str) -> str:
    with open(output, 'wb') as f:
        f.write(base64.b64decode(data.strip()))
    return f"✅ 已写入 {output} ({len(base64.b64decode(data.strip()))} bytes)"


def main():
    parser = argparse.ArgumentParser(description="Base64 编解码工具")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("-e", "--encode", help="编码文本")
    group.add_argument("-d", "--decode", help="解码文本")
    group.add_argument("--encode-file", metavar="FILE", help="文件 → base64")
    group.add_argument("--decode-file", metavar="OUTPUT", help="base64 → 文件")
    parser.add_argument("--url-safe", action="store_true", help="URL-safe 编码")
    args = parser.parse_args()

    if args.encode:
        print(e
ncode(args.encode, args.url_safe))
    elif args.decode:
        print(decode(args.decode))
    elif args.encode_file:
        print(encode_file(args.encode_file))
    elif args.decode_file:
        data = sys.stdin.read()
        print(decode_to_file(data, args.decode_file))
    elif not sys.stdin.isatty():
        data = sys.stdin.read().strip()
        # 自动检测:如果看起来像 base64 就解码,否则编码
        try:
            decoded = base64.b64decode(data).decode('utf-8')
            if all(c.isprintable() or c.isspace() for c in decoded):
                print(f"🔓 解码结果:\n{decoded}")
            else:
                print(f"🔒 编码结果:\n{encode(data, args.url_safe)}")
        except Exception:
            print(f"🔒 编码结果:\n{encode(data, args.url_safe)}")
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

5. JWT 解析器 (jwt_parse.py)

解码 JWT header/payload,检查过期时间

使用方法

python3 jwt_parse.py "<token>"            # 解析
python3 jwt_parse.py --check "<token>"     # 检查过期
python3 jwt_parse.py "<token>" --json      # JSON 输出

完整源码 (复制保存为 jwt_parse.py 即可使用)

#!/usr/bin/env python3
"""
JWT 解析器 - 解码 JWT 的 header、payload,检查过期时间
用法:
  python3 jwt_parse.py <token>              # 解析 JWT
  python3 jwt_parse.py <token> --json       # JSON 格式输出
  python3 jwt_parse.py --check <token>      # 检查是否过期
"""

import sys
import json
import base64
import argparse
from datetime import datetime, timezone


def decode_segment(segment: str) -> dict:
    """解码 JWT 的一个 segment"""
    # 补齐 padding
    padding = 4 - len(segment) % 4
    if padding != 4:
        segment += '=' * padding
    return json.loads(base64.urlsafe_b64decode(segment).decode('utf-8'))


def parse_jwt(token: str) -> dict:
    """解析完整 JWT"""
    parts = token.strip().split('.')
    if len(parts) != 3:
        raise ValueError(f"无效的 JWT 格式 (期望 3 段,实际 {len(parts)} 段)")

    header = decode_segment(parts[0])
    payload = decode_segment(parts[1])

    return {
        "header": header,
        "payload": payload,
        "signature": parts[2],
        "signature_length": len(parts[2]),
    }


def format_jwt_info(token: str) -> str:
    """格式化 JWT 信息为可读输出"""
    try:
        jwt = parse_jwt(token)
    except Exception as e:
        return f"❌ 解析失败: {e}"

    lines = []
    lines.append("🔐 JWT 解析结果")
    lines.append("=" * 40)

    # Header
    lines.append("\n📋 Header:")
    lines.append(f"  算法: {jwt['header'].get('alg', 'N/A')}")
    lines.append(f"  类型: {jwt['header'].get('typ', 'N/A')}")
    if 'kid' in jwt['header']:
        lines.append(f"  Key ID: {jwt['header']['kid']}")

    # Payload
    lines.append("\n📋 Payload:")
    payload = jwt['payload']

    # 标准字段
    standard_fields = {
        'iss': '签发者',
        'sub': '主题',
        'aud': '受众',
        'exp': '过期时间',
        'nbf': '生效时间',
        'iat': '签发时间',
        'jti': 'JWT ID',
    }

    for key, label in standard_fields.items():
        if key in payload:
            val = payload[key]
            if key in ('exp
', 'nbf', 'iat'):
                dt = datetime.fromtimestamp(val, tz=timezone.utc)
                now = datetime.now(timezone.utc)
                status = "✅ 有效" if key != 'exp' or val > now.timestamp() else "❌ 已过期"
                lines.append(f"  {label}: {dt.strftime('%Y-%m-%d %H:%M:%S')} UTC {status}")
            else:
                lines.append(f"  {label}: {val}")

    # 自定义字段
    custom = {k: v for k, v in payload.items() if k not in standard_fields}
    if custom:
        lines.append("\n📋 自定义字段:")
        for k, v in custom.items():
            val = json.dumps(v, ensure_ascii=False) if isinstance(v, (dict, list)) else str(v)
            lines.append(f"  {k}: {val}")

    # Signature
    lines.append(f"\n📋 Signature:")
    lines.append(f"  长度: {jwt['signature_length']} 字符")
    lines.append(f"  (需要 secret 验证)")

    return "\n".join(lines)


def check_expiry(token: str) -> str:
    """检查 JWT 是否过期"""
    try:
        jwt = parse_jwt(token)
    except Exception as e:
        return f"❌ {e}"

    payload = jwt['payload']
    if 'exp' not in payload:
        return "⚠️ 该 JWT 没有过期时间 (exp 字段)"

    exp = payload['exp']
    now = datetime.now(timezone.utc).timestamp()
    dt = datetime.fromtimestamp(exp, tz=timezone.utc)
    remaining = exp - now

    if remaining > 0:
        hours = remaining / 3600
        if hours > 24:
            return f"✅ 有效 | 过期时间: {dt.strftime('%Y-%m-%d %H:%M:%S')} UTC | 剩余 {hours/24:.1f} 天"
        return f"✅ 有效 | 过期时间: {dt.strftime('%Y-%m-%d %H:%M:%S')} UTC | 剩余 {hours:.1f} 小时"
    else:
        hours = abs(remaining) / 3600
        return f"❌ 已过期 | 过期时间: {dt.strftime('%Y-%m-%d %H:%M:%S')} UTC | 已过期 {hours:.1f} 小时"


def main():
    parser = argparse.ArgumentParser(description="JWT 解析器")
    parser.add_argument("token", nargs="?", help="JWT token")
    parser.add_argument("--check", "-c", action="store_true", help="检查是否过期")
    par
ser.add_argument("--json", "-j", action="store_true", help="JSON 格式输出")
    args = parser.parse_args()

    token = args.token
    if not token and not sys.stdin.isatty():
        token = sys.stdin.read().strip()

    if not token:
        parser.print_help()
        return

    if args.check:
        print(check_expiry(token))
    elif args.json:
        try:
            jwt = parse_jwt(token)
            print(json.dumps(jwt, ensure_ascii=False, indent=2))
        except Exception as e:
            print(f"❌ {e}")
    else:
        print(format_jwt_info(token))


if __name__ == "__main__":
    main()

6. 正则表达式测试器 (regex_test.py)

实时匹配、高亮、捕获组、替换演示

使用方法

python3 regex_test.py "(\w+)@(\w+)" "test@example.com" --groups
python3 regex_test.py "\d+" "a1b2c3" --all
python3 regex_test.py "\d+" "a1b2" -r "X"  # 替换

完整源码 (复制保存为 regex_test.py 即可使用)

#!/usr/bin/env python3
"""
正则表达式测试器 - 实时匹配、高亮、捕获组
用法:
  python3 regex_test.py "pattern" "test_string"
  python3 regex_test.py "\d+" "abc123def456"
  python3 regex_test.py "(\w+)@(\w+)" "test@example.com" --groups
  python3 regex_test.py "\d+" "a1 b2 c3" --all
  echo "test data" | python3 regex_test.py "\w+"
"""

import re
import sys
import json
import argparse


def test_regex(pattern: str, text: str, show_all: bool = False, show_groups: bool = False) -> str:
    """测试正则表达式"""
    try:
        compiled = re.compile(pattern)
    except re.error as e:
        return f"❌ 正则表达式错误: {e}"

    lines = []
    lines.append(f"🔍 正则: {pattern}")
    lines.append(f"📝 文本: {text[:100]}{'...' if len(text) > 100 else ''}")
    lines.append("=" * 40)

    # 匹配检测
    match = compiled.search(text)
    if not match:
        lines.append("❌ 无匹配")
        return "\n".join(lines)

    if show_all:
        matches = list(compiled.finditer(text))
        lines.append(f"✅ 找到 {len(matches)} 个匹配:\n")
        for i, m in enumerate(matches):
            lines.append(f"  [{i}] 位置 {m.start()}-{m.end()}: '{m.group()}'")
            if show_groups and m.groups():
                for j, g in enumerate(m.groups()):
                    lines.append(f"      组{j+1}: '{g}'")
    else:
        lines.append(f"✅ 匹配成功!")
        lines.append(f"  位置: {match.start()}-{match.end()}")
        lines.append(f"  匹配: '{match.group()}'")
        if show_groups and match.groups():
            lines.append(f"  捕获组:")
            for i, g in enumerate(match.groups()):
                lines.append(f"    组{i+1}: '{g}'")
            if match.groupdict():
                lines.append(f"  命名组:")
                for k, v in match.groupdict().items():
                    lines.append(f"    {k}: '{v}'")

    # 高亮展示
    highlighted = compiled.sub(lambda m: f"\033[43m\033[30m{m.group()}\033[0m", text)
    lines.append(f"\n📌 高亮:\n{hig
hlighted}")

    return "\n".join(lines)


def replace_demo(pattern: str, text: str, replacement: str) -> str:
    """替换演示"""
    try:
        result = re.sub(pattern, replacement, text)
        return f"替换前: {text}\n替换后: {result}"
    except re.error as e:
        return f"❌ {e}"


def main():
    parser = argparse.ArgumentParser(description="正则表达式测试器")
    parser.add_argument("pattern", nargs="?", help="正则表达式")
    parser.add_argument("text", nargs="?", help="测试文本")
    parser.add_argument("--all", "-a", action="store_true", help="显示所有匹配")
    parser.add_argument("--groups", "-g", action="store_true", help="显示捕获组")
    parser.add_argument("--replace", "-r", metavar="REPLACEMENT", help="替换测试")
    args = parser.parse_args()

    if args.pattern and args.text:
        if args.replace:
            print(replace_demo(args.pattern, args.text, args.replace))
        else:
            print(test_regex(args.pattern, args.text, args.all, args.groups))
    elif args.pattern and not sys.stdin.isatty():
        text = sys.stdin.read().strip()
        print(test_regex(args.pattern, text, args.all, args.groups))
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

7. Hash 生成器 (hash_gen.py)

MD5/SHA1/SHA256/SHA512 一键生成

使用方法

echo "hello" | python3 hash_gen.py           # 所有 hash
python3 hash_gen.py -t sha256 "hello"       # 指定算法
python3 hash_gen.py --file image.png        # 文件 hash
python3 hash_gen.py --compare a.txt b.txt   # 比较文件

完整源码 (复制保存为 hash_gen.py 即可使用)

#!/usr/bin/env python3
"""
Hash 生成器 - MD5/SHA1/SHA256/SHA512 一键生成
用法:
  echo "hello" | python3 hash_gen.py                 # 生成所有 hash
  python3 hash_gen.py -t sha256 "hello world"        # 指定算法
  python3 hash_gen.py --file image.png               # 文件 hash
  python3 hash_gen.py --compare a.txt b.txt          # 比较两个文件 hash
"""

import hashlib
import sys
import argparse


def hash_text(text: str, algo: str = "all") -> str:
    """生成文本 hash"""
    data = text.encode('utf-8')
    return _hash_data(data, algo)


def hash_file(filepath: str, algo: str = "all") -> str:
    """生成文件 hash"""
    with open(filepath, 'rb') as f:
        data = f.read()
    return _hash_data(data, algo)


def _hash_data(data: bytes, algo: str) -> str:
    algorithms = {
        "md5": hashlib.md5,
        "sha1": hashlib.sha1,
        "sha256": hashlib.sha256,
        "sha512": hashlib.sha512,
        "sha224": hashlib.sha224,
        "sha384": hashlib.sha384,
        "sha3_256": hashlib.sha3_256,
        "sha3_512": hashlib.sha3_512,
    }

    if algo == "all":
        lines = ["# Hash 结果\n"]
        for name, func in algorithms.items():
            h = func(data).hexdigest()
            lines.append(f"{name.upper():12} {h}")
        return "\n".join(lines)
    elif algo.lower() in algorithms:
        return algorithms[algo.lower()](data).hexdigest()
    else:
        return f"❌ 不支持的算法: {algo}\n支持: {', '.join(algorithms.keys())}"


def compare_files(file1: str, file2: str) -> str:
    """比较两个文件的 hash"""
    h1 = hash_file(file1, "sha256")
    h2 = hash_file(file2, "sha256")
    same = h1 == h2
    return (
        f"文件1: {file1}\n"
        f"  SHA256: {h1}\n"
        f"文件2: {file2}\n"
        f"  SHA256: {h2}\n"
        f"{'✅ 文件相同' if same else '❌ 文件不同'}"
    )


def main():
    parser = argparse.ArgumentParser(description="Hash 生成器")
    group = parser.add_mutually_exclusive_group()
    group.
add_argument("-t", "--type", default="all", help="算法 (md5/sha1/sha256/sha512/sha3_256)")
    group.add_argument("--compare", nargs=2, metavar=("FILE1", "FILE2"), help="比较两个文件")
    parser.add_argument("--file", "-f", help="对文件生成 hash")
    parser.add_argument("text", nargs="?", help="要 hash 的文本")
    args = parser.parse_args()

    if args.compare:
        print(compare_files(args.compare[0], args.compare[1]))
    elif args.file:
        print(hash_file(args.file, args.type))
    elif args.text:
        print(hash_text(args.text, args.type))
    elif not sys.stdin.isatty():
        data = sys.stdin.read().strip()
        print(hash_text(data, args.type))
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

8. URL 编解码 (url_tool.py)

URL 编解码 + 参数解析 + 构建

使用方法

python3 url_tool.py -e "hello world"     # 编码
python3 url_tool.py -d "hello%20world"    # 解码
python3 url_tool.py --parse "https://x.com?a=1&b=2"
python3 url_tool.py --build "https://x.com" a=1 b=2

完整源码 (复制保存为 url_tool.py 即可使用)

#!/usr/bin/env python3
"""
URL 编解码 + 参数解析工具
用法:
  python3 url_tool.py -e "hello world"             # URL 编码
  python3 url_tool.py -d "hello%20world"           # URL 解码
  python3 url_tool.py --parse "https://x.com?a=1&b=2"  # 解析 URL 参数
  python3 url_tool.py --build "https://x.com" a=1 b=2  # 构建 URL
  echo "hello world" | python3 url_tool.py -e      # 管道编码
"""

import sys
import argparse
from urllib.parse import quote, unquote, urlparse, parse_qs, urlencode


def encode(text: str) -> str:
    return quote(text, safe='')


def decode(text: str) -> str:
    return unquote(text)


def parse_url(url: str) -> str:
    """解析 URL 各部分"""
    parsed = urlparse(url)
    lines = []
    lines.append(f"🔗 URL 解析结果")
    lines.append("=" * 40)
    lines.append(f"  协议: {parsed.scheme or 'N/A'}")
    lines.append(f"  主机: {parsed.hostname or 'N/A'}")
    if parsed.port:
        lines.append(f"  端口: {parsed.port}")
    lines.append(f"  路径: {parsed.path or '/'}")
    if parsed.fragment:
        lines.append(f"  锚点: {parsed.fragment}")

    # 查询参数
    params = parse_qs(parsed.query, keep_blank_values=True)
    if params:
        lines.append(f"\n📋 查询参数 ({len(params)} 个):")
        for k, v in params.items():
            val = v[0] if len(v) == 1 else str(v)
            lines.append(f"  {k}: {val}")

    return "\n".join(lines)


def build_url(base: str, params: list) -> str:
    """构建带参数的 URL"""
    pairs = []
    for p in params:
        if '=' in p:
            k, v = p.split('=', 1)
            pairs.append((k, v))
        else:
            pairs.append((p, ''))

    separator = '&' if '?' in base else '?'
    return base + separator + urlencode(pairs)


def main():
    parser = argparse.ArgumentParser(description="URL 编解码工具")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("-e", "--encode", help="URL 编码")
    group.add_argument("-d", "--decode", help="URL 解码")
 
   group.add_argument("--parse", help="解析 URL")
    group.add_argument("--build", nargs="+", metavar=("BASE", "KEY=VAL"), help="构建 URL")
    args = parser.parse_args()

    if args.encode:
        print(encode(args.encode))
    elif args.decode:
        print(decode(args.decode))
    elif args.parse:
        print(parse_url(args.parse))
    elif args.build and len(args.build) >= 1:
        print(build_url(args.build[0], args.build[1:]))
    elif not sys.stdin.isatty():
        data = sys.stdin.read().strip()
        print(encode(data))
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

9. CSV ↔ JSON 互转 (csv_json.py)

CSV 与 JSON 互转,支持嵌套扁平化

使用方法

python3 csv_json.py --to-json data.csv    # CSV→JSON
python3 csv_json.py --to-csv data.json     # JSON→CSV
python3 csv_json.py --stats data.csv        # CSV 统计
echo 'name,age\nAlice,30' | python3 csv_json.py

完整源码 (复制保存为 csv_json.py 即可使用)

#!/usr/bin/env python3
"""
CSV ↔ JSON 互转工具
用法:
  python3 csv_json.py --to-json data.csv          # CSV → JSON
  python3 csv_json.py --to-csv data.json          # JSON → CSV
  python3 csv_json.py --to-json data.csv --pretty # 美化输出
  echo 'name,age\nAlice,30' | python3 csv_json.py # 管道输入
  python3 csv_json.py --stats data.csv            # CSV 统计
"""

import csv
import json
import sys
import argparse
import io
from pathlib import Path


def csv_to_json(filepath: str = None, data: str = None, pretty: bool = True) -> str:
    """CSV → JSON"""
    if filepath:
        with open(filepath, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f)
            rows = list(reader)
    else:
        reader = csv.DictReader(io.StringIO(data))
        rows = list(reader)

    return json.dumps(rows, ensure_ascii=False, indent=2 if pretty else None)


def json_to_csv(filepath: str = None, data: str = None) -> str:
    """JSON → CSV"""
    if filepath:
        with open(filepath, 'r', encoding='utf-8') as f:
            obj = json.load(f)
    else:
        obj = json.loads(data)

    if not isinstance(obj, list) or not obj:
        return "❌ JSON 必须是非空数组"

    # 扁平化嵌套对象
    def flatten(d, parent_key='', sep='.'):
        items = []
        for k, v in d.items():
            new_key = f"{parent_key}{sep}{k}" if parent_key else k
            if isinstance(v, dict):
                items.extend(flatten(v, new_key, sep).items())
            elif isinstance(v, list):
                items.append((new_key, json.dumps(v, ensure_ascii=False)))
            else:
                items.append((new_key, v))
        return dict(items)

    flat_rows = [flatten(row) for row in obj]

    # 收集所有字段
    all_fields = []
    seen = set()
    for row in flat_rows:
        for k in row:
            if k not in seen:
                all_fields.append(k)
                seen.add(k)

    output = i
o.StringIO()
    writer = csv.DictWriter(output, fieldnames=all_fields)
    writer.writeheader()
    writer.writerows(flat_rows)
    return output.getvalue()


def csv_stats(filepath: str = None, data: str = None) -> str:
    """CSV 统计信息"""
    if filepath:
        with open(filepath, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f)
            rows = list(reader)
            fields = reader.fieldnames
    else:
        reader = csv.DictReader(io.StringIO(data))
        rows = list(reader)
        fields = reader.fieldnames

    lines = [f"📊 CSV 统计", "=" * 30]
    lines.append(f"  行数: {len(rows)}")
    lines.append(f"  列数: {len(fields)}")
    lines.append(f"\n📋 字段:")
    for f in fields:
        non_empty = sum(1 for r in rows if r.get(f, '').strip())
        lines.append(f"  {f}: {non_empty}/{len(rows)} 非空 ({non_empty/len(rows)*100:.0f}%)")

    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="CSV ↔ JSON 互转")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("--to-json", metavar="CSV_FILE", help="CSV → JSON")
    group.add_argument("--to-csv", metavar="JSON_FILE", help="JSON → CSV")
    group.add_argument("--stats", metavar="CSV_FILE", help="CSV 统计")
    parser.add_argument("--pretty", action="store_true", help="美化 JSON 输出")
    parser.add_argument("--output", "-o", help="输出文件")
    args = parser.parse_args()

    if args.to_json:
        result = csv_to_json(args.to_json, pretty=args.pretty or True)
    elif args.to_csv:
        result = json_to_csv(args.to_csv)
    elif args.stats:
        result = csv_stats(args.stats)
    elif not sys.stdin.isatty():
        data = sys.stdin.read()
        # 自动检测
        if data.strip().startswith('['):
            result = json_to_csv(data=data)
        else:
            result = csv_to_json(data=data)
    else:
        parser.print_help()
        r
eturn

    if args.output:
        Path(args.output).write_text(result, encoding='utf-8')
        print(f"✅ 已写入 {args.output}")
    else:
        print(result)


if __name__ == "__main__":
    main()

10. Cron 解析器 (cron_parse.py)

解释 cron 含义 + 下次执行时间

使用方法

python3 cron_parse.py "*/5 * * * *"        # 解析
python3 cron_parse.py "0 9 * * 1-5" --next 5 # 未来5次
python3 cron_parse.py "0 0 1 * *"            # 每月1号

完整源码 (复制保存为 cron_parse.py 即可使用)

#!/usr/bin/env python3
"""
Cron 表达式解析器 - 解释 cron 含义 + 下次执行时间
用法:
  python3 cron_parse.py "*/5 * * * *"             # 解析 cron
  python3 cron_parse.py "0 9 * * 1-5"             # 工作日每天 9 点
  python3 cron_parse.py "0 0 1 * *" --next 5      # 显示未来 5 次执行时间
"""

import sys
import argparse
from datetime import datetime, timedelta


FIELD_NAMES = ["分钟", "小时", "日", "月", "星期几"]
MONTH_NAMES = ["", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]
DOW_NAMES = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"]


def parse_field(field: str, min_val: int, max_val: int, names: list = None) -> list:
    """解析单个 cron 字段"""
    results = set()
    for part in field.split(','):
        if '/' in part:
            # */5 or 1-10/2
            range_part, step = part.split('/')
            step = int(step)
            if range_part == '*':
                start, end = min_val, max_val
            elif '-' in range_part:
                start, end = map(int, range_part.split('-'))
            else:
                start = end = int(range_part)
            for v in range(start, end + 1, step):
                results.add(v)
        elif '-' in part:
            start, end = map(int, part.split('-'))
            for v in range(start, end + 1):
                results.add(v)
        elif part == '*':
            for v in range(min_val, max_val + 1):
                results.add(v)
        else:
            val = int(part)
            results.add(val)
    return sorted(results)


def explain_cron(expr: str) -> str:
    """解释 cron 表达式的含义"""
    fields = expr.strip().split()
    if len(fields) != 5:
        return f"❌ cron 表达式必须有 5 个字段 (实际 {len(fields)} 个)"

    minute, hour, day, month, dow = fields
    lines = ["⏰ Cron 表达式解析"]
    lines.append(f"  表达式: {expr}")
    lines.append("=" * 40)

    # 解析各字段
    parsed = {
        "分钟": parse_field(minute, 0, 59),
        "小时": parse_fie
ld(hour, 0, 23),
        "日": parse_field(day, 1, 31),
        "月": parse_field(month, 1, 12, MONTH_NAMES),
        "星期几": parse_field(dow, 0, 6, DOW_NAMES),
    }

    for name, values in parsed.items():
        if len(values) == (60 if name == "分钟" else 24 if name == "小时" else 31 if name == "日" else 12 if name == "月" else 7):
            lines.append(f"  {name}: 每个值都匹配")
        else:
            lines.append(f"  {name}: {values}")

    # 生成人类可读描述
    desc = []
    if minute == "*/1":
        desc.append("每分钟")
    elif minute.startswith("*/"):
        desc.append(f"每 {minute[2:]} 分钟")
    else:
        desc.append(f"第 {minute} 分钟")

    if hour == "*":
        desc.append("每小时")
    elif hour.startswith("*/"):
        desc.append(f"每 {hour[2:]} 小时")
    else:
        desc.append(f"{hour} 点")

    if day != "*":
        desc.append(f"每月第 {day} 天")

    if dow != "*":
        dow_map = {"0": "周日", "1": "周一", "2": "周二", "3": "周三", "4": "周四", "5": "周五", "6": "周六", "7": "周日"}
        if dow in dow_map:
            desc.append(dow_map[dow])
        else:
            desc.append(f"星期 {dow}")

    lines.append(f"\n📌 含义: {' '.join(desc)}")

    return "\n".join(lines)


def next_runs(expr: str, count: int = 5, start: datetime = None) -> str:
    """计算未来 N 次执行时间"""
    fields = expr.strip().split()
    if len(fields) != 5:
        return f"❌ 无效的 cron 表达式"

    minute_f, hour_f, day_f, month_f, dow_f = fields

    minutes = parse_field(minute_f, 0, 59)
    hours = parse_field(hour_f, 0, 23)
    days = parse_field(day_f, 1, 31)
    months = parse_field(month_f, 1, 12)
    dows = parse_field(dow_f, 0, 6)

    if start is None:
        start = datetime.now().replace(second=0, microsecond=0) + timedelta(minutes=1)

    runs = []
    current = start
    max_iterations = 366 * 24 * 60  # 最多搜索一年

    for _ in range(max_iterations):
        if len(runs) >= count:
            break
  
      if (current.minute in minutes and
            current.hour in hours and
            current.day in days and
            current.month in months and
            current.weekday() in [d - 1 if d > 0 else 6 for d in dows]):
            runs.append(current)
        current += timedelta(minutes=1)

    if not runs:
        return "❌ 未找到未来执行时间"

    lines = [f"📅 未来 {len(runs)} 次执行时间:"]
    for i, dt in enumerate(runs):
        weekday = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][dt.weekday()]
        lines.append(f"  {i+1}. {dt.strftime('%Y-%m-%d %H:%M')} ({weekday})")

    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="Cron 表达式解析器")
    parser.add_argument("expr", help="Cron 表达式 (5 字段)")
    parser.add_argument("--next", "-n", type=int, default=0, metavar="N", help="显示未来 N 次执行时间")
    args = parser.parse_args()

    print(explain_cron(args.expr))
    if args.next > 0:
        print()
        print(next_runs(args.expr, args.next))


if __name__ == "__main__":
    main()

设计理念

  • 零依赖: 只用 Python 标准库,无需 pip install
  • 单文件: 每个工具一个 .py 文件,拷走即用
  • 管道友好: 支持 stdin/stdout,可组合使用
  • 自动检测: 智能判断输入类型,减少参数记忆