カテゴリー
【Awkでデータ解析のすゝめ】gawk(GNU AWK)でカスタムソートを使ってみる
※ 当ページには【広告/PR】を含む場合があります。
2020/02/08
2023/06/26
ビッグデータ解析の分野において、とりわけPythonとRが有名で、選ばれている要因の色々な解析のためのツールやライブラリなどが充実しているので、初学者にも敷居が低いことなどが選ばれている要因の一つになっていると思います。
ただし、高度な統計処理をしないのであれば、動作環境を選ばない圧倒的にAwkが使いやすいですし、かなり高速にデータを処理できます。
著者個人的には株式データや機械学習訓練用データのような時系列データを捌くのに日常的に利用しているので、もはやAwkによるデータ解析は手放せないものになっている程です。
複雑なデータ処理に欠かせないのが、データの順番を自由に並び替える
Shellでソートをする場合、とにかくsortコマンドを使うことが多いと思いますが、実はawkでも高度なソート機能が備わっているため、わざわざsortコマンドとawkコマンドを併用させて使うまでもなく目的の処理をawkだけで実装できる場合が多くあります。
今回は知っていると得をするかもしれないawkでのカスタムソートの作成方法と使い方をご紹介します。
はじめに〜mawkならgawkに移行しよう
ほとんどのLinuxディストリビューションではほぼ
しかしながら、Debian系は慣習として
なのでmawkとgawkの違いを気にせずに使っていると、手持ちの既存のawkスクリプトが他の環境に移したときに上手く動作してくれない場合があります。
この厄介なmawkとの互換性ですが、そもそもmawkは軽量で高速な動作をウリにしている代わりに、gawkと比べて高機能な関数を使えないようなプログラムです。
そしてmawkの一番の問題として
$ awk -W version
mawk 1.3.3 Nov 1996, Copyright (C) Michael D. Brennan
compiled limits:
max NF 32767
sprintf buffer 1020
ということで、mawkが標準である場合、gawkがデフォルトで使えるように設定し直しを当初から考えておいたほうがあとあと幸せになれると思います。
なお、gawkは安定してメンテナンスがされており、(既にgawk5がリリースされている段階ではありますが...)手元のgawk4は
$ gawk --version
GNU Awk 4.2.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.1.2)
Copyright (C) 1989, 1991-2018 Free Software Foundation.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
となっています。
Debian系のOSにgawkを導入する場合にはaptコマンドでほぼ一発導入することができます。
$ sudo apt-get install gawk
$ gawk --version
GNU Awk 4.2.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.1.2)
Copyright (C) 1989, 1991-2018 Free Software Foundation.
#👇aptでパッケージインストールするとデフォルトのawkも自動で置き換わる
$ awk --version
GNU Awk 4.2.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.1.2)
Copyright (C) 1989, 1991-2018 Free Software Foundation.
注意されたいのは、aptパッケージマネージャからgawkをインストールすると、awkコマンドが自動でgawkと紐付けされるようなので、mawkをどうしてもawkコマンドにしたい方はgawkのソースビルドから使う方が良いでしょう。
とにかくこれで
sorti
Macユーザーの注意点〜nawkからgawkに移行する場合
MacOSで標準となっているのは、先程説明したようなmawk同様に、軽量で簡素なAWKの拡張実装である
このnawkについては深くは言及しませんが、当然ながらモダンなgawkの機能と一部コンパチブルではないので、OS間を通じて同じシェルスクリプトを実行する際に困ってしまいます。
ということで、MacOSでもデフォルトのawkコマンドをgawkコマンドに変えておくほうが賢明といえます。
MacOSにgawkを導入するのは簡単で、
$ brew install gawk
MacOSへのgawkの導入に関しては、HomeBrewが環境変数のパスをよしなにやってくれてそのままawkコマンドに置き換わるときと、そうでないときで見解が分かれてそうな気がします。
手元の環境では、自動でawkコマンドが置き換わることはなかったので、ホームディレクトリの
$ AWK_PATH=$(which gawk)
#👇.zshrcに書き込む場合
$ echo 'PATH="'$AWK_PATH'/opt/gawk/libexec/gnubin:$PATH"' >> ~/.zshrc
$ source ~/.zshrc
で、awkコマンドがgawkに置き換わっていれば設定完了です。
awkでカスタムソート
mawkとgawkの違いの前置きが長くなりましたが、それでは本題のカスタムソートの実装法を具体的にやっていきます。 もちろんここでのawkとは
gawk
配列のカスタムソートといってもおもに2つのやり方があります。 返ってくる結果としては同じですので、どちらのやり方が適しているかはコーダーの判断になりますが、早速、配列のカスタムソートを解説していきます。
PROCINFO["sorted_in"]でカスタムソート
最初に
PROCINFO
for ... in
まずは例題として、どこかの学校の生徒ごとのテスト点数をまとめたデータ
EXAM_SCORE
1.sh
#!/bin/bash
#👇1列目がキー、2列目が値として使う
EXAM_SCORE=$(cat << EOF
Ichiro 84
Bob 25
Wakame 76
Hanako 56
Alice 90
Kabao 53
Jam 43
Tarao 25
Cheese 12
Kasuo 47
Piyoko 88
Ikura 29
EOF
)
echo "$EXAM_SCORE" | awk '
function cmp_num_val(i1, v1, i2, v2) {
if (v1 < v2) {
return -1;
} else {
return 1;
}
}
{
data[$1] = $2;
}
END {
PROCINFO["sorted_in"] = "cmp_num_val";
for (j in data) {
printf("data[%s] = %s\n", j, data[j]);
}
}
'
これを実行させますと、
$ chmod +x 1.sh
$ ./1.sh
data[Cheese] = 12
data[Tarao] = 25
data[Bob] = 25
data[Ikura] = 29
data[Jam] = 43
data[Kasuo] = 47
data[Kabao] = 53
data[Hanako] = 56
data[Wakame] = 76
data[Ichiro] = 84
data[Piyoko] = 88
data[Alice] = 90
という風にテストの点数を昇順でソートする結果が得られました。
PROCINFO["sorted_in"]
#.......
function cmp_num_val(i1, v1, i2, v2) {
if (v1 < v2) {
return -1;
} else {
return 1;
}
}
#.......
{
PROCINFO["sorted_in"] = "cmp_num_val";
for (j in data) {
printf("data[%s] = %s\n", j, data[j]);
}
}
#.......
まず、
PROCINFO["sorted_in"]
for ... in 配列名
PROCINFO["sorted_in"]
このとき
PROCINFO["sorted_in"]
関数名(i1, v1, i2, v2)
ちなみに0を返すと、その二つの要素は同じだったということになりますが、-1でもないし1でもなかった順位をつけられない状態と同じですので、0の場合は特に関数に実装してもしなくても結果は同じです。
ということで降順にソートしたい場合には
function cmp_num_val(i1, v1, i2, v2) {
if (v1 > v2) {
return -1;
} else {
return 1;
}
}
となります。
この程度の例ではカスタムソートで無くてもできますが、文字列ようなキー値を使ってもソートが可能です。 例を以下の
2.sh
#!/bin/bash
EXAM_SCORE=$(cat << EOF
Ichiro 84
Bob 25
Wakame 76
Hanako 56
Alice 90
Kabao 53
Jam 43
Tarao 25
Cheese 12
Kasuo 47
Piyoko 88
Ikura 29
EOF
)
echo "$EXAM_SCORE" | awk '
function cmp_str_ind(i1, v1, i2, v2) {
if (i1 < i2) {
return -1;
} else {
return 1;
}
}
{
data[$1] = $2;
}
END {
PROCINFO["sorted_in"] = "cmp_str_ind";
for (j in data) {
printf("data[%s] = %s\n", j, data[j]);
}
}
'
これの実行結果は、
$ chmod +x 2.sh
$ ./2.sh
data[Alice] = 90
data[Bob] = 25
data[Cheese] = 12
data[Hanako] = 56
data[Ichiro] = 84
data[Ikura] = 29
data[Jam] = 43
data[Kabao] = 53
data[Kasuo] = 47
data[Piyoko] = 88
data[Tarao] = 25
data[Wakame] = 76
となりキーがアルファベット順で昇順となっています。 なお、日本語でも比較可能ですが、ユニコードのコードポイント値で比較されているようです。
ということで、この
PROCINFO["sorted_in"]
asort/asorti関数でカスタムソート
次に
PROCINFO["sorted_in"]
値をソートする〜asort
まずは
配列の値
asort
asort関数は以下の用法でカスタムソート出来ます。
asort(ソートする元の配列, ソート結果配列, ソート処理関数)
以下のスクリプトを
3.sh
#!/bin/bash
EXAM_SCORE=$(cat << EOF
Ichiro 84
Bob 25
Wakame 76
Hanako 56
Alice 90
Kabao 53
Jam 43
Tarao 25
Cheese 12
Kasuo 47
Piyoko 88
Ikura 29
EOF
)
echo "$EXAM_SCORE" | awk '
function cmp_num_val(i1, v1, i2, v2) {
if (v1 < v2) {
return -1;
} else {
return 1;
}
}
{
data[$1] = $2;
}
END {
asort(data, sorted_data, "cmp_num_val");
for (j in sorted_data) {
printf("sorted_data[%s] = %s\n", j, sorted_data[j]);
}
}
'
これを実行しますと、
$ chmod +x 3.sh
$ ./3.sh
sorted_data[1] = 12
sorted_data[2] = 25
sorted_data[3] = 25
sorted_data[4] = 29
sorted_data[5] = 43
sorted_data[6] = 47
sorted_data[7] = 53
sorted_data[8] = 56
sorted_data[9] = 76
sorted_data[10] = 84
sorted_data[11] = 88
sorted_data[12] = 90
これは前節での
1.sh
また、ソート元の指定した配列(ここではdata[*])のキー情報はソート後の配列には反映されないので、元の配列のキーと一緒に平行処理したい場合には、
PROCINFO["sorted_in"]
キー(インデックス)をソートする〜asorti
配列のキーに作用するソート操作の関数は
asorti
asorti(対象配列, ソート結果配列, ソート方法)
これを上節の
2.sh
4.sh
#!/bin/bash
EXAM_SCORE=$(cat << EOF
Ichiro 84
Bob 25
Wakame 76
Hanako 56
Alice 90
Kabao 53
Jam 43
Tarao 25
Cheese 12
Kasuo 47
Piyoko 88
Ikura 29
EOF
)
echo "$EXAM_SCORE" | awk '
function cmp_str_ind(i1, v1, i2, v2) {
if (i1 < i2) {
return -1;
} else {
return 1;
}
}
{
data[$1] = $2;
}
END {
asorti(data, sorted_data, "cmp_str_ind");
for (j in sorted_data) {
printf("sorted_data[%s] = %s\n", j, sorted_data[j]);
}
}
'
これを実行させてどうなるかというと、
$ chmod +x 4.sh
$ ./4.sh
sorted_data[1] = Alice
sorted_data[2] = Bob
sorted_data[3] = Cheese
sorted_data[4] = Hanako
sorted_data[5] = Ichiro
sorted_data[6] = Ikura
sorted_data[7] = Jam
sorted_data[8] = Kabao
sorted_data[9] = Kasuo
sorted_data[10] = Piyoko
sorted_data[11] = Tarao
sorted_data[12] = Wakame
という風に元の配列のキー値が新たに値として並び替えられた配列として出力されています。 また、この結果として得られた配列のキーは1から始まるインデックスが割り振られていることにも注意が必要です。
先ほどのasort関数でキーの内容が反映されなかったときと同様で、asorti関数を使った場合に元配列の値(ラベル)の情報は反映されませんので、ソートした配列の要素の内容を全て残しておきたい場合には、
PROCINFO["sorted_in"]
まとめ
awk内部のソートする工程でも、今回のようなテクニックを駆使すると、かなり自由度の高い並び替えが効率良く仕込めることが分かりました。
これでデータをソートするときに逐一awkのコマンドの外に出して、カスタムソート用のsortコマンドでパイプして、再びawkにパイプする...というコマンドを繋ぐような処理もawkだけで完結するかと思います。
今回は
PROCINFO["sorted_in"]
asort/asorti関数
これらのカスタムソートの方法には若干ソートした返す結果が異なりますので、その都度目的に適した方法を選択していく必要があります。
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー