パスワードを忘れた? アカウント作成
13565268 journal
日記

tuneoの日記: ンなものはawkで書けや 7

日記 by tuneo

「標準入力から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が成り立たなくなったら即終了すれば、範囲外のログデータの件数が多くなっても処理時間は一定で頭打ちになるんじゃないかな。

月曜日になったら修正しようっと。

この議論は、tuneo (2938)によって テキとトモのテキ禁止として作成されたが、今となっては 新たにコメントを付けることはできません。
  • bash で書くならこうかなぁ。

    #!/bin/bash
    time_min=$(date --date="1 day ago" "+%s")
    while read line; do
        t=${line%%,*}
        [[ $t =~ ^[0-9]+$ && $t -gt $time_min ]] && echo "$line"
    done

    ruby 版が出てたので perl 版も。

    #!/usr/bin/perl
    my $time_min = `date --date="$ARGV[0]" "+%s"`;
    while (<>) {
        my ($t) = split /,/;
        print if ($t > $time_min);
    }

    $1 > time_minが成り立たなくなったら即終了

    データが大きい順でソートされている保証がなければ使えないような?元はDBか何かか。

    --
    svn-init() {
      svnadmin create .svnrepo
      svn checkout file://$PWD/.svnrepo .
    }
    • 小生も原則bash派、但し可能な限り仔プロセス起こさない。此の例ではcutはダミ。
      bashで間に合わなければ、素直にruby。

      # awkは今時半端杉で却下。perlは罠が大杉て…。
      親コメント
      • 但し可能な限り仔プロセス起こさない。此の例ではcutはダミ。

        できる限りbashの組込コマンドでやる、ってこと?
        なんで?
        そういう流派?

        個人的には、シェルスクリプトは基本外部コマンドを起動するためのものと思ってるので、それほど子プロセスを避けることにこだわりはないかな。
        まあ、[...]でなく[[...]]exprコマンドじゃなくて((...))使おうとは思うけど。

        bashで間に合わなければ、素直にruby。

        私もrubyを使うけど、自分で環境を選べない現場も多くて、rubyを使えなくてもawkなら入っているって場合は少なくないんだよね。

        もちろん、awkでは半端な場合も多い。
        今だったらrubyがダメでもpythonが入ってることも多くて、pythonも使えるようにしているけど。

        親コメント
  • by Ryo.F (3896) on 2018年04月01日 11時25分 (#3386086) 日記

    {
      print;
    }

    は不要ですね。
    単に{print $0}するだけなら省略可。
    # mawk は違ったりするかな…?

    • 後から見る人がawkわかんなかったら困りそうなので一応つけときました。

      なにぶん弊社はシェルスクリプトでループするだけでこいつスゲェ!と言ってもらえるイット業界の底辺ですので、後輩がまともな人間であることを期待するほど私は楽天的ではありませんw

      親コメント
      • by Ryo.F (3896) on 2018年04月01日 16時41分 (#3386198) 日記

        弊社はそれなりに業界上位なIT屋ですが、うちの部門にはろくにプログラムが書けない奴多数…
        さすがにシェルスクリプトのループもそうだけど、${FILENAME%.*}みたいな引数展開を書くとついて来れなさそう。
        man bashすれば良いだけなんだけどねぇ。

        親コメント
  • #!/bin/env ruby
    date = ARGV.shift
    time_min = Time.parse(date).to_i
    while line = gets
        print(line) if line.split(',')[0].to_i > time_min
    end
typodupeerror

人生の大半の問題はスルー力で解決する -- スルー力研究専門家

読み込み中...