逍遥云初 | 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_argument("--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(encode(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="检查是否过期")
parser.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{highlighted}")
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 = io.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()
return
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_field(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,可组合使用
- 自动检测: 智能判断输入类型,减少参数记忆





