シェルの結果をJSONで出力 - awkを使いこなしたい -

こんにちは!インフラの奥村です!

インフラエンジニア歴1年半になりました。
日々の業務にも慣れだし、「まぁまぁこなせてるんじゃないか?」と思ってしまうまでになりました。

とは思いつつも
いざ、障害、トラブルなどが発生したときに迅速に対処できるかと言われるとまだまだです。
入社当初から先輩社員に言われている

「まずログを見よう」

「まずログを見る」という行為 はしているものの、必要なログを抜き出す能力が明らかに足りていません。
ワンライナーのシェルコマンドを実行し、必要な情報を抜き出しているエンジニア見るたびに「すげぇ」としか思えていません。

そこで!そういった能力を身につけるために「awk」の勉強をはじめました!

今回は勉強の一環として「awk」を使ったシェルスクリプトを作成しました。

スクリプト

標準入力から値を受け取りそれをJSON形式で出力するスクリプトです。

  • loj.sh
#!/bin/sh
usage() {
  echo 'outjson.sh [-k] key colum number [-c] "1,colum name 2,columm name" [-e] Except from top 0..*  [-s] <,> <:> default Value is space'
}
EXCEPT=1
while getopts k:c:e:s: OPT
do
  case $OPT in
    k ) KEY=$OPTARG ;;
    c ) COLUMM_TEXT=$OPTARG ;;
    e ) EXCEPT=$OPTARG ;;
    s ) SEP="-F ${OPTARG}" ;;
    * ) usage
          exit 1 ;;
  esac
done

if [ -p /dev/stdin ] ; then
    a=$(cat -)
    echo "$a" |
    tail -n +$EXCEPT |
    awk $SEP -v key=${KEY} -v colum_text="${COLUMM_TEXT}" ' \
    BEGIN {
      split(colum_text, colum, " ")
      for (i in colum ) {
         split(colum[i], sp_colum, ",")
         colums_hash[sp_colum[1]]=sp_colum[2]
      }
      print "{"
    }
    {
       printf("\"%s\": {",$key)
       for(i in colums_hash ) {
         printf("\"%s\": \"%s\",",colums_hash[i], $i);
       }
       printf( "},")
    }
    END{
       print "}"
    }' |
    sed -e 's/,}/}/g'

else
    echo "need STDIN"
    usage
fi

説明

全プロセスからsystemdのプロセスを探す例で説明します。

パイプで値を渡し、必要な引数を与えます

ps aux | grep systemd | ./loj.sh -k 2 -c "2,PID 11,CMD" -e 2
  • -k
    • JSONのキーにしたいもの。標準出力されたものの列番号を入れる(数字)-k 2
  • -c
    • JSONの値として入れたいもの。標準出力されたものの列番号とキー名をダブルクウォートで囲って入れる。 -c "2,PID 11,CMD"
  • -e
    • 一番最初の行から無視する行数を入れる。 -e 2

として出力された結果がこちらです。

{
"319": {"CMD": "/lib/systemd/systemd-udevd","PID": "319"},"807": {"CMD": "/usr/bin/dbus-daemon","PID": "807"},"853": {"CMD": "/lib/systemd/systemd-logind","PID": "853"},"13703": {"CMD": "/lib/systemd/systemd","PID": "13703"},"22499": {"CMD": "grep","PID": "22499"}}

これをjqに渡してるみると

{
  "319": {
    "CMD": "/lib/systemd/systemd-udevd",
    "PID": "319"
  },
  "807": {
    "CMD": "/usr/bin/dbus-daemon",
    "PID": "807"
  },
  "853": {
    "CMD": "/lib/systemd/systemd-logind",
    "PID": "853"
  },
  "13703": {
    "CMD": "/lib/systemd/systemd",
    "PID": "13703"
  },
  "22489": {
    "CMD": "grep",
    "PID": "22489"
  }
}

こんな感じで出力されます。

lsのパターン

  • 通常の出力
$ ls -la
合計 8
drwxrwxr-x 2 vagrant vagrant 4096 1116 17:35 2017 .
drwxrwxr-x 3 vagrant vagrant 4096 1116 17:35 2017 ..
-rw-rw-r-- 1 vagrant vagrant    0 1116 17:35 2017 file1
-rw-rw-r-- 1 okumura okumura    0 1116 17:35 2017 file2
-rwx------ 1 vagrant vagrant    0 1116 17:35 2017 file3
ls -la | loj -k 10 -c "1,PERMISSION 3,USER 4,GROUP 5,SIZE 6,MONTH 7,DAY 8,TIME, 9,YEAR" -e 4 | jq

出力結果

{
  "file1": {
    "GROUP": "vagrant",
    "SIZE": "63",
    "MONTH": "11月",
    "DAY": "16",
    "TIME": "17:41",
    "YEAR": "2017",
    "PERMISSION": "-rw-rw-r--",
    "USER": "vagrant"
  },
  "file2": {
    "GROUP": "okumura",
    "SIZE": "0",
    "MONTH": "11月",
    "DAY": "16",
    "TIME": "17:35",
    "YEAR": "2017",
    "PERMISSION": "-rw-rw-r--",
    "USER": "okumura"
  },
  "file3": {
    "GROUP": "vagrant",
    "SIZE": "223",
    "MONTH": "11月",
    "DAY": "16",
    "TIME": "17:42",
    "YEAR": "2017",
    "PERMISSION": "-rwx------",
    "USER": "vagrant"
  }
}

行と列がある出力ならJSON形式で出力でます!

まとめ

このスクリプト作成を通して「awk」、「シェル」の奥深さを知りました。

加えて、自分が入り口にすら立っていなかったことを思い知らされました。

今後も日々鍛錬を重ね、自分の頭とシェルが繋がっているかのようなスピードでワンライナーを生み出す

一流のシェル芸人になりたいです。