tuneoの日記: ンなものはawkで書けや 7
「標準入力からCSV形式のログデータを読み込み、現在から一定時間(以下の例では1日前)遡った範囲のデータを標準出力に書き出す」という処理を、bashスクリプトで書いてる馬鹿がいた。
#!/bin/bash
time_min=$(date --date="1 day ago" "+%s")
while read line; do
t=$(echo "$line" | awk -F, '{ print $1; }')
if ! expr $t + 1; then
continue
fi
if [ "$t" -gt "$time_min" ]; then
echo "$line"
fi
done
俺ぁbashマイスターでもawkマイスターでもないが、いくらなんでも酷すぎやせんか。
最初にdateで「期間内の時刻の最小値」を求めるのはわかる。1日前の時刻を求めるなら、引用符つけて"1 day ago"よりyesterdayって書けばいいんじゃないのかなとは思うがw
なお、この「期間」が「昨日」ではなく「先月」だったり「先週」に置き換わっただけのシェルスクリプトが4つぐらいある、ってのも軽く殺意が沸いたポイントだ。修正する時は4つともかよ。酷い。
で、次はお決まりのwhileループ。こういうテキスト処理のループをbashで回すのもわりとどうかと思うんだが、問題はその中身だ。
ログデータはCSV形式で、最初のフィールドにtime_t型(というか1970/1/1からの秒数)で時刻が入っているんだが、それを取り出すためだけにawkを使うという、牛刀を以て鶏を割く愚行!cut -d, -f1とやればいいのにねぇ。
そんでもって次の行、expr $t + 1して終了ステータスを見ることで$tが整数であることを検査しているらしいのだが……grep使わんの?正規表現書けない系の人?
さらに次、冒頭で求めた時刻$time_minと$tを比較して$tの方が大きい(新しい)なら行を出力している、というカラクリだ。……道理でクソ遅いわけだ
CSVならフィールド分解はcutで事が足りるし、awkまたはcutにexpr(grep)にtest、1回ループを回るごとに3つも子プロセスを作っている。最初にtime_min作るところ以外はawkで全部賄えるのにねぇ。つーことで書いてみた。
bashスクリプトだけど、時刻計算でdateを使ったらmawk呼ぶだけ。なお、dateの引数だけが違うスクリプトを4つ作るバカは止めて、ログデータを書き出す期間をスクリプトに与える引数で指定することにしたのでスクリプトのファイルも1つにまとまってめでたい。
#!/bin/bash
mawk -v time_min=$(date --date="$1" "+%s") -F, '
$1 ~ /[0-9]+/ && $1 > time_min {
print;
}'
処理時間を測ってみたら、ログデータが数件しかない状態でも俺バージョンは1.5倍ほど高速になっていた。
なお、現行バージョンは範囲外の時間のログデータもSTDINから全部読んでるわけで……$1 > time_minが成り立たなくなったら即終了すれば、範囲外のログデータの件数が多くなっても処理時間は一定で頭打ちになるんじゃないかな。
月曜日になったら修正しようっと。
Bash だって、いいじゃない (スコア:2)
bash で書くならこうかなぁ。
ruby 版が出てたので perl 版も。
$1 > time_minが成り立たなくなったら即終了
データが大きい順でソートされている保証がなければ使えないような?元はDBか何かか。
svn-init() {
svnadmin create .svnrepo
svn checkout file://$PWD/.svnrepo .
}
Re:Bash だって、いいじゃない (スコア:2)
bashで間に合わなければ、素直にruby。
# awkは今時半端杉で却下。perlは罠が大杉て…。
Re:Bash だって、いいじゃない (スコア:1)
但し可能な限り仔プロセス起こさない。此の例ではcutはダミ。
できる限りbashの組込コマンドでやる、ってこと?
なんで?
そういう流派?
個人的には、シェルスクリプトは基本外部コマンドを起動するためのものと思ってるので、それほど子プロセスを避けることにこだわりはないかな。
まあ、[...]でなく[[...]]、exprコマンドじゃなくて((...))使おうとは思うけど。
bashで間に合わなければ、素直にruby。
私もrubyを使うけど、自分で環境を選べない現場も多くて、rubyを使えなくてもawkなら入っているって場合は少なくないんだよね。
もちろん、awkでは半端な場合も多い。
今だったらrubyがダメでもpythonが入ってることも多くて、pythonも使えるようにしているけど。
ちなみに、 (スコア:1)
は不要ですね。
単に{print $0}するだけなら省略可。
# mawk は違ったりするかな…?
Re:ちなみに、 (スコア:1)
後から見る人がawkわかんなかったら困りそうなので一応つけときました。
なにぶん弊社はシェルスクリプトでループするだけでこいつスゲェ!と言ってもらえるイット業界の底辺ですので、後輩がまともな人間であることを期待するほど私は楽天的ではありませんw
Re:ちなみに、 (スコア:1)
弊社はそれなりに業界上位なIT屋ですが、うちの部門にはろくにプログラムが書けない奴多数…
さすがにシェルスクリプトのループもそうだけど、${FILENAME%.*}みたいな引数展開を書くとついて来れなさそう。
man bashすれば良いだけなんだけどねぇ。
Ruby で書いてみるてすと (スコア:1)
date = ARGV.shift
time_min = Time.parse(date).to_i
while line = gets
print(line) if line.split(',')[0].to_i > time_min
end