q - 直接在CSV或TSV文件上运行SQL



一、概述

q 是一个可以运行在 CSV / TSV 文件(或其他表格式的文本文件)上运行类SQL命令的命令行工具。

q 将普通文本(如上述)作为数据库表,且支持所有的SQL语法如:WHERE、GROUP BY、各种JOIN等。此外,还拥有自动识别列名和列类型及广泛支持多种编码的特性。

q "SELECT COUNT(*) FROM ./clicks_file.csv WHERE c3 > 32.3"
ps -ef | q -H "SELECT UID,COUNT(*) cnt FROM - GROUP BY UID ORDER BY cnt DESC LIMIT 3"

查看示例安装体验.

完全支持所有的字符编码 すべての文字エンコーディングを完全にサポート
모든 문자 인코딩이 완벽하게 지원됩니다 все кодировки символов полностью поддерживаются

非英语用户: q 完全支持所有类型的字符编码。 使用 -e data-encoding 设置输入编码; 使用 -Q query-encoding 设置查询编码; 使用 -E output-encoding 设置输出编码; 如上三个参数均设有合理的默认值。

如果遇到问题请与我联系,期待与你交流。


含有BOM的文件: python的csv模块并不能很好的支持含有Byte Order Mark 的文件。针对该种情况,使用 -e utf-8-sig 命令参数可读取包含BOM的UTF8编码文件。

我们计划将BOM相关处理与编码’解耦’, 这样就可以支持所有编码的BOM文件了。


二、安装

格式 说明 备注
OSX 运行 brew install q 该方式暂不支持MAN手册, 可以使用 q --help 查看帮助
RPM Package 运行 rpm -ivh <package-filename> 如果安装过旧版则运行 rpm -U <package-filename> 该方式支持MAN手册,可运行man q查看
DEB Package 运行 sudo dpkg -i <package-filename> 该方式支持MAN手册,可运行man q查看
Windows Installer 运行安装可执行文件,一直点击下一步、下一步… q.exe 将被添加至PATH,以便于随处运行 PATH更新后并不会即时生效,重新打开cmd命令窗口便可
tar.gz 最新稳定版的所有源码文件。提示,q.py 文件不能直接使用,因为它需要python依赖
zip 最新稳定版的所有源码文件。提示,q.py 文件不能直接使用,因为它需要python依赖

旧版本可以在这儿下载 。按理说不会有人愿意用旧版本,要是你计划使用旧版,希望能与你交流。


须知

2.0.9版本开始,不需要任何外部依赖。Python(3.7)和其他所需的库包含在了安装文件中且与系统隔离。


三、使用

q <flags> "<query>"

  最简单的执行语句:q "SELECT * FROM myfile" 该语句会输出文件内容 

q 支持在表格式的文本上执行类SQL命令。它的初衷是为Linux命令行附加SQL的表达力且实现对文本数据的轻松访问。

类SQL的查询将文件名(或标准输入流)看作表名。查询语句会作为命令输入的一个参数(使用引号包裹),如果将多个文件看作一张表,可以这样写 文件名1+文件名2....或者使用通配符(比如:my_files*.csv)。

使用 -H 表示输入内容中包含表头。该情况下列名会被自动识别,如果没有指定该参数,列名将会被以cX命名,X从1开始(比如: q "SELECT c3,c8 from ...") 。

使用 -d 声明输入的分隔符。

列类型可由工具自动识别,无需强制转换。 提示,使用--as-text 可以强制将所有列类型转换为文本类型。

依据sqlite规范,如果列名中含有空格,需要使用反引号 (即:`) 引起来。

完全支持查询/输入/输出的编码设置(q 力争提供一种开箱即用的方法), 可以分别使用-Q,-e-E来指定编码设置类型。

支持所有的sqlite3 SQL方法,包括文件之间的 JOIN(可以为文件设置别名)操作。在下面的限制小节可以看到一些少有使用的、欠支持的说明。


查询

q 的每一个参数都是由双引号包裹的一条完整的SQL语句。所有的查询语句会依次执行,最终结果以标准输出流形式输出。 提示,在同一命令行中执行多条查询语句时,仅在执行第一条查询语句时需要耗时载入数据,其他查询语句即时执行。

支持所有标准SQL语法,条件(WHERE 和 HAVING)、GROUP BY、ORDER BY等。

在WHERE条件查询中,支持JOIN操作和子查询,但在FROM子句中并不支持。JOIN操作时,可以为文件起别名。

SQL语法同sqlite的语法,详情见 http://www.sqlite.org/lang.html 或上网找一些示例。


注意

  • 支持所有类型的自动识别,无需强制转换或其他操作。
  • 如果重命名输出列,则需要为列指定别名并使用 -O 声明。
    如: q -O -H "select count(*) cnt,sum(*) as mysum from -" 便会将cntmysum作为列名输出。

四、指令

使用:

q 支持在表格式的文本数据上执行类SQL查询。

它的初衷是为Linux命令行附加SQL的表达力且实现对文本数据的轻松访问。

基本操作是 q “SQL查询语句” 表名便是文件名(使用 - 从标注输入中读取数据)。若输入内容包含表头时,可以使用 -H 指定列名。若无表头,则列将会自动命名为 c1…cN。

列类型可被自动识别。可以使用 -A 命令查看每列的名称及其类型。

可以使用 -d (或 -t) 指定分隔符,使用 -D 指定输出分割符。

支持所有的sqlite3 SQL方法。

示例:

例子1:ls -ltrd * | q "select c1,count(1) from - group by c1"
上例将会输出当前目录下,所有文件的权限表达式分组及每组数量。

例子2: seq 1 1000 | q "select avg(c1),sum(c1) from -"
上例将会输出1到1000的平均数与和数。

例子3: sudo find /tmp -ls | q "select c5,c6,sum(c7)/1024.0/1024 as total from - group by c5,c6 order by total desc"
上例将会输出在/tmp目录下,相同’用户+组’的文件所占用的MB磁盘空间。

更多详情见 https://github.com/harelba/q/ 或查看帮助


选项:

  • -h, --help
    显示此帮助信息并退出
  • -v, --version
    显示版本号
  • -V, --verbose 出现问题时显示调试信息
  • -S SAVE_DB_TO_DISK_FILENAME, --save-db-to-disk=SAVE_DB_TO_DISK_FILENAME
    将数据库保存为一个 sqlite 数据库文件
  • --save-db-to-disk-method=SAVE_DB_TO_DISK_METHOD
    保存数据库到磁盘的方法
    ‘standard’ 不需要任何设置
    'fast’需要手动在python的安装目录下执行pip install sqlitebck
    打包的问题解决后,'fast’即被作为默认方式

数据相关的选项:
  • -H, --skip-header
    忽略表头,在早期的版本中已修改为:仅支持用于标明列名的一行表头
  • -d DELIMITER, --delimiter=DELIMITER
    列分隔符,若无特别指定,默认为空格符
  • -p, --pipe-delimited
    作用同 -d ‘|’,为了方便和可读性提供该参数
  • -t, --tab-delimited
    作用同 -d ,这仅是一种简写,也可以在Linux命令行中使用$‘\t’
  • -e ENCODING, --encoding=ENCODING
    输入文件的编码,默认是UTF-8
  • -z, --gzipped
    压缩数据,对于从输入流读取文件非常高效 .gz 是自动压缩后文件扩展名
  • -A, --analyze-only
    简单分析:各列的数据类型
  • -m MODE, --mode=MODE
    数据解析模式: 松散, 宽松和严格。在严格模式下必须指定 -c, --column-count 参数。
  • -c COLUMN_COUNT, --column-count=COLUMN_COUNT
    当使用宽松或严格模式时,用于指定列的数量
  • -k, --keep-leading-whitespace
    保留每列前的空格。为了使其开箱即用,默认去除了列前的空格
    如果有需要,可以指定该参数
  • --disable-double-double-quoting
    禁止一对双引号的转义。默认可以使用 “” 转义双引号
    主要为了向后兼容
  • --disable-escaped-double-quoting
    禁止转义双引号
    默认可以在双引号字段中使用 " 进行转义
    主要为了向后兼容
  • --as-text
    不识别列类型(所有列被当作文本类型)
  • -w INPUT_QUOTING_MODE, --input-quoting-mode=INPUT_QUOTING_MODE
    输入内容的转义模式,可选值 all、minimal、none
    该参数稍有误导性,-W 指定输出内容的转义模式
  • -M MAX_COLUMN_LENGTH_LIMIT, --max-column-length-limit=MAX_COLUMN_LENGTH_LIMIT
    设置列的最大长度
  • -U, --with-universal-newlines
    设置通用换行符
  • - -U 参数当前仅适用于常规文件,输入流或.gz类文件暂不支持

输出相关的选项:
  • -D OUTPUT_DELIMITER, --output-delimiter=OUTPUT_DELIMITER
    输出列间的分隔符
    若未指定,则与 -d 指定的分隔符相同;若均为指定,则默认为空格符
  • -P, --pipe-delimited-output
    同 -D ‘|’ 为了方便和可读性提供该参数
  • -T, --tab-delimited-output
    同 -D 这仅是一种简写,也可以在Linux命令行中使用$‘\t’
  • -O, --output-header
    输出表头,输出的列名是由查询中指定的别名
    如: select name FirstName, value1/value2 MyCalculation from ... 即使输入时未指定表头仍可使用该参数。
  • -b, --beautify
    美化输出结果,可能较慢…
  • -f FORMATTING, --formatting=FORMATTING
    格式化输出列
    如格式X=fmt,Y=fmt等,上述中的X、Y是指第几列(如:1 表示 SELECT
    的第一列)
  • -E OUTPUT_ENCODING, --output-encoding=OUTPUT_ENCODING
    输出内容的编码,默认是 ‘none’,跟随系统或终端的编码
  • -W OUTPUT_QUOTING_MODE, --output-quoting-mode=OUTPUT_QUOTING_MODE
    输出内容的转义模式,可选值 all、minimal、none
    该参数稍有误导性,-w 指定输入内容的转义模式
  • -L, --list-user-functions
    列出所有内置函数

查询相关的参数:
  • -q QUERY_FILENAME, --query-filename=QUERY_FILENAME
    指定文件名,由文件中读取查询语句。
    该操作常与查询编码(使用 -Q)一同使用
  • -Q QUERY_ENCODING, --query-encoding=QUERY_ENCODING
    查询编码(包含查询语句的文件编码)
    实验性参数,对该参数的意见可反馈

五、示例

下述 -H 参数的例子,表示文件中含有表头时使用该参数。

-t 参数是指定文件以 tab 作为分隔符的缩写(可以使用 -d 参数指定任意分隔符)。

为了清楚起见,查询关键字均使用大写,实际上关键字(如 SELECT、WHERE等)对大小写并不敏感。

示例目录:


例1

对指定字段(点击数据中的uuid)执行 COUNT DISTINCT

q -H -t "SELECT COUNT(DISTINCT(uuid)) FROM ./clicks.csv"

输出:

229

例2

过滤数值数据、排序并限制输出数量

注意:q 将其看作数值类型并对其进行数值过滤(数值比较而不是字符串比较)

q -H -t "SELECT request_id,score FROM ./clicks.csv WHERE score > 0.7 ORDER BY score DESC LIMIT 5"

输出:

2cfab5ceca922a1a2179dc4687a3b26e    1.0
f6de737b5aa2c46a3db3208413a54d64    0.986665809568
766025d25479b95a224bd614141feee5    0.977105183282
2c09058a1b82c6dbcf9dc463e73eddd2    0.703255121794

例3

GROUP BY 简单示例

q -t -H "SELECT hashed_source_machine,count(*) FROM ./clicks.csv GROUP BY hashed_source_machine"

输出:

47d9087db433b9ba.domain.com 400000

例4

GROUP BY进阶示例 (以时间格式分组)

q -t -H "SELECT strftime('%H:%M',date_time) hour_and_minute,count(*) FROM ./clicks.csv GROUP BY hour_and_minute"

输出:

07:00   138148
07:01   140026
07:02   121826

例5

标准输入流作为输入

计算 /tmp 目录下各 user/group 的占用空间大小

sudo find /tmp -ls | q "SELECT c5,c6,sum(c7)/1024.0/1024 AS total FROM - GROUP BY c5,c6 ORDER BY total desc"

输出:

mapred hadoop   304.00390625
root   root     8.0431451797485
smith  smith    4.34389972687

例6

使用表头中列名

计算拥有进程数最多的前3位用户名及其数量

注意: 该查询中自动识别了列名

ps -ef | q -H "SELECT UID,COUNT(*) cnt FROM - GROUP BY UID ORDER BY cnt DESC LIMIT 3"

输出:

root 152
harel 119
avahi 2

例7

JOIN 两个文件

如下命令中JOIN一个ls命令输出内容文件(exampledatafile) 和一个包含group_name、email两列字段的文件(group-emails-example),每一邮件组均包含filename、email列, 为了输出简便,使用WHERE条件过滤出名为 ppp 的文件

q "SELECT myfiles.c8,emails.c2 FROM exampledatafile myfiles JOIN group-emails-example emails ON (myfiles.c4 = emails.c1) WHERE myfiles.c8 = 'ppp'"

输出:

ppp [email protected]
ppp [email protected]

可以看出 ppp 文件出现了两次,每次都匹配到了它所属的dip邮件组(如例中 dip.1@… / dip2@…),可以在 exampledatafilegroup-emails-example 文件中查看数据。

JOIN 的应用场景中也支持列名识别,在查询包含表头的文件时,只需指定 -H 参数即可。


六、声明

为了避免引用外部依赖,当前是使用由Python编写的内存数据库实现的。当前是支持 SELECT 语句及 各种JOIN ( 目前仅在 WHERE 语句中支持子查询)。 若想对数据进一步分析,可以使用 --save-db-to-disk 参数,以将结果输出为 sqlite 数据库文件,然后使用 sqlite3 语句来执行查询操作。

需要提示的是,当前并没有对数据量的大小进行检测和限制 - 也就是说,需要用户自己掌控文件大小。

请务必阅读限制小节。


七、开发


测试

源码中包含了测试用例,可以通过 test/test-all 来执行。若想要提交 PR的话,一定先确保其均执行成功。


八、限制

如下罗列了一些已知的限制,若你的使用场景中需要用到以下标明的限制,请联系我。

  • 不支持 FROM <subquery>
  • 不支持公用表表达式(CTE)
  • 不支持文件名中包含空格 (可以将文件以标准输入流的方式输入 q 或重命名文件)
  • 不支持较少用到的子查询

九、原理

你是否曾经盯着屏幕上的文本文件发呆,希望它要是数据库就好了,这样就可以找出自己想要的内容?我曾有过很多次,最终顿悟。我想要的不是数据库,而是 SQL。

SQL 是一种面向数据声明的语言,它允许自定义数据内容而无需关心其执行过程。这也正是SQL强大之处,因为它对于数据’所见即所得’,而不是将数据看作字节码。

本工具的目的是:在文本文件和SQL之间搭建一座桥梁。


为什么其他Linux工具不能满足需求?

传统的Linux工具库也很酷,我也经常使用它们, 但Linux的整体理念是为任一部分搭配最好的工具。本工具为传统Linux工具集新添了 SQL 族类工具,其他工具并不会失去本来优势。 事实上,我也经常将 q 和其他Linux工具搭配使用,就如同使用管道将 awk/sed 和 grep 搭配使用一样。

另外需要注意的是,许多Linux工具就将文本看作文本,而不是数据。从这个意义上来讲,可以将 q 看作提供了 SQL 功能(如:表达式、排序、分组、聚合等)的元工具。


理念

本工具的设计遵从了 Linux/Unix 的传统设计原则。若你对这些设计原则感兴趣,可以阅读 这本书 ,尤其是书中 这部分 若你认为本工具工作方式与之背道而驰,愿洗耳恭听你的建议。


十、展望

  • 主要方向:将其作为python的模块公开。 在公开之前,需要对处理标准输入流做一些内部API的完善。
  • 支持分布式以提高算力。

2025-03-30(日)