LogをShell芸で解析するテクニック

## これは何?

最近はSIEMやSplunkがログを見てくれている気はしますが，LogをShell芸で解析する時のテクニック的なものを書きます。

---

## ログサーバ内でやりがちなミス

- logを編集可能なコマンドで開く(事故の元なので)
- catなどで雑にでかいlogファイルを開く: ターミナルに大量の出力されることでサーバに負荷がかかる可能性がある。
    ```shell
    # NG
    zcat access.log.gz
    # OK
    zcat access.log.gz | less
    zcat access.log.gz | head -n 10
    ```
---

## 役に立つコマンド

### cut

```shell
cut -b 2-4,7-10 >sample.txt　各行の2~4,7~10文字目を出力(byte単位)
cut -b 2- < sample.txt　     各行の2文字目以降を出力(byte単位)
cut -c 2-4 > sample.txt      文字数単位で切り出す。
cut -c2-12,22-               2文字目から12文字目,22文字目からそれ以降を出力
cut -d ' ' -f2               スペース区切りで2番目のみを抜き出す。cutの' 'は純粋なスペースにしか判定が出ない時がある。→trを使う。
cut -d: -f 1,2 < sample.txt　各行を区切り文字(今回は:)で区切り、1,2項目を出力
cut -f1 # 区切り文字を指定しないほうがうまくいくこともある。
```

### grep

```shell
grep -R 'hoge' /home  homeディレクトリとすべてのサブディレクトリ内で、hogeという単語を含むファイルを検索する。ファイル内部まで検索してくれる。
grep -w video      videoという単語が含まれる行のみ検索する。(vboxvideoなどは該当しない)
grep -w -2 video     videoという単語が含まれる行とその前後２行を表示
grep -B 2 video      検索結果と前方2行を表示
grep -A 2 video      検索結果と後方2行を表示
grep -n video        行番号も表示
grep -v video        videoを含まない行のみ表示
grep -x              行全体がパターンと一致するものを表示
grep -i              大文字小文字を区別しない
grep -e video -e hoge 複数の文字列を指定して検索する。videoを含む行とhogeのどちらかを含む行が検索される。
grep "video\|hoge"   複数の文字列を指定して検索する(正規表現)
grep -E video|hoge"  複数の文字列を指定して検索する(拡張正規表現)
grep -v "root root"  空白を文字列が含む場合には""で囲む
grep -G              基本正規表現を使う。
grep -l              パターンが見つかったファイル名とパスのみを表示する。
grep -n              パターンが見つかったファイル中の行数のみを表示する。
grep -E "root\s+root"拡張正規表現を使う。\sは空白,+は直前のパターンの1回以上の繰り返しを意味する。
grep log.txt -f timelist.txt 検索にファイルの中に含まれている文字列を使う。パターン(正規表現も使える)は1行に一つずつ書く。
egrep $(cat timelist.txt | tr '\n' '\|') log.txt # 上とほぼ同じことができる。改行文字をパイプに置換することで複数パターンのいずれかにマッチするか調べる。
grep video : grep hoge 両方を含む行を検索する。
grep video.＊hoge    順番が決まっている場合には正規表現も使える。

grep --color '\$\|cores\|processor\|name\|'  #全文を表示しつつ、一致した部分をハイライトする。
grep -o aiueo #一致した部分のみ表示
grep -io "pattern" hoge.txt | wc -l # 一致した部分のみを表示してカウントする。
echo 'abcde' | grep -o '.' # 文字列を1文字ずつ分離する。
a
b
c
d
e
```

### sort

```shell
sort -c　並んでいることを確認する。並んでいない場合に1を返す。
sort -m　複数のファイルを指定した場合でもそれらを合わせて並び替える。事前に個々のファイルが並んでいる必要がある。
srot -b　空白を無視する
sort -f　大文字小文字を区別しない。
sort -n　数字順で並べる。(デフォルトは1桁目のみ見る。)
sort -r　数字逆順
sort -d　電話帳順に並べる。
sort -M　比較する文字列が月の短縮形になっている時に並び替える。
sort -R　ランダムに並び替える。
ls | sort -R | head -n1 #ランダムにコマンドの結果を1つ取り出す。

sort -S　指定したメモリを使ってsortを行う。単位はb,K,M...
sort -s　同じ数値が並んだ場合最後に空白の有無などを含めて並び替えが行われるがそれを無視すること。
sort -o savefilename　保存するファイル名を選んで実行できる。リダイレクトと何か違いあるのかなあ。保存ファイル名が読み込みファイルより左に記述してコマンドを実行することに注意。
sort -t 区切り文字　区切り文字を指定して並べ替える。
sort -k 数字　-tなどと組み合わせて使うことが多い。sortする範囲を選択できる。-k 2なら2列目を、1.5,1.7なら1列目の5~7番目をsortする。
sort -u　重複行を省く
filenum=($(for m in "${!filelist[@]}"; do echo ${listtail[$m]} ; done | sort -n)) # 配列を並び替える
cat logfile sort -t "," -k2.8,2.11 -k2.4,6M -k2.1,2.2 -k2.13,2.14 -k2.16,2.17 -k2.19,2.20 # -t ","で区切り文字を,に変更 -kはキーの指定ができ，Mをつけるとキーを月名とみなす。これで10/Sep/2015:03:36みたいなやつをソートできる。
```
> UTFだと日本語は一文字3バイト

### head
- ファイルのheaderを飛ばすのに使えるときも。

```shell
head -c 10  先頭から指定したバイト数のみ表示
head -n 11  先頭から11行表示(デフォルトは10行)
head -q     ファイルごとのヘッダ表示をしない(複数行選択時)
head -v     ファイルごとにヘッダを表示
```

### tail

```shell
オプションはheadと同じ
tail -n +2 hoge.txt 3行目以降を出力
tail -f ファイルを監視して内容が追加されるたびに末尾に表示する。ログ監視に使用する
```

### jq

jsonをパースできる。

```shell
echo '{"items":[{"item_id":1,"name":"thinkpad","price":250000},{"item_id":2,"name":"Mac Book Pro","price":450000}]}' | jq .
{
  "items": [
    {
      "item_id": 1,
      "name": "thinkpad",
      "price": 250000
    },
    {
      "item_id": 2,
      "name": "Mac Book Pro",
      "price": 450000
    }
  ]
}

# 一部分だけ取り出す。
echo '{"items":[{"item_id":1,"name":"thinkpad","price":250000},{"item_id":2,"name":"Mac Book Pro","price":450000}]}' | jq .items[].item_id
1
2
echo '{"items":[{"item_id":1,"name":"thinkpad","price":250000},{"item_id":2,"name":"Mac Book Pro","price":450000}]}' | jq '.items[] | .item_id' # パイプがbashと衝突しないようにクオーティングが必要。
1
2

# -rで生のテキスト(ダブルクオートやシングルクォートを外す)として出力
echo '{"items":[{"item_id":1,"name":"thinkpad","price":250000},{"item_id":2,"name":"Mac Book Pro","price":450000}]}' | jq -r .items[].name
thinkpad
Mac Book Pro
```

### expand
タブをスペースn文字に変更する。

```shell
expand -t 4 hoge.txt
```

### join

- joinは2つのファイルに共通のフィールドが存在する行を結合する。
- joinを正しく動作させるにはソート済みである必要がある。
- 同じ項目がない行は表示されない

```shell
join -1 3 -2 1 english.txt japanese.txt #-1 3は1つめのファイルは左から3番目を、-2 1は2つめのファイルの1番目をファイル結合に使用する。
join -j 3 english.txt japanese.txt #1つ目のファイルも2つ目のファイルも3番目の項目をつかってファイルを結合する。
join -t"," english.txt japanese.txt     #区切り文字を,に指定してデフォルト通り1行目を共通の項目にしてファイルを結合する。
join -t $'\t' file1 file2 #タブを区切り文字にする。ただし、デフォルトの区切り文字も空白であるため、オプションなしでも使用できるが出力もタブ区切りにしたい場合はこのように指定する。
join -t"," --header english.txt japanese.txt #1行目をヘッダーとして扱う。オプションなしだとヘッダー部分は結合できないため表示されないが、--headerオプションをつけることでヘッダーを表示できる。
join -t"," -a 2 english.txt japanese.txt #一致する行がない部分はファイル2の内容だけを表示する。
join -t"," -a 2 -o auto -e "---" english.txt japanese.txt #一致する行がない時はファイル2の内容を表示して空白を"---"で埋める。

```

### sed

- 文字の置き換えができる。
- コマンドの実行結果は標準出力に書き出されるので必要ならリダイレクトする。
- 出力の行を読み飛ばすなどもできる。
- macだとs/Users/usr/と書かなきゃダメ？
- -e オプションがない時はオプションの次の引数がファイル名として扱われる。

```shell
cat /etc/shels | sed s/usr/Users #パイプラインを使う。
cat /etc/shels | sed -n s/usr/Users #-nで置換した行だけ出力。
cat /etc/shels | sed -n 2,5s/usr/Users #2~5行目のうち、置換した行だけ出力。
cat /etc/shels | sed s/usr/Users/g gというスクリプトコマンドを使うことで複数マッチしたもの全てに置換が適応される。
sed 's/10\.0\.4\.35/10.0.4.27/g' hoge.txt #hoge.txtの10.0.4.35を10.0.4.27に変更
sed "s/\r//" win.txt > linux.txt #windowsの改行CRを空文字に置き換えて削除する。
sed "s/$\r/" linux.txt win.txt #$は行末を表しており、行末にCRを挿入する。
cat test.txt | sed 's/[ ]\+/ /g' # 複数行のスペースを一つにまとめる。
sed -i s/abc/ABC test.txt #-iでファイルを直接編集する。
ls -l / | sed 1d #ls -l / の結果の最初の行を消す。
ls -l / | sed 1,12d #ls -l / の結果の1~12行目を消す。
ls -l / | sed /^合計/d  #正規表現でパターンを指定して削除する。-rで拡張正規表現を使う。
sed -n 2 test.txt #2行目だけを出力する。
sed -n 2,4p test.txt #2~4行目だけを出力する。
sed -n /^+/p test.txt #+から始まる行のみ削除
sed -n -e 2p -e5,6p #複数の範囲を選択する。この場合は2,5~6行目のみが出力される。
sed 1~2i---- text.txt #1行目に----を追加して、その後は2行ごとに----を挿入する。
sed "1iLIST\n----" test.txt #複数の行を一度に追加する。1行目にLISTを追加し、次の行に----を追加する。
\nで追加する行を区切って指定する。
ls -l | sed -n -f sedfile #スクリプトファイルに記述した処理を行わせることができる。スクリプトファイルには2p、5,6pなどを1行ずつに記載する。
cat /etc/shells | sed -e 1iList -e \$aEND #行頭にListを行末にENDを追加する。
$は行末を表すが、変数との区別のため、\をつける。i,aはvimと同じ。
sed "s/^/--/" txt.txt #行頭すべてに--を追加する。
sed "s/$/--/" txt.txt #行末すべてに--を追加する。
sed "s/\/.*/--&--/" txt.txt #正規表現.*は行全体にマッチする。&は、マッチした行全体を取り出すという意味になる。
つまり、行全体を行頭、行末に--をつけたものに置換する。
hoge | sed '1d'  #出力の1行目を削除
sed "s/ /,/g" #csvに変換する。(gで全てに適応される)
echo -e "aaa\nbbb\nccc" | sed -z 's/\n/,/g' # 改行文字を置換するには-zが必要。
sed -i '/$:/d' w99.ntds # $とコロンの区切りがある行を削除する。
```

### tr

- trは文字を置き換えるためのコマンド
- 指定した文字を削除したり、文字が連続している部分をまとめる。

```shell
tr 012 abc row.txt tr.txt #row.txtの012をabcに置換してtr.txtに保存する。
tr --version | tr a-z A-Z #tr --versionの結果を大文字に直す。
tr -d ' ' #cutで区切り文字にならないスペースなども消せる。
tr -d "\r"  #-dで指定した文字を削除する。windowsファイルをLinux用に直す。
tr '\\:' '/|' row.txt tr.txt #\を/に:を|に変換。
```

### uniq

- 隣接する重複行を1行に集約する。
- 事前にsortを行うほうがベター
- uniqには行を指定する方法はなさそう。

```shell
sort -f hoge.txt | uniq -i  #大文字小文字を区別しない。
sort hoge.txt | uniq -c  #重複した回数をカウントする。
sort hoge.txt | uniq -d #重複行をのみを表示
sort hoge.txt | uniq -f 3 #3行目までを無視する。
```

### wc

数を数える

```shell
wc -c # バイト数
wc -l # 行数
wc -m #マルチバイト(日本語の1文字も1になる？)
wc -w # 単語数
```


### diff

```shell
diff -u 5 hoge1 hoge2 #差分とその前後5行(default3行)を表示
diff -q hoge1 hoge2 # 差分ありかなしかを判定
diff <dir1> <dir2> #ディレクトリの中身を比較する。
diff <(cat /var/www/html/index.html) <(curl 127.0.0.1) # <()を使うと一時的にコマンドの実行結果を保存することができる
```
### sdiff
diffと違って差分が横方向に表示される。

```shell
sdiff -s hoge1 hoge2 #差分のみを出力
sdiff -i hoge1 hoge2 #大文字小文字を区別しない。
```

### comm
- テキストファイルを比較し，ファイル1とファイル2にある行，共通している行だけを出力する。

```shell
cat test1.txt
a 1
b 2
c 3
cat test2.txt
a 1
C 3
d 4
comm test1.txt test2.txt
		a 1
b 2
c 3
	C 3
	d 4
comm -1 -2 test1.txt test2.txt # -1と-2を指定するとファイル1にだけある行，ファイル2だけにある行を無視する。-3を指定すると共通している行を無視する。
a 1
```

---

## 番外編 awk

### awkを使う個人的利点

- cut等とは違い，正規表現で区切り文字を指定できる。
    ```shell
    date | awk -F'[ :]' '{print $6}' # 区切り文字にスペースと:をまとめて指定。
    ```
- 外部スクリプトを読み込むことが可能
    ```shell
    awk -f test.awk
    ```
- 大体のテキスト操作はできるので，shell芸よりも短く書ける

---