ラズベリーパイで、例えばセンサーの値を読みだして数値のデータファイルを作ったときに、そのデータを後から統計的に処理したくなることがあります。
1回きりなら、表計算ソフトで行えばよいでしょう。
しかし、たくさんのデータファイルを作って、それらに対して同じ計算処理を行う場合などには、このawkがとても便利です。
awkは非常に高機能でいろいろなことができますが、まずは基本を押さえておきましょう。
awkとは
awk(オーク)は、主にスペースやタブなどで区切られたテキストのデータファイルに対していろいろな処理を行うプログラミング言語です。
処理の内容をスクリプトに書いて、awkという名前のコマンドによりテキストデータを処理します。
GNUで拡張されたgawkというのもあり、こちらの方が多く使われているかもしれませんが、ここではオリジナルのawkでもできる範囲で書きます。
awkは高機能で、いろいろな使い方ができますが、この記事ではそのほんの触りの部分を説明します。
awkを使った計算例1:列ごとの平均値を計算する
例えばここに、ラズベリーパイに温度センサと湿度センサを取り付けて、一日の気温および湿度のデータを5分ごとに計測して書き込んだファイルがあるとしましょう。
1 2 3 4 5 6 | 00:00:00 6.1 63.5 00:05:00 5.9 62.1 00:10:00 5.7 62.4 : : 23:55:00 6.0 60.5 |
一番左の列がタイムスタンプ、2番目の列が気温、3番面の列が湿度です。
awkを使って、この1日の温度と気温の平均値を出すことを考えます。
次のawkスクリプトをその例です。
スポンサーリンク1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | BEGIN{ sumTemp=0 sumHum=0 num=0 } { sumTemp+=$2 sumHum+=$3 num++ } END{ meanSumTemp=sumTemp/num meanSumHum=sumHum/num printf("%s: %.1f %.1f\n",FILENAME, meanSumTemp, meanSumHum) } |
実行方法と実行結果例は以下です。
1 2 | $ awk -f average1.awk data_1.dat data_1.dat: 6.5 61.0 |
average1.awkが先ほどのawkスクリプトのファイル名、data_1.datが先ほどのデータファイルです。
実行結果はdata_1.datの内容によります。
では、スクリプトの簡単な説明を書きます。
awkのスクリプトでは、基本的に「パターン{アクション}」という記述を並べて書いていきます。
そして、この場合data_1.datを1行づつ読み込み、その度にパターンと照合し、一致したらアクションを実行する、という流れで処理を進めます。
パターンとアクションの組はいくつあっても構いません。複数ある場合は、上から順に1つづつパターン照合していきます。
パターンは省略可能です。単に{アクション}だけを書くと、無条件にそのアクションをその一行に対して実行します。
BEGINとENDは特別で、BEGINはファイルを読み込む前に1回だけ実行、ENDはファイルをすべて読み込んだあとに1回だけ実行します。
6行目〜10行目は、パターンの指定がなく、アクションのみが書かれていますので、data_1.datを1行読み込むたびに無条件にアクションを実行します。
スクリプトの全体の流れとしては、以下のようになります。
- BEGINのところで3つの変数sumTemp,sumHum,numを0に初期化。
- data_1.datの2カラム目(左から2列目)の値をsumTemp, data_1.datの3カラム目の値をsumHumにそれぞれ加算。さらにデータの個数を数える変数numをインクリメント
- ENDのところでsumTemp,sumHumの2つの変数の値の平均値(meanSumTemp, meanSumHum)を計算して表示
$2や$3がawkの特徴的な変数で、data_1.datを1行読み込んでアクションを起こすときに、$2や$3という変数に、その行の2カラム目や3カラム目の値を自動的に代入しておいてくれるのです。
ですので、ユーザーは$2や$3といった変数を使えば、簡単にdata_1.datの中の数値を拾えるわけです。
試しにdata_1.datを5行くらい書いてみて、試してみてください。
ところで、いろいろ試してみるとわかりますが、このスクリプトはちょっと雑で、data_1.datに不要な空行が1行でもあると、うまくいきません。
numのインクリメントを無条件にやっているので、平均値の計算を誤ってしまうのです。
ですので、それを避けたい時には、average1.awkで省略したパターンを1つ加える必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | BEGIN{ sumTemp=0 sumHum=0 num=0 } /^[0-9]+:[0-9]+:[0-9]+/{ sumTemp+=$2 sumHum+=$3 num++ } END{ meanSumTemp=sumTemp/num meanSumHum=sumHum/num printf("%s: %.1f %.1f\n",FILENAME, meanSumTemp, meanSumHum) } |
6行目だけが変わりました。average1.awkではパターンがありませんでしたが、この例ではパターンを追加しました。
これは、行の先頭に「数字:数字:数字」を含む行だけを対象にアクションを実行するという指示です。
data_1.datは、各行の先頭にタイムスタンプを入れているのでした。この正規表現によるパターンを使って、タイムスタンプが先頭に書かれている行だけを計算の対象にしているのです。
こうしておくと、data_1.datの中にコメント行や空行があっても正しく計算してくれます。
awkを使った計算例2:同じ計算をたくさんやるときの自動化
ここまでの例をみて、「これなら表計算ソフトを使った方が簡単じゃないか」と疑問に思われる方もいると思います。
確かに、平均値を1回出すだけなら、表計算ソフトにdata_1.datを貼りつけて計算させた方がわかりやすく、早いかもしれません。
しかし、このdata_1.datが100日分あるとしましょう。data_1.dat〜data_100.datです。そして、まだまだ増え続けるとしましょう。
そうなると、表計算ソフトを都度起動してデータファイルを読み込み、平均値を計算するという手作業をしなければならなくなります。
ところが、awkでスクリプトを作っておくと、それをある程度自動化できるのです。
たとえば、dataというディレクトリの中に、data_1.dat〜data_100.datというデータファイルが収められているとして、以下のシェルスクリプトを書きます。
1 2 3 4 5 | #!/bin/sh for f in data/data*.dat;do awk -f average2.awk $f done |
3行目は、dataディレクトリの下のdata*.datのファイルを一つとって、変数fに入れるという記述です。これはforループになっていて、dataディレクトリの下でdata*.datにマッチするすべてのファイルを取り尽くすまでループします。
4行目がawkの実行結果ですね。
実行すると
1 2 3 4 5 6 | $ calc_ave1.sh data/data_1.dat: 6.5 61.0 data/data_10.dat: 6.4 61.2 data/data_100.dat: 6.6 61.3 data/data_11.dat: 5.9 60.5 data/data_12.dat: 5.7 59.9 |
というように、data_1.dat~data_100.datすべてについて、平均値を計算したデータを表示してくれるはずです。
ただ、これではファイル名の並びにちょっと問題がありますね。data_1.datの次にはdata_2.datを表示してほしいところです。
対処方法はいろいろあると思いますが、ここではlsコマンドを利用する方法を使ってみましょう。さきほどのスクリプトを少し修正します。
1 2 3 4 5 6 7 8 | #!/bin/sh DIRNAME=data FLIST=`ls -v $DIRNAME/` for f in $FLIST;do awk -f average2.awk $DIRNAME/$f done |
FLISTという変数にls -vで作ったdata_1.dat~data_100.datのファイル名リストをあらかじめ作っておきます。
そうすると、
1 2 3 4 | $ calc_ave2.sh data/data_1.dat: 6.5 61.0 data/data_2.dat: 6.3 60.0 data/data_3.dat: 6.1 58.0 |
というように、データファイルのインデックスの数字順に並べてくれます。
このls -vの-vオプションは、本来はバージョン番号でファイル名をソートするためのものです。
この実行結果をファイルに書き込んでおきたければ、リダイレクトを使用して
1 | $ calc_ave2.sh > result.dat |
などとすればよいでしょう。
リダイレクトについては別の記事で説明します。