UNIX/Linux環境でcsv/tsvにクエリを投げることができるqが便利すぎて生きるのが辛い

UNIX/Linux環境でSQLコマンドが使えたらいいのに、と思ったことはありませんか?私はあります。

10万〜100万行レベルのデータを集計したいけど、わざわざDBにぶっ込むほどではない。そんな時は、awk, join, grep, paste, wc, uniqなどなどの各種コマンドを駆使して集計しています。

ちなみに、joinコマンドに関してはここに大変お世話になりました。本当に便利すぎて生きるのが辛いです。

ただ、これが複数ファイルにまたがって処理をしたりして、複雑になってくるとだんだん何のデータを操っているか混乱してきます。そんなときに、カジュアルにtsv, csvファイルに対してqueryを投げられたらいいのになぁと感じていました。

そんなときに、これを見つけました。

q という超絶短いコマンドのため、単語のググラビリティ(単語のググられやすさ/ググりやすさ。)が悪すぎて、あまり発見されないかもしれません。

俺が求めていたのはコレだぁ!ということで、実務でも使っています。

もっと普及して欲しいので、インストール手順からちょっとした実行サンプルを紹介します。

久々に見てみたら、brewでインストールできるようになってました。下記インストール手順参照のこと。(2015/07/24 追記)http://harelba.github.io/q/install.html

必要環境はPython 2.5以上か、SQLite3モジュールがインストールされているPython 2.4以上です。(Python 2.5は、SQLite3が標準バンドルされています)

% mkdir -p ~/local/bin
% git clone git@github.com:harelba/q.git
Cloning into q…
remote: Reusing existing pack: 145, done.
remote: Counting objects: 16, done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 161 (delta 7), reused 8 (delta 0)
Receiving objects: 100% (161/161), 52.47 KiB, done.
Resolving deltas: 100% (60/60), done.

中身を見てみると、1行目にインタプリタの指定があります。

自分の環境は、2.4.3と2.7.1の両バージョンがインストールしてあるため、2.7.1で実行されるように変更します。

% /usr/bin/python -V
Python 2.4.3

% /usr/local/bin/python -V
Python 2.7.1

% head /home/serima/local/bin/q/q
#!/usr/local/bin/python

# Name      : q (With respect to The Q Continuum)
# Author    : Harel Ben Attia - harelba@gmail.com, harelba @ github, @harelba on twitter
# Requires  : python with sqlite3
#
#
# q allows performing SQL-like statements on tabular text data.
#
# Its purpose is to bring SQL expressive power to manipulating text data using the Linux command line.
% ~/local/bin/q/q -v
q version 1.1.2

動きました。

PATHを通します。

.zshrcに以下を追記。

export PATH=$PATH:/home/serima/local/bin/q

% source ~/.zshrc
% q
Usage:
        q allows performing SQL-like statements on tabular text data.

        Its purpose is to bring SQL expressive power to manipulating text data using the Linux command line.

        Basic usage is q "<sql like query>" where table names are just regular file names (Use - to read from standard input)
        Columns are named c1..cN and delimiter can be set using the -d (or -t) option.

        All sqlite3 SQL constructs are supported.

        Examples:

          Example 1: ls -ltrd * | q "select c1,count(1) from - group by c1"
            This example would print a count of each unique permission string in the current folder.

          Example 2: seq 1 1000 | q "select avg(c1),sum(c1) from -"
            This example would provide the average and the sum of the numbers in the range 1 to 1000

        See the help or https://github.com/harelba/q for more details.

Options:
  -h, --help            show this help message and exit
  -b, --beautify        Beautify output according to actual values. Might be
                        slow...
  -z, --gzipped         Data is gzipped. Useful for reading from stdin. For
                        files, .gz means automatic gunzipping
  -d DELIMITER, --delimiter=DELIMITER
                        Field delimiter. If none specified, then standard
                        whitespace is used as a delimiter
  -t, --tab-delimited-with-header
                        Same as -d <tab> -H 1. Just a shorthand for handling
                        standard tab delimited file with one header line at
                        the beginning of the file
  -H HEADER_SKIP, --header-skip=HEADER_SKIP
                        Skip n lines at the beginning of the data (still takes
                        those lines into account in terms of structure)
  -f FORMATTING, --formatting=FORMATTING
                        Output-level formatting, in the format X=fmt,Y=fmt
                        etc, where X,Y are output column numbers (e.g. 1 for
                        first SELECT column etc.
  -e ENCODING, --encoding=ENCODING
                        Input file encoding. Defaults to UTF-8. set to none
                        for not setting any encoding - faster, but at your own
                        risk...
  -v, --version         Print version

パスが通りました!

Examplesが載っているので、試しにそのまま入力してみます。

% seq 1 1000 | q "select avg(c1),sum(c1) from -"
500.5 500500

おー!素晴らしい。

% seq 1 100012345(略)1000

を出力して、これを標準入力として q に渡す。c1というのが、カラムの1番目という意味なので、avg(c1)で、1〜1000の総和の平均sum(c1)で、1〜1000の総和が出力されています。

こんな風にgroup byも使える!

% cat test.txt
Mike    10
John    1
Bob     8
Mary    19
Bob     15
Bob     20
Mike    2

% q "select c1, count(*), sum(c2) from test.txt group by c1"
Bob 3 43
John 1 1
Mary 1 19
Mike 2 12

ちなみに awk でやろうとするとこうですか。

% awk ’{a[$1]++;b[$1]=b[$1]+$2}END{for (i in a) printf("%s %d %dn", i, a[i], b[i])}’ test.txt
Mike 2 12
Bob 3 43
Mary 1 19
John 1 1

join も書けます。

% cat test1.txt
a1234   aa1
b1234   aa2
c1234   aa3
d1234   aa3
% cat test2.txt
1       a1234   bb1
2       a1234   bb2
3       b1234   cc1
4       c1234   dd3
5       e1234   NULL
% q "select * from test1.txt as a left join test2.txt as b on a.c1 = b.c2"
a1234 aa1        1 a1234 bb1
a1234 aa1        2 a1234 bb2
b1234 aa2        3 b1234 cc1
c1234 aa3        4 c1234 dd3
d1234 aa3

SQLに馴染みのある人はqを使ったほうがサクッと書けていいですね。

image