notebook

都内でWEB系エンジニアやってます。

xargsで連番ファイルを処理する

調査とかでこんな感じのファイルに対してgrepなりしたりする場合

access_log.2015-08-01.gz
access_log.2015-08-02.gz
access_log.2015-08-03.gz
access_log.2015-08-04.gz
access_log.2015-08-05.gz
access_log.2015-08-06.gz
access_log.2015-08-07.gz
access_log.2015-08-08.gz
access_log.2015-08-09.gz
access_log.2015-08-10.gz

今まではスクリプトでやっていました。

毎回スクリプトに書くのも面倒だと思ったので、割と汎用的に使えるスクリプトを作ろうと思って作ってみました。

要件としては

  • 連続した日付のログに対して同じコマンドを発行して結果を得たい

スクリプトでの実装

  • date-seq-exec
#!/bin/sh

_usage() {
cat <<_EOT_
Usage:
 $0 [-s datetime] [-e datetime] [-f format] [-c command]

Description:
  this script is handle for the file involved continuation date.

Options:
  -s start date. Required
  -e end date.   Required
  -c execute command. please single quotation. Required
    please ues '{}'
    replace date string for command.
    in command.
  -f datetime format in shell. default is '+%Y-%m-%d'

_EOT_
exit 1
}

# default date format
FORMAT='+%Y-%m-%d'
while getopts s:e:f:c: opt
do
  case ${opt} in
    s)
      set_options='y'
      STARTDATE=${OPTARG};;
    e)
      set_options='y'
      ENDDATE=${OPTARG};;
    f)
      set_options='y'
      FORMAT=${OPTARG};;
    c)
      set_options='y'
      COMMAND=${OPTARG};;
    :|\?) _usage;;
  esac
done

[ "${set_options}" != 'y' ] && _usage

CURDATE=$STARTDATE
while [ 1 ]; do
  curdate_s=`date -d "$CURDATE" '+%s'`
  enddate_s=`date -d "$ENDDATE" '+%s'`
  diff=`expr $enddate_s - $curdate_s`

  datestr=`date -d "$CURDATE" "$FORMAT"`
  ${COMMAND//\{\}/$datestr}

  if [ $diff -le 0 ]; then
    break
  fi
  CURDATE=`date -d "$CURDATE 1day" "+%Y-%m-%d"`
done
date-seq-exec -s 2015-08-01 -e 2015-08-10 -f '+%Y-%m-%d' -c 'wc -l /data/var/log/applog/access_log.{}.gz'

こんな感じで実行することができます

これで一定期間中のdatetimeに関する情報を使っているファイルに対しての処理を行う場合はあまり意識しなくてもよくなります

ワンライナーでの実装

ただ、そもそもそういう事考える人いるんじゃないか?と思いなおし調べたところやっぱりありました。。。

基準日からの連続した日付を得る

上記ページのseqとforの組み合わせ + xargs -i でやりたいことはもはやワンライナーで書けます

for i in `seq 0 9`;do date +"%F" --date "20150801 $i days";done | xargs -i wc -l /data/var/log/applog/access_log.{}.gz

xargs は-iオプションを使うことで、パイプで受け取った出力を{}で置き換えることができます

また、複数コマンドやパイプを使いたい時などは sh -cで実行するスクリプトを渡してあげればいいようです

for i in `seq 0 9`;do date +"%F" --date "20150801 $i days";done | xargs -i sh -c  'grep "hoge" /data/var/log/applog/access_log.{}.gz | grep " 500 "'

awkとかも使うことができます、これで特にツールも入れずに調査はポチして待つだけ、が実現できますね!

for i in `seq 0 9`;do date +"%F" --date "20150801 $i days";done | xargs -i sh -c  'ssh 10.0.0.1 grep "hoge" /data/var/log/applog/access_log.{}.gz | awk "{print \$14}" | awk -F"[=&]" "{sum+=\$8;}END{print \"{}\" sum \"\t\" NR}"'

はまったところ

この方法に限った事ではないですがシェルスクリプトとシェルで実行するのとで、若干違いがあってそこではまることが多々ありました

特にawkで良くはまったので残しておきます

  • 変数
awk '{print $14}'
awk '{print \$14}'
  • クォーテーション
END{print sum \t NR}
END{print sum \"\t\" NR}

awkでの変数として認識させるところとクォーテーションを認識させてあげるところですね

わかっていても時間とられることが多かったです。。。

並列実行

xargsの-Pオプションで並列実行もすれば時間短縮も図れます、無駄に長いスクリプトより便利そう

-P0で最適なプロセス数で実行してくれるようです

ローカルからxargsでssh叩く場合はちょっと気を付けたほうがいいですが、サーバ1台で完結する場合なら基本的には0でいいのかなと思います

日付以外にも

例では日付に関するファイルで色々やりましたが、サーバ一覧をとってきてそれらに対して一斉にコマンド打つとかも簡単にできますね

ansibleあるじゃんって話でもありますが、まぁコード書いて何とかするほどではないかなっていう感じのレベルならこちらの方が早いと思います

今までスクリプト書いてましたがスクリプト書く必要がなくなります

まとめ

forとseqは結構便利、そしてxargsと組み合わせるともっと便利!

まだまだこういう便利な使い方があることを知らずに無駄に行っていることが沢山あると思うのでもっと理解を深めて効率化を図りたいですね