日常开发中经常需要快速查看 SQLite 数据库——调试本地应用、分析浏览器历史记录、检查爬虫数据,场景非常多。虽然市面上有 DB Browser 等 GUI 工具,但在终端里一条命令搞定显然更快。
今天用 OpenCode 写一个命令行 SQLite 查询工具 sqliteq,支持:
技术栈:Python 3,零依赖(仅用标准库 sqlite3、argparse、json、csv),约 90 行代码。
python --version # Python 3.8+ mkdir sqliteq && cd sqliteq opencode .
准备一个测试数据库:
sqlite3 test.db "CREATE TABLE users(id INTEGER, name TEXT, score REAL);" sqlite3 test.db "INSERT INTO users VALUES(1,'张三',95.5),(2,'李四',87.0),(3,'王五',92.3);"
我对 OpenCode 说:
> 用 Python 写一个命令行 SQLite 查询工具 sqliteq.py。用 argparse 解析参数:第一个位置参数是数据库路径,-q 指定 SQL 查询语句,-f 指定输出格式(table/csv/json,默认 table)。先搭好框架,sqlite3 连接和查询逻辑用 TODO 注释占位。
OpenCode 生成:
#!/usr/bin/env python3
"""SQLite 命令行查询工具"""
import sys
import sqlite3
import argparse
def main():
parser = argparse.ArgumentParser(
description="sqliteq — 命令行 SQLite 查询工具"
)
parser.add_argument("db", help="SQLite 数据库文件路径")
parser.add_argument("-q", "--query", help="要执行的 SQL 查询")
parser.add_argument(
"-f", "--format", choices=["table", "csv", "json"],
default="table", help="输出格式(默认 table)"
)
args = parser.parse_args()
if not args.query:
print("请用 -q 指定查询语句,例如: python sqliteq.py test.db -q 'SELECT * FROM users'")
sys.exit(1)
# TODO: 连接数据库并执行查询
# TODO: 按指定格式输出结果
print(f"数据库: {args.db}")
print(f"查询: {args.query}")
if __name__ == "__main__":
main()
关键要点: 先让 OpenCode 把骨架搭好——参数解析 + 输入校验。不用一次说清所有细节,第一版能跑起来最重要。choices=["table","csv","json"] 这个参数约束是 OpenCode 自己加的,比手动指定 type=str 更严谨。
我对 OpenCode 说:
> 把两行 TODO 换成真正的实现。用 sqlite3 连接数据库,执行 query,拿到列名和数据行。默认 table 格式输出一个 ASCII 表格——像 MySQL 命令行那种带边框的对齐表格。用 sqlite3.Row 让列名可访问。
OpenCode 生成了核心逻辑:
conn = sqlite3.connect(args.db)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
try:
cursor.execute(args.query)
rows = cursor.fetchall()
headers = [desc[0] for desc in cursor.description]
if args.format == "table":
# 计算每列宽度
col_widths = [len(h) for h in headers]
for row in rows:
for i, val in enumerate(row):
col_widths[i] = max(col_widths[i], len(str(val)))
sep = "+" + "+".join("-" * (w + 2) for w in col_widths) + "+"
header_line = "|" + "|".join(
f" {h:<{col_widths[i]}} " for i, h in enumerate(headers)
) + "|"
lines = [sep, header_line, sep]
for row in rows:
line = "|" + "|".join(
f" {str(v):<{col_widths[i]}} " for i, v in enumerate(row)
) + "|"
lines.append(line)
lines.append(sep)
print("\n".join(lines))
except sqlite3.Error as e:
print(f"SQLite 错误: {e}", file=sys.stderr)
sys.exit(1)
finally:
conn.close()
运行效果:
$ python sqliteq.py test.db -q "SELECT * FROM users" +----+------+-------+ | id | name | score | +----+------+-------+ | 1 | 张三 | 95.5 | | 2 | 李四 | 87.0 | | 3 | 王五 | 92.3 | +----+------+-------+
关键要点: 表格绘制逻辑约 20 行,OpenCode 用 max() 算出每列最大宽度再拼接边框,和你在网上搜到的"Python ASCII table"教程写法几乎一样。conn.row_factory = sqlite3.Row 让遍历行更干净,这个细节说明 OpenCode 对标准库很熟悉。
我对 OpenCode 说:
> 补全 csv 和 json 两种输出格式。另外加两个参数:-l 列出数据库所有表名,-s TABLE_NAME 查看某张表的建表 SQL。-o 参数把结果输出到文件而不是终端。
OpenCode 一次性补全了整个文件(加上前面的代码,完整版见文末)。
JSON 导出部分:
if args.format == "json":
result = [dict(zip(headers, row)) for row in rows]
output = json.dumps(result, ensure_ascii=False, indent=2)
elif args.format == "csv":
buf = StringIO()
writer = csv.writer(buf)
if headers:
writer.writerow(headers)
writer.writerows(rows)
output = buf.getvalue()
列出表:
$ python sqliteq.py test.db -l 数据库表: - users
查看建表语句:
$ python sqliteq.py test.db -s users CREATE TABLE users(id INTEGER, name TEXT, score REAL);
导出为 JSON 文件:
$ python sqliteq.py test.db -q "SELECT * FROM users" -f json -o result.json 结果已写入 result.json
关键要点: 这一步需求不少(3 种输出格式 + 2 个辅助命令 + 文件输出),但 OpenCode 能一次处理多个改动。dict(zip(headers, row)) 把 sqlite3.Row 转成字典很 Pythonic,StringIO 做 CSV 缓冲也很合理。这说明只要把需求列清楚,OpenCode 喜欢一次性搞定。
#!/usr/bin/env python3
"""SQLite 命令行查询工具"""
import sys
import sqlite3
import argparse
import json
import csv
from io import StringIO
def format_table(rows, headers):
if not rows:
return "(空结果)"
col_widths = [len(h) for h in headers]
for row in rows:
for i, val in enumerate(row):
col_widths[i] = max(col_widths[i], len(str(val)))
sep = "+" + "+".join("-" * (w + 2) for w in col_widths) + "+"
header_line = "|" + "|".join(
f" {h:<{col_widths[i]}} " for i, h in enumerate(headers)
) + "|"
lines = [sep, header_line, sep]
for row in rows:
line = "|" + "|".join(
f" {str(v):<{col_widths[i]}} " for i, v in enumerate(row)
) + "|"
lines.append(line)
lines.append(sep)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="sqliteq — 命令行 SQLite 查询工具")
parser.add_argument("db", help="SQLite 数据库文件路径")
parser.add_argument("-q", "--query", help="要执行的 SQL 查询")
parser.add_argument("-l", "--list-tables", action="store_true", help="列出所有表")
parser.add_argument("-s", "--schema", help="查看指定表的建表语句")
parser.add_argument("-f", "--format", choices=["table", "csv", "json"],
default="table", help="输出格式(默认 table)")
parser.add_argument("-o", "--output", help="输出到文件(默认输出到终端)")
args = parser.parse_args()
conn = sqlite3.connect(args.db)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
try:
if args.list_tables:
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
)
tables = [row[0] for row in cursor.fetchall()]
if tables:
print("数据库表:")
for t in tables:
print(f" - {t}")
else:
print("数据库中无表")
return
if args.schema:
cursor.execute(
"SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
(args.schema,),
)
row = cursor.fetchone()
if row and row[0]:
print(row[0] + ";")
else:
print(f"表 '{args.schema}' 不存在")
return
if not args.query:
print("请用 -q 指定查询,或用 -l 列出表")
sys.exit(1)
cursor.execute(args.query)
rows = cursor.fetchall()
headers = [desc[0] for desc in cursor.description] if cursor.description else []
if args.format == "json":
result = [dict(zip(headers, row)) for row in rows]
output = json.dumps(result, ensure_ascii=False, indent=2)
elif args.format == "csv":
buf = StringIO()
writer = csv.writer(buf)
if headers:
writer.writerow(headers)
writer.writerows(rows)
output = buf.getvalue()
else:
output = format_table([list(r) for r in rows], headers)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(output)
print(f"结果已写入 {args.output}")
else:
print(output)
except sqlite3.Error as e:
print(f"SQLite 错误: {e}", file=sys.stderr)
sys.exit(1)
finally:
conn.close()
if __name__ == "__main__":
main()
整个工具从想法到可用,3 段对话、不到 5 分钟。一次代码都没手写。
| 方式 | 耗时 | 操作 |
|------|------|------|
| 手动编码 | ~20 分钟 | 查 sqlite3 文档、写表格对齐逻辑、调试 CSV/JSON 导出 |
| OpenCode | ~5 分钟 | 3 段对话,逐层迭代 |
体会最深的是迭代开发节奏:搭框架 → 加核心逻辑 → 补辅助功能,每一步都产出可运行的结果。中间表格对齐出了个小问题,直接对 OpenCode 说"列宽没对齐",它立刻修正了 f-string 里的宽度参数。这种即时反馈让人很安心。
如果你也需要频繁和 SQLite 打交道,不妨花几分钟让 OpenCode 给你写个顺手工具。