TextQL 2.0.3 配置参数详解

TextQL 没有任何配置文件与持久化配置体系,全部行为由 13 个命令行参数与内置行为规则(表名映射、NUMERIC 类型推断、短 SQL、多语句拆分)决定,本文以源码与全历史检索为证,逐参数详解输入、SQL、输出、持久化四组配置与实测行为

TextQLTextQLSQLCSVCLI工具2026/11/03

TextQL 2.0.3 配置参数详解

目标:把 TextQL 的全部可配置面——13 个命令行参数、4 条内置行为规则(表名映射、类型推断、短 SQL、多语句拆分)与 1 个可选外部扩展——逐项讲清默认值、实测效果与坑。特别说明:TextQL 不存在配置文件,本文第二章给出源码级与全 Git 历史级论证。以下所有命令与输出均已在 Ubuntu 22.04(x86_64)+ TextQL 2.0.3(master 源码构建)环境实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(jammy),内核 7.0.0-38-generic,架构 x86_64
  • TextQL 版本:2.0.3(master 提交 e6545d5 构建,-ldflags "-X main.VERSION=2.0.3 -s" 注入版本串,故含 -sqlfile;2015 年的 2.0.3 release 源码无此参数)
  • 安装位置:/usr/local/bin/textql(4,408,672 字节)
$ textql -version
2.0.3
  • 测试数据:/root/textql-lab 下 sales.csv(带表头 10 行)、nohdr.csv(无表头)、nginx.tsv(制表符)、semi.csv(分号)、regions.csv、multi/ 目录、query.sql(数据内容见《TextQL 2.0.3 常用 CLI 命令与工具详解》第三章)

二、配置模型:无配置文件,参数即配置

很多同类工具(如 csvq 的 csvq_env.json)有配置文件分层,TextQL 没有——"配置"一词在它这里只等于命令行参数 + 内置行为规则。三点源码级证据:

  1. 入口代码无配置文件逻辑:textql/main.go 的 commandLineOptions 结构体集中定义全部 13 个 flag,newCommandLineOptions() 直接 flag.Parse(),全文没有任何路径拼接、HOME 查找或 JSON/YAML 解析代码;
  2. 全 Git 历史检索为空:在源码仓库执行 git log --all -S "config.json" 无任何提交命中——"读取 .textql/config.json 配置"这一特性从未在本项目任何分支存在过,网上部分笔记属于以讹传讹;
  3. man 手册佐证:man/textql.1.ronn 的 FILES 节只定义"结构化文本输入文件",OPTIONS 节与 flag.PrintDefaults() 输出一致,无配置文件条目。

💡 技术拓展:CLI 工具的配置来源常见四种形态——

形态例子TextQL 是否具备
命令行 flagtextql -sql ...✅ 唯一形态
环境变量LS_COLORS、GOPROXY❌ 不读任何环境变量(仅 exec.LookPath 用 PATH 找 sqlite3)
用户级配置文件~/.gitconfig、csvq_env.json❌ 无
可选外部组件nginx 动态模块✅ 仅一项:libgo-sqlite3-extension-functions.so 自动加载(见 5.5 节)

因此使用 TextQL 的正确心智是:把参数组合当配置管理——用 shell 函数/别名固化常用组合(见第六章模板),而不是找配置文件。

三、参数查看方式

无参运行即打印全部参数与默认值(这是唯一"查看当前可用参数"的手段,TextQL 没有 --show-config 类命令):

$ textql
Usage of textql:

  textql [-console] [-save-to path path] [-output-file path] [-output-dlm delimter] [-output-header] [-pretty] [-quiet] [-header] [-dlm delimter] [-sql sql_statements] [path ...] 

  -console
    	After all statements are run, open SQLite3 REPL with this data
  -dlm string
    	Input delimiter character between fields -dlm=tab for tab, -dlm=0x## to specify a character code in hex (default ",")
  -header
    	Treat input files as having the first row as a header row
  -output-dlm string
    	Output delimiter character between fields -output-dlm=tab for tab, -dlm=0x## to specify a character code in hex (default ",")
  -output-file string
    	Filename to write output to, if empty no output is written (default "stdout")
  -output-header
    	Display column names in output
  -pretty
    	Output pretty formatting
  -quiet
    	Surpress logging
  -save-to string
    	SQLite3 db is left on disk at this file
  -sql string
    	SQL Statement(s) to run on the data
  -sqlfile string
    	SQL filepath to run on the data
  -version
    	Print version and exit
2026-11-03 04:49:28 0xc0000120f0

⚠️ 注意最后一行:Usage 打印后程序以 log.Fatalln(err) 退出,而传入的是 *error 指针,于是把内存地址 0xc0000120f0 打了出来——这是未修复缺陷(停更项目,无补丁),不影响功能但脚本解析 stderr 时要容忍这行。

四、参数总览

参数类型默认值作用备注
-sqlstring""要执行的 SQL 语句(可多条,分号分隔)与 -sqlfile 二选一,-sql 优先
-sqlfilestring""从文件读取 SQL2023-10 加入 master;2015 release 无此参数
-dlmstring,输入分隔符支持 -dlm=tab、-dlm=0x3b(十六进制码点)
-headerboolfalse首行作列名不加则列名为 c0..cN 且首行成为数据
-output-headerboolfalse输出显示列名只影响 stdout/文件,不影响 -save-to 库内列名(见 5.4)
-output-dlmstring,输出分隔符同样支持 tab/0x##
-output-filestringstdout结果写文件传空串 "" 表示不输出任何结果(源码判空)
-prettyboolfalse框线表输出生效时忽略 -output-dlm
-save-tostring""把内存库落盘为 SQLite 文件产物可被 sqlite3 直接打开
-consoleboolfalse结束后 fork 系统 sqlite3 REPLPATH 需有 sqlite3;拒绝管道输入
-quietboolfalse抑制日志输出只关 log 输出,Usage 打印不受影响
-versionboolfalse打印版本并退出版本串靠构建时 ldflags 注入,go install 产物打印空行
位置参数 path ...——输入文件/目录,任意多个名字:文件 可覆写表名;目录非递归

五、核心参数分类详解

5.1 输入读取组:-dlm / -header / 表名规则

-dlm 决定字段切分字符。除默认逗号外,tab 与 0x## 十六进制写法均实测可用:

$ textql -sql "select * from nginx" -header -dlm tab nginx.tsv
10.0.0.5,GET,/health,200
10.0.0.6,POST,/api/order,201
10.0.0.5,GET,/index.html,404

$ textql -sql "select * from semi" -header -dlm 0x3b semi.csv
Tom,28,SH
Amy,31,BJ

-header 决定列名来源:加上则首行作列名(数据 10 行);不加则列名自动编为 c0..c5、首行也作为数据(所以 count(*) 会多 1):

$ textql -sql "select * from nohdr" nohdr.csv
1,apple,3.5
2,banana,7.25
3,cherry,12

表名规则(内置行为,无参数可调):

  • 文件基名即表名:sales.csv → sales(扩展名剥离);
  • stdin 管道载入的表名固定为 stdin:
$ cat sales.csv | textql -sql "select region, round(sum(amount),2) from stdin group by region" -header
east,328.6
north,381.25
west,620.74
  • 名字:文件 语法覆写表名(README v2 曾声称移除该能力,实测 master 仍支持):
$ textql -sql "select count(*) from orders" "orders:sales.csv"
11
  • 非法表名直接拒绝:
$ textql -sql "select 1" "bad[]name:sales.csv"
2026-11-03 04:50:53 Invalid table name bad[]name
  • 目录参数非递归展开,目录下每个文件各建一表:
$ textql -sql "select count(*) from t1" multi
11
$ textql -sql "select count(*) from t2" multi
3

类型推断规则(内置行为):所有列一律建为 NUMERIC,由 SQLite 亲和性逐值推断,同一列可混返 real/text/integer:

$ textql -sql "select typeof(amount) amt_t, typeof(date) date_t, typeof(id) id_t from sales limit 1" -header sales.csv
real,text,integer

5.2 SQL 行为组:-sql / -sqlfile / 短 SQL / 多语句

-sql 与 -sqlfile 互斥优先级:源码 GetStatements() 中 -sql 非空即用 -sql,-sqlfile 被忽略;两者都为空时打印 Usage 后输出指针退出(见第三章 ⚠️)。-sqlfile 指向不存在的文件同样只输出指针(0xc000099f40)而非错误信息,属同源缺陷。

短 SQL 语法(内置行为):单表场景可省略 FROM 表,简单查询可省略 SELECT:

$ textql -sql "*" -header sales.csv | head -4
1,2026-01-03,east,WidgetA,120.5,3
2,2026-01-04,east,WidgetB,89.9,1
3,2026-01-05,west,WidgetA,210,5
4,2026-02-01,north,WidgetC,55.25,2

$ textql -sql "count(*)" sales.csv
11

无 FROM 的语句自动注入首表:select 'x' 不带 FROM 时会对首表逐行执行(10 行数据出 10 个 x):

$ textql -sql "select 'x' as m" -header sales.csv | head -4
x
x
x
x

多语句拆分:按分号拆分顺序执行;handleSemiColon() 对引号不配对的分号做合并保护,因此字符串里的分号不会被误拆:

$ textql -sql "select 'a;b' as s; select count(*) as n from sales" sales.csv
a;b
a;b
...(每行数据一次,共 11 行)
11

(输出节选(实测首尾):a;b 重复 11 次后接第二条语句结果 11。)

⚠️ 最危险的坑(实测复现):-sql 的语句以"分号 + 换行"结尾(如 shell 里 -sql "select count(*) from sales; " 后带换行符)会触发拆分逻辑死循环,程序无限刷输出——实测 8 秒写出 73 MB 后靠 timeout 杀掉。规避:SQL 结尾不要带分号,或确保分号后没有换行;用 -sqlfile 时文件末尾留空行反而安全(文件内容结尾无换行分号组合)。

单引号奇偶校验:整条 SQL 的单引号个数为奇数时启动即拒:

$ textql -sql "select 'unbalanced from sales" sales.csv
2026-11-03 04:49:28 String contains odd number of "'(Single Quotes)"

5.3 输出组:-output-header / -output-dlm / -output-file / -pretty

-output-header 与 -output-dlm 控制结果行的列名与分隔符(与输入的 -header/-dlm 完全独立):

$ textql -sql "select product, sum(qty) q from sales group by product" -header -output-header -output-dlm tab sales.csv
product	q
WidgetA	14
WidgetB	11
WidgetC	 6

-pretty 切换为 tablewriter 框线表,此时 -output-dlm 被忽略(源码二选一分支):

$ textql -sql "select product, sum(qty) q from sales group by product" -header -pretty sales.csv
+---------+----+
| WidgetA | 14 |
| WidgetB | 11 |
| WidgetC |  6 |
+---------+----+

-output-file 把结果写文件(-pretty 的框线表同样可落盘):

$ textql -sql "select region, round(sum(amount),2) total from sales group by region" -header -output-file out_region.csv sales.csv
$ cat out_region.csv
east,328.6
north,381.25
west,620.74

⚠️ 注意:-output-file 的默认值是字符串 stdout(映射到标准输出),而不是空;源码以 != "" 判断是否创建输出器,所以显式传 -output-file "" 会静默丢弃全部结果——想"只落库不出结果"时这是合法技巧,想输出时千万别传空串。

5.4 持久化与交互组:-save-to / -console / -quiet / -version

-save-to 把内存库落盘为标准 SQLite 文件,列名沿用 -header 规则、列类型全 NUMERIC:

$ textql -sql "select count(*) from sales" -header -save-to salesdb2.sqlite sales.csv
10
$ sqlite3 salesdb2.sqlite ".schema sales"
CREATE TABLE [sales] ([id] NUMERIC, [date] NUMERIC, [region] NUMERIC, [product] NUMERIC, [amount] NUMERIC, [qty] NUMERIC);
$ sqlite3 salesdb2.sqlite "select region, round(sum(amount),2) from sales group by region order by 2 desc;"
west|620.74
north|381.25
east|328.6

⚠️ 注意:忘带 -header 时落盘的表列名是 c0..c5,后续用 sqlite3 按业务列名查询会报 no such column: amount(实测)。要落盘就务必带 -header。

-console 在所有语句执行完后 fork 系统 sqlite3 打开数据:未给 -save-to 时写临时库文件(退出即删),给了则直接打开该库;-output-header 会透传为 sqlite3 的 -header。两个硬性前置:PATH 必须有 sqlite3(否则报 Console requested but unable to locate \sqlite3` program on $PATH`),且拒绝管道输入:

$ cat sales.csv | textql -console -sql "select 1"
2026-11-03 04:49:28 Can not open console with pipe input, read a file instead

交互会话实测(文件输入 + REPL 续查)见《TextQL 2.0.3 常用 CLI 命令与工具详解》第六章。

-quiet 把 log 输出重定向到丢弃(进度/错误日志消失,结果输出不受影响):

$ textql -sql "select count(*) from sales" -quiet sales.csv
11

-version 打印构建时注入的 VERSION。该值不是代码常量:go install 产物未注入,打印空行(实测);只有 make/手工 ldflags 构建才有值。

5.5 函数扩展配置:内置 regexp 与可选扩展库

  • 内置 regexp 函数:无需任何配置,WHERE 列 regexp '模式' 直接可用:
$ textql -sql "select id, product from sales where product regexp 'Widget[AB]' and qty > 2" -header sales.csv
1,WidgetA
3,WidgetA
5,WidgetB
7,WidgetA
10,WidgetB
  • 可选扩展函数库:安装 dinedal/go-sqlite3-extension-functions 生成 libgo-sqlite3-extension-functions.so 后,TextQL 启动时自动加载,提供 Math(acos/sqrt/power/…)、String(replicate/reverse/padc/…)、Aggregate(stdev/variance/mode/median/lower_quartile/upper_quartile)函数。本文实验环境未安装该库,stdev 等函数实测不可用;函数清单照抄官方 README。

💡 技术拓展:平面文件 SQL 工具的函数扩展路线有三类——SQLite 系(TextQL/trdsql 可加载 .so 扩展或换后端)、自研引擎系(csvq 用 DECLARE FUNCTION 在会话内定义)、内建全家桶系(dsq 走 Calcite/Postgres 方言自带函数)。TextQL 选的是最省事的"自动探测 .so"路线:零参数、零配置,装了就有。

六、常用参数组合模板

按场景把参数组合固化成 shell 别名/函数即可当"配置文件"用(示例放 ~/.bashrc,root 账号):

场景参数组合
即席查 CSVtextql -header -sql "..." data.csv
TSV 日志textql -header -dlm tab -sql "..." access.tsv
管道统计... | textql -header -sql "select ... from stdin"
报表导出 CSVtextql -header -output-header -output-file rpt.csv -sql "..." data.csv
管道下游友好追加 -output-dlm tab 导出 TSV
人读结果追加 -pretty
落 SQLite 续用textql -header -save-to x.sqlite -sql "..." data.csv(务必带 -header)
进 REPL 探索textql -header -console -sql "select 1" data.csv(需 sqlite3)
脚本静默追加 -quiet

七、常见问题

7.1 有没有 .textql/config.json 或环境变量配置?

没有。第二章已用源码 + git log --all -S "config.json" 全历史检索证明该特性从未存在;TextQL 也不读任何业务环境变量。所有配置面就是第四章的 13 个参数。

7.2 SQL 结尾带分号(再加换行)为什么刷屏停不下来?

触发 handleSemiColon 死循环,输出无限增长(实测 8 秒 73 MB)。立即 Ctrl+C/timeout 终止,去掉结尾分号即可。长期方案:用 -sqlfile 从文件读 SQL。

7.3 报错信息怎么是一串 0xc000045ef0?

无 -sql/-sqlfile、或 -sqlfile 文件不存在时,log.Fatalln(*error) 打印的是指针而非错误文本(未修复缺陷)。检查:① 是否给了 -sql/-sqlfile;② -sqlfile 路径是否存在。

7.4 为什么 README 里 from pipe 查不到表?

管道表名实际是 stdin(实测 no such table: pipe)。README 该示例与代码不符。

7.5 -save-to 出来的库列名是 c0..c5?

建库时没带 -header。补带后重新生成,或 sqlite3 里 ALTER TABLE ... RENAME COLUMN(SQLite 3.25+)补救。

7.6 -sqlfile 报 flag provided but not defined?

你的二进制是从 2015 年 2.0.3 release 源码构建的;-sqlfile 是 2023-10 master 新增。按《Ubuntu 22.04 安装 TextQL 2.0.3 指南》用 master 源码重新构建即可。

7.7 结果一个都不输出?

检查是否传了 -output-file ""(空串=丢弃输出,见 5.3 节 ⚠️);默认应省略该参数或给真实文件名。

八、参考链接