dsq 0.23.0 常用 CLI 命令与工具详解

dsq v0.23.0 命令行实战全解:文件/stdin/REPL 三种输入形态、{N} 与 {"路径"} 表引用、多文件 JOIN、11 种格式转 JSON、嵌套字段与 JSON 操作符、REGEXP/stdlib 扩展函数/窗口函数、jq 与 Shell 脚本集成、退出码矩阵,及追加重定向缺陷、REPL 热缓存报错等实测排障。

dsqdsqCLISQLCSV2026/11/03

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.0dsq --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 中 .exit1

dsq 只用 0/1 两档,脚本判错需结合 stderr 文本。

十、常用命令速查

任务命令形态
查单文件dsq users.csv "SELECT * FROM {}"
人读表格前缀 --pretty 或用 -i
管道输入cat access.log | dsq -s text/nginxaccess "SELECT * FROM {}"
多文件 JOINdsq a.csv b.json "select {0}.name, {1}.age from {0} join {1} on {0}.id = {1}.id"
格式转 JSONdsq 文件(省查询)
嵌套数组dsq api-results.json 'SELECT * FROM {"data.data"}'
嵌套标量SELECT "location.city" FROM {}
数组元素SELECT field1->0 FROM {}
复杂 SQLdsq scores.csv -f query.sql
探列名类型dsq --schema 文件
大文件反复查dsq 文件 --cache "SELECT COUNT(1) FROM {}"(缓存库在 /tmp)
取标量进变量dsq 文件 "聚合查询" | jq -r ".[0].列名"

十一、常见问题(实测排障实录)

  1. JSON 输出 >> 追加失败:write /dev/stdout: copy_file_range: bad file descriptor(exit=1),v0.23.0 真实缺陷;追加一律 dsq users.csv | cat >> out2.json 经 cat 中转,覆盖 > 与管道正常(复现与验证见《配置参数详解》第六章)。
  2. REPL 二次进入报 table "t_0" already exists:既有缓存库被重复建表;rm -f /tmp/dsq-cache-*.db 后重进(见《配置参数详解》3.6)。
  3. no such column:列名猜错或 CSV 数值列被字符串化;先 --schema 看真实列,再按需 -n/CAST。
  4. Unknown mimetype for file: unknown.xyz.:扩展名不在支持清单;管道 + -s 显式声明类型。
  5. open missing.csv: no such file or directory:路径不存在,dsq 原样透传系统错误。

十二、参考链接

dsq 0.23.0 常用 CLI 命令与工具详解 - Quzuo