dsq 0.23.0 常用 CLI 命令与工具详解
dsq v0.23.0 命令行实战全解:文件/stdin/REPL 三种输入形态、{N} 与 {"路径"} 表引用、多文件 JOIN、11 种格式转 JSON、嵌套字段与 JSON 操作符、REGEXP/stdlib 扩展函数/窗口函数、jq 与 Shell 脚本集成、退出码矩阵,及追加重定向缺陷、REPL 热缓存报错等实测排障。
dsq 0.23.0 常用 CLI 命令与工具详解
目标:系统梳理 dsq v0.23.0 的命令行实战——三种输入形态、
{N}/{"路径"}表引用语法、多文件 JOIN、格式转换 dump、嵌套字段与 JSON 操作符、REGEXP/扩展函数/窗口函数、查询文件、与 jq/Shell 脚本的集成及退出码语义。全部命令与回显均为 Ubuntu 22.04 真实环境实测、逐行照抄。二进制安装见 《Ubuntu 22.04 安装 dsq 0.23.0 指南》,flag 与环境变量语义见 《dsq 0.23.0 配置参数详解》。
一、环境信息与测试数据准备
| 项目 | 值 | 说明 |
|---|---|---|
| 操作系统 | Ubuntu 22.04 LTS(x86_64) | 操作账号 root |
| 组件 | dsq v0.23.0 | dsq --version 实测回显 dsq v0.23.0 |
| SQL 方言 | SQLite | 引擎经 DataStation runner 库驱动,文件即表 |
本文示例数据均可一条命令重建:
$ printf "id,name\n1,Ted\n2,Marjory\n3,Micah\n" > users.csv
$ printf "name,score\nFritz,90\nRainer,95.2\nFountainer,100\n" > scores.csv
$ mkdir -p testdata/join
$ printf "id,name\n1,Ted\n2,Marjory\n3,Micah\n" > testdata/join/users.csv
$ cat > testdata/join/ages.json <<'EOF'
[
{"id": 1, "age": 88},
{"id": 2, "age": 56},
{"id": 3, "age": 33}
]
EOF
$ cat > user_addresses.json <<'EOF'
[
{"name": "Agarrah", "location": {"city": "Toronto", "address": { "number": 1002 }}},
{"name": "Minoara", "location": {"city": "Mexico City", "address": { "number": 19 }}},
{"name": "Fontoon", "location": {"city": "New London", "address": { "number": 12 }}}
]
EOF
$ cat > fields.json <<'EOF'
[
{"field1": [1]},
{"field1": [2]}
]
EOF
$ cat > api-results.json <<'EOF'
{
"data": {
"data": [
{"id": 1, "name": "Corah"},
{"id": 3, "name": "Minh"}
]
},
"total": 2
}
EOF
$ cat > orders.ndjson <<'EOF'
{"id": 1, "item": "keyboard", "qty": 2, "price": 49.9}
{"id": 2, "item": "mouse", "qty": 5, "price": 12.5}
{"id": 3, "item": "monitor", "qty": 1, "price": 219.0}
EOF
$ cat > config.yaml <<'EOF'
- env: prod
region: cn-east
replicas: 3
- env: dev
region: cn-north
replicas: 1
EOF
$ cat > app.logfmt <<'EOF'
level=info msg="served" path=/api duration=12ms
level=error msg="db timeout" path=/order duration=5000ms
level=info msg="served" path=/health duration=2ms
EOF
$ cat > access.log <<'EOF'
127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
192.168.1.5 - - [10/Oct/2000:14:02:11 -0700] "POST /api/order HTTP/1.1" 500 0
10.0.0.8 - alice [10/Oct/2000:14:10:05 -0700] "GET /index.html HTTP/1.1" 404 128
EOF
$ printf "city\tpop\nTokyo\t37\nDelhi\t32\n" > city.tsv
另有官方仓库 testdata/ 目录的 userdata.parquet(1000 行)用于大文件演示。JSON 输出统一接 | cat(规避 v0.23.0 追加重定向缺陷,见第十一章第 1 条)。
二、三种输入形态
2.1 文件参数 + 查询(主形态)
$ dsq --pretty users.csv "SELECT * FROM {}"
+----+---------+
| id | name |
+----+---------+
| 1 | Ted |
| 2 | Marjory |
| 3 | Micah |
+----+---------+
(3 rows)
文件按扩展名识别格式,查询是最后一个位置参数。
2.2 stdin 管道 + -s 声明类型
无扩展名的日志流必须显式给类型:
$ cat scores.csv | dsq -s csv "SELECT * FROM {} LIMIT 2" | cat
[{"name":"Fritz","score":"90"},
{"name":"Rainer","score":"95.2"}]
nginx 访问日志解析为 11 列(agent/code/host/http_x_forwarded_for/method/path/referer/remote/size/time/user),实测按状态码过滤(code 按字符串导入,数值比较需 CAST):
$ cat access.log | dsq -s text/nginxaccess --pretty "SELECT * FROM {} WHERE CAST(code AS INTEGER) >= 400"
+-------+------+------+----------------------+--------+-------------+---------+-------------+------+----------------------------+-------+
| agent | code | host | http_x_forwarded_for | method | path | referer | remote | size | time | user |
+-------+------+------+----------------------+--------+-------------+---------+-------------+------+----------------------------+-------+
| null | 500 | - | null | POST | /api/order | null | 192.168.1.5 | 0 | 10/Oct/2000:14:02:11 -0700 | - |
| null | 404 | - | null | GET | /index.html | null | 10.0.0.8 | 128 | 10/Oct/2000:14:10:05 -0700 | alice |
+-------+------+------+----------------------+--------+-------------+---------+-------------+------+----------------------------+-------+
(2 rows)
2.3 REPL 交互模式
-i 进入 REPL(强制表格输出与缓存),逐行输入 SQL、分号结尾:
$ printf "SELECT COUNT(1) FROM {};\nSELECT name FROM {} WHERE id = 1;\n" | dsq users.csv -i
Cache invalid, re-import required.
+----------+
| COUNT(1) |
+----------+
| 3 |
+----------+
(1 row)
EOF
+------+
| name |
+------+
| Ted |
+------+
(1 row)
$ echo $?
1
三个实测要点:输入耗尽时 REPL 打印 EOF 标记行并以退出码 1 结束——脚本化喂语句时不要把 $? 当成功判据;交互模式退出按 Ctrl+D(.exit 会报 near ".": syntax error);⚠️ 同一文件第二次进 REPL 会因既有缓存库重复建表直接失败(table "t_0" already exists),先删 /tmp/dsq-cache-*.db 再进(详见《配置参数详解》3.6)。
三、表引用语法:{N}、{} 与路径
文件以表的形式参与 SQL,表名是花括号引用:
| 写法 | 含义 | 实测 |
|---|---|---|
{0} | 第 0 个文件(0 起始) | 见 4.1 |
{} | {0} 的简写 | 见 2.1 |
{N} | 第 N 个文件 | 见第四章 |
{"路径"} | 单文件时,对象内数组的嵌套路径简写 | 见 3.2 |
{N, "路径"} | 指定文件 + 路径 | 见 3.2 |
3.1 越界与不存在的引用
$ dsq users.csv "SELECT * FROM {5}"
no such table:
$ echo $?
1
只传 1 个文件却引用 {5},报错里表名为空串——排错先数文件个数。
3.2 嵌套数组路径
api-results.json 的对象数组藏在 data.data 路径下,直接查会查不到;用路径引用:
$ dsq --pretty api-results.json "SELECT * FROM {\"data.data\"} ORDER BY id DESC"
+----+-------+
| id | name |
+----+-------+
| 3 | Minh |
| 1 | Corah |
+----+-------+
(2 rows)
完整写法 {0, "data.data"} 等价(实测输出 +----+-------+ | 1 | Corah | | 3 | Minh | 两行,默认序)。路径只支持穿过对象,不能穿过数组(官方 README 明示);Excel 多 sheet 时 sheet 名即路径,如 dsq data.xlsx 'SELECT COUNT(1) FROM {"Sheet2"}'(官方 README 用法,查询日期 2026-11-03)。
四、多文件 JOIN
4.1 {0}/{1} 直接引用
CSV 与 JSON 两种来源混合 JOIN(表格输出实测):
$ dsq --pretty testdata/join/users.csv testdata/join/ages.json "select {0}.name, {1}.age from {0} join {1} on {0}.id = {1}.id"
+-----+---------+
| age | name |
+-----+---------+
| 88 | Ted |
| 56 | Marjory |
| 33 | Micah |
+-----+---------+
(3 rows)
4.2 表别名
标准 SQL 别名同样可用,可读性更好:
$ dsq testdata/join/users.csv testdata/join/ages.json "select u.name, a.age from {0} u join {1} a on u.id = a.id" | cat
[{"name":"Ted","age":88},
{"name":"Marjory","age":56},
{"name":"Micah","age":33}]
注意 JSON 输出键序不保证稳定(同一查询实测多次运行键序可能不同),需要稳定序时接 jq --sort-keys(见 9.1)。
五、格式转换:不带查询直接 dump
省略查询参数即把任意支持格式转成 JSON 数组输出——dsq 可当轻量转换器用:
$ dsq testdata/join/users.csv | cat
[{"id":"1","name":"Ted"},
{"id":"2","name":"Marjory"},
{"id":"3","name":"Micah"}]
各格式实测(输入 → JSON 输出):
$ dsq config.yaml "SELECT * FROM {}" | cat
[{"env":"prod","region":"cn-east","replicas":3},
{"replicas":1,"env":"dev","region":"cn-north"}]
$ dsq app.logfmt "SELECT * FROM {}" | cat
[{"level":"info","msg":"served","path":"/api","duration":"12ms"},
{"duration":"5000ms","level":"error","msg":"db timeout","path":"/order"},
{"duration":"2ms","level":"info","msg":"served","path":"/health"}]
$ dsq orders.ndjson --pretty "SELECT item, qty, price FROM {} WHERE qty > 1"
+----------+-------+-----+
| item | price | qty |
+----------+-------+-----+
| keyboard | 49.9 | 2 |
| mouse | 12.5 | 5 |
+----------+-------+-----+
(2 rows)
$ dsq city.tsv --pretty "SELECT * FROM {} ORDER BY pop DESC"
+-------+-----+
| city | pop |
+-------+-----+
| Tokyo | 37 |
| Delhi | 32 |
+-------+-----+
(2 rows)
TSV 数值列默认已按数字导入(37/32 参与数值排序),CSV 则需 -n(对照实验见《配置参数详解》3.7)。Concatenated JSON——多个 JSON 对象首尾直接拼接、无逗号无换行(扩展名 cjson)——实测同样可读:
$ printf "{\"id\":1,\"v\":\"a\"}{\"id\":2,\"v\":\"b\"}" > test.cjson; dsq test.cjson "SELECT * FROM {}" | cat
[{"v":"a","id":1},
{"id":2,"v":"b"}]
Parquet 大文件计数:
$ dsq testdata/userdata.parquet "select count(*) from {}" | cat
[{"count(*)":1000}]
六、嵌套字段与 JSON 操作符
6.1 双引号点路径取标量
JSON 嵌套对象展平为 location.city 形式的列,引用需双引号:
$ dsq user_addresses.json "SELECT name, \"location.city\" FROM {}" | cat
[{"name":"Agarrah","location.city":"Toronto"},
{"name":"Minoara","location.city":"Mexico City"},
{"name":"Fontoon","location.city":"New London"}]
6.2 JSON 操作符取数组元素
->N 按下标取 JSON 数组内容:
$ dsq fields.json "SELECT field1->0 FROM {}" | cat
[{"field1->0":"1"},
{"field1->0":"2"}]
不加操作符直接选列得到的是数组原文(实测 [{"field1":"[1]"},\n{"field1":"[2]"}])。
6.3 限制:整对象不可检索
只能取到标量叶子;把整个嵌套对象选出来会报错(实测):
$ dsq user_addresses.json "SELECT name, {}.\"location\" FROM {}"
no such column: t_0.location
需要整对象时改为取出其全部标量子列,或用 {"路径"} 把对象数组提为表。
七、过滤、函数与聚合
7.1 REGEXP
SQLite 本身不带正则,dsq 注册了 Go regexp 实现(非 PCRE2,官方 README 明示):
$ dsq user_addresses.json "SELECT * FROM {} WHERE name REGEXP \"A.*\"" | cat
[{"location.address.number":1002,"location.city":"Toronto","name":"Agarrah"}]
7.2 LIKE、IN 子查询、LIMIT
$ dsq orders.ndjson "SELECT item, qty FROM {} WHERE item LIKE \"m%\"" | cat
[{"item":"mouse","qty":5},
{"item":"monitor","qty":1}]
$ dsq orders.ndjson "SELECT item FROM {} WHERE qty IN (SELECT qty FROM {} WHERE qty > 1)" | cat
[{"item":"keyboard"},
{"item":"mouse"}]
7.3 聚合与扩展函数
dsq 注册 go-sqlite3-stdlib 扩展,提供 stddev 等统计/字符串/URL/数学函数(SQLite 原生没有):
$ dsq orders.ndjson "SELECT item, AVG(price) AS avg_price, stddev(price) AS sd FROM {} GROUP BY item" | cat
[{"item":"keyboard","avg_price":49.9,"sd":0},
{"item":"monitor","avg_price":219,"sd":0},
{"avg_price":12.5,"sd":0,"item":"mouse"}]
组合聚合 + 排序做简易报表:
$ dsq orders.ndjson --pretty "SELECT item, SUM(qty*price) AS total FROM {} GROUP BY item ORDER BY total DESC"
+----------+-------+
| item | total |
+----------+-------+
| monitor | 219 |
| keyboard | 99.8 |
| mouse | 62.5 |
+----------+-------+
(3 rows)
7.4 窗口函数
SQLite 3.25+ 的窗口语法实测可用:
$ dsq orders.ndjson "SELECT item, price, ROW_NUMBER() OVER (ORDER BY price DESC) AS rn FROM {}" | cat
[{"item":"monitor","price":219,"rn":1},
{"item":"keyboard","price":49.9,"rn":2},
{"rn":3,"item":"mouse","price":12.5}]
7.5 CAST 处理字符串数值
CSV/TSV 列默认按字符串导入,数值比较/排序要显式 CAST(或开 -n):
$ dsq scores.csv "SELECT name, score FROM {0} WHERE CAST(score AS REAL) > 90 ORDER BY CAST(score AS REAL) DESC" | cat
[{"name":"Fountainer","score":"100"},
{"name":"Rainer","score":"95.2"}]
八、查询文件与 schema 探查
复杂 SQL 落文件用 -f 引用(查询内容即 7.5 的语句存为 query.sql):
$ dsq scores.csv -f query.sql | cat
[{"score":"100","name":"Fountainer"},
{"score":"95.2","name":"Rainer"}]
写查询前先探列名与类型,避免 no such column:
$ dsq --schema users.csv
{
"kind": "array",
"array": {
"kind": "object",
"object": {
"id": {
"kind": "scalar",
"scalar": "string"
},
"name": {
"kind": "scalar",
"scalar": "string"
}
}
}
}
输出是 JSON 树:kind 标层级(array/object/scalar),scalar 给推断类型。实测该 Parquet 无 id 列时 SELECT SUM(id) 报 no such column: id,先 --schema 可避免;嵌套对象展开与 Avro 复杂 schema 的更多形态见《配置参数详解》3.2。
九、与 jq、Shell 脚本的集成
9.1 jq 美化与键序固定
$ dsq testdata/userdata.parquet "select count(*) from {}" | jq
[
{
"count(*)": 1000
}
]
$ dsq testdata/join/users.csv "SELECT * FROM {}" | jq --sort-keys
[
{
"id": "1",
"name": "Ted"
},
{
"id": "2",
"name": "Marjory"
},
{
"id": "3",
"name": "Micah"
}
]
9.2 Shell 脚本集成(实测)
report.sh 取聚合结果字段进变量:
#!/usr/bin/env bash
total=$(dsq orders.ndjson "SELECT SUM(qty*price) AS total FROM {}" | jq -r ".[0].total")
if [ $? -eq 0 ]; then
echo "GMV: $total"
else
echo "query failed" >&2
fi
执行实测:
$ bash report.sh
GMV: 381.3
$ echo $?
0
要点:dsq 输出整体是合法 JSON 数组,jq -r ".[0].列名" 即可取标量进变量。
9.3 退出码语义(实测矩阵)
| 场景 | 实测退出码 |
|---|---|
| 查询成功 | 0 |
SQL 语法错误(SELCT 1) | 1 |
表引用越界({5}) | 1 |
| 文件不存在 | 1 |
| 未知扩展名 | 1 |
JSON 模式 >> 追加重定向 | 1 |
REPL 中 .exit | 1 |
dsq 只用 0/1 两档,脚本判错需结合 stderr 文本。
十、常用命令速查
| 任务 | 命令形态 |
|---|---|
| 查单文件 | dsq users.csv "SELECT * FROM {}" |
| 人读表格 | 前缀 --pretty 或用 -i |
| 管道输入 | cat access.log | dsq -s text/nginxaccess "SELECT * FROM {}" |
| 多文件 JOIN | dsq a.csv b.json "select {0}.name, {1}.age from {0} join {1} on {0}.id = {1}.id" |
| 格式转 JSON | dsq 文件(省查询) |
| 嵌套数组 | dsq api-results.json 'SELECT * FROM {"data.data"}' |
| 嵌套标量 | SELECT "location.city" FROM {} |
| 数组元素 | SELECT field1->0 FROM {} |
| 复杂 SQL | dsq scores.csv -f query.sql |
| 探列名类型 | dsq --schema 文件 |
| 大文件反复查 | dsq 文件 --cache "SELECT COUNT(1) FROM {}"(缓存库在 /tmp) |
| 取标量进变量 | dsq 文件 "聚合查询" | jq -r ".[0].列名" |
十一、常见问题(实测排障实录)
- JSON 输出
>>追加失败:write /dev/stdout: copy_file_range: bad file descriptor(exit=1),v0.23.0 真实缺陷;追加一律dsq users.csv | cat >> out2.json经cat中转,覆盖>与管道正常(复现与验证见《配置参数详解》第六章)。 - REPL 二次进入报
table "t_0" already exists:既有缓存库被重复建表;rm -f /tmp/dsq-cache-*.db后重进(见《配置参数详解》3.6)。 no such column:列名猜错或 CSV 数值列被字符串化;先--schema看真实列,再按需-n/CAST。Unknown mimetype for file: unknown.xyz.:扩展名不在支持清单;管道 +-s显式声明类型。open missing.csv: no such file or directory:路径不存在,dsq 原样透传系统错误。
十二、参考链接
- dsq 源码仓库与 README(Usage 全例):https://github.com/multiprocessio/dsq
- go-sqlite3-stdlib 扩展函数清单:https://github.com/multiprocessio/go-sqlite3-stdlib
- SQLite 表达式与窗口函数语法:https://www.sqlite.org/lang.html
- jq 手册:https://jqlang.github.io/jq/manual/