🔍

調査時大活躍!テキスト処理シェルワンライナー

こんにちは、エンジニアの水野です。

普段我々エンジニアが用いるOSには様々な種類があります。
フォルシアでは主に、Windows標準のLinux環境であるWSLや、VirtualBoxのUbuntuを用いて開発しています。

Linuxの操作には様々なコマンドを駆使しますが、頻繁に使うもの以外は忘れてしまったり、応用方法が狭まってしまうのもまた事実。
フォルシアの現役エンジニア社員はどのようなコマンドを駆使しているのでしょうか?

今回は頻出のコマンドから、それらを組み合わせ調査時に大活躍する「FORCIA的便利なワンライナー」までを簡単に紹介します。

業務で頻出!基本コマンドいろいろ

まずは基本的な頻出コマンドから。

grep “検索したい文字” * ファイル名

grepは文字列を検索し、テキスト内に存在した場合その行を抽出するコマンドです。
調査に限らず超頻出のコマンドといえるでしょう。

  • grep -E "hogehoge":拡張正規表現を有効にする
  • grep -v "hogehoge":not検索、一致しないものを抽出
  • grep -r "hogehoge" [ディレクトリ]:指定したディレクトリ以下を全部探索する
  • grep -l "hogehoge":ヒットした行ではなく、該当ファイルのみを表示する
  • grep -i "hogehoge":大文字小文字を無視して検索する
  • grep -c "hogehoge" :ヒットする行数を確認
 grep -rl 水 ./
./multi_2word.txt
./multi_1word.txt
./multi_3word.txt
./multi_6word.txt
./multi_4word.txt
./multi_5word.txt

上記のようなオプションは調査にも頻出です!

sort

言うまでもなく結果をソートする機能。集計調査にはマストです。

  • sort -f:大文字と小文字を判別せずにソート
  • sort -r:逆順にソート
  • sort -u:重複行を削除、ソート
  • sort -n:数値順にソート(この指定がないと辞書順)
grep -rl 水 ./ | sort -r
./multi_6word.txt
./multi_5word.txt
./multi_4word.txt
./multi_3word.txt
./multi_2word.txt
./multi_1word.txt

uniq

ファイルの重複行を取り除きます。
とはいえ隣り合った前後のものしか重複判定をしてくれないので、前述のsortコマンドとの組み合わせが一般的ですね。

  • sort | uniq -c:(ソートして)各行の前に重複回数を出力する

以下のように、ファイル内での文字列の出現回数をカウントできます。

cat multi_1word.txt | sort | uniq -c | head -6
     12 '
      5 +
      1 1
      4 2
      3 3
     13 5

nkf

異なるOS間でテキストデータを交換する際に問題となる文字コードと改行コードを変換するコマンドです。
どの文字コードなのか調べる、またはテキストを各文字コードへ変換するために使うことがほとんどではないでしょうか。

  • nkf -g [hoge.txt]:テキストの文字コードを出力
  • nkf -j:JISコードで出力する(デフォルト)
  • nkf -s:Shift_JISで出力
  • nkf -e:EUCコードで出力
  • nkf -S:入力をShift_JISと仮定して出力(半角カナ(JIS X 0201 片仮名)も
    受け入れる)
    こちらはオプションの大文字・小文字で出力方法が変わる点に要注意!
echo %83R%83s%81%5B%97p%8E%86 | nkf -S --url-input
コピー用紙

テキスト処理に大活躍!awk

次にご紹介するのが、awkというテキスト処理に適した軽量なプログラミング言語です。
csv, tsv など、区切り文字で区切られたタイプのテキストデータの処理に適しており、
コマンドと同様にすぐ使いこなせるような手軽さが嬉しいところです。

今回は簡単な活用方法をいくつかご紹介しますが、詳しい説明はこちらの記事で是非ご覧ください!
なお、本記事中のプログラムはすべて gawk 5.0.1 にて動作を確認しています。

awk -f (ソースファイル).awk (入力ファイル1)[, (入力ファイル2), ...]
awk '(awk のソースコードを直接書く)' (入力ファイル1)[, (入力ファイル2), ...]

基本的には上記のように使用します。
[ \t]+、つまりスペースかタブの繰り返しを区切り文字として、n個目の要素で処理を行う、というように使います。(区切り文字の指定も行えます!)

とはいえイメージが湧きづらいでしょうか。
ここからはawkを含め複数コマンドを組み合わせた、具体的な活用方法を見ていきましょう。

FORCIA的調査ワンライナー

ログ調査はサーバーの運用にあたり欠かせない業務です。
フォルシアではApacheを使用しており、サーバー運用上このアクセスログ調査は欠かせません。
そこで上記で紹介したコマンドを用いて、目的別にFORCIAエンジニアがよく使うワンライナーをご紹介します。

ログ形式

なお、本記事では以下のような形式のApacheログ(access_log.yyyymmdd)を調査する際のコマンドを紹介しています。

111.111.11.11 - - [01/Jan/2023:00:00:00 +0900] "GET /..." 200 11111 "https://hogehoge..." "https://foobar..."

[IPアドレス] - - [アクセス日次] "[メソッド] [アクセス先URL]" [ステータスコード] [転送量] "[アクセス元URL]" "[どのOS・ブラウザから遷移してきたか]"

形式の異なるログ調査時には、正規表現やseq, cutの対象を変更して活用してみてください。

アクセス数が多いIPアドレス上位10件を出力

cat access_log.$DATE | cut -d ' ' -f 1 | sort | uniq -c | sort -gr | head

複数のコマンドが組み合わさっていますが、順にみるととてもシンプルなワンライナーです。

  • cut -d ' ' -f 1
    • スペースで分割した1つ目の要素(IPアドレス部分)のみを抽出し、
  • sort | uniq -c | sort -gr
    • IPアドレスをソートし、出現回数で降順に並び替え、
  • head
    • 上位10件を出力しています。
cat access_log.$DATE  | cut -d ' ' -f 1 | sort | uniq -c | sort -gr | head -3
  59197 123.456.789.01
   8222 00.000.000.000
   8102 11.22.33.44

「やたらサーバーに負荷がかかっている、怪しいIPアドレスからのアクセスが多くないか??」という調査には頻繁に使われます。

特定の時間帯のエラーリクエスト数を調べる

 cat access_log.$DATE | awk '"[01/Jan/2023:00:00:00" <= $4 && $4 < "[01/Jan/2023:03:10:00"' | cut -d ' ' -f 9 | sort | uniq -c

次に、「何時から何時の間、どのようなHTTPステータスコードが返されたか」を集計するワンライナー。
awkが駆使されていますね!順に見ていきます。

  • awk '"[01/Jan/2023:00:00:00" <= $4 && $4 < "[01/Jan/2023:03:10:00"'
    • スペースで区切り4つ目の要素=時間帯を見て、指定した時間帯内のものを絞り込む
  • cut -d ' ' -f 9
    • スペース区切りで分割、9番目=HTTPステータスコードを取得
  • sort | uniq -c
    • ソートしてまとめ、出現回数を出力
cat test_access_log.date | awk '"[29/Nov/2023:14:00:00" <= $4 && $4 < "[29/Nov/2023:17:10:00"' | cut -d ' ' -f 9 | sort | uniq -c
1664914 200
   1459 301
   6296 302
   3931 304
     18 400
    985 404
      2 500

エラーリクエスト種類は限られているので、今回は上位出力を行っていませんが、
あらゆる「hogehogeの出現回数をカウント、上位n個を抽出」という際には

[hogehogeのみ取得] | sort | uniq -c | sort -gr | head -[n]

という構文が便利ですね。

一時間ごとのリクエスト数を算出する

cut -d' ' -f4 test_access_log.date | sed -r 's/\[([0-9]{1,2}\/[a-zA-Z]{3}\/[0-9]{4}:[0-9]{2}).+/\1/' | sort | uniq -c

こちらではawkではなくcut,seqコマンドを用いてシンプルにアクセス日次のみを切り出しています。

  • cut -d' ' -f4 test_access_log.date
    • アクセス日時部分を切り出し
  • sed -r 's/\[([0-9]{1,2}\/[a-zA-Z]{3}\/[0-9]{4}:[0-9]{2}).+/\1/'
    • 日、月、年、時間のみ抽出 (ex: [29/Nov/2023:10:01:01 から 29/Nov/2023:10
  • sort | uniq -c
    • ソートし出現回数を出す
cut -d' ' -f4 test_access_log.date | sed -r 's/\[([0-9]{1,2}\/[a-zA-Z]{3}\/[0-9]{4}:[0-9]{2}).+/\1/' | sort | uniq -c | sort -r | head -5
 614465 29/Nov/2023:10
 566165 29/Nov/2023:09
 549744 29/Nov/2023:11
 547899 29/Nov/2023:14
 545520 29/Nov/2023:13

最大秒間アクセス数上位n件・アクセス合計数

上の「1時間ごとのリクエスト数」をマスターすれば、正規表現をいじるだけで秒間アクセス数の取得も簡単です。
以下の例では、秒間アクセス数の多かった日時上位5件とアクセス数の合計を出力しています。

cut -d' ' -f4 test_access_log.date | sed -E 's/.*:([0-9]{2}:[0-9]{2}:[0-9]{2}).*/\1/' | sort  | uniq -c | sort -gr | head -n 6
 295267 
    451 16:43:45
    445 16:09:14
    439 11:48:50
    438 10:08:12
    435 14:34:21

エラー影響を受けたユーザー数

エラーが発生した時間帯に絞り、200以外のユーザ数をカウントしています。
awkを複数回駆使しています!

cat test_access_log.date |  awk '"[17/Oct/2019:14:45:00" <= $4 && $4 < "[17/Oct/2019:17:10:00"' | grep -ive "GET /.*\.\(css\|js\|jpg\|gif\|png\|swf\|ico\)\ HTTP" | awk '{if($9!=200)print $0}' | awk '{print $1}' | sort | uniq -c | wc -l
  • awk '"[17/Oct/2019:14:45:00" <= $4 && $4 < "[17/Oct/2019:17:10:00]"
    日時フィールド比較、時間範囲指定
  • grep -ive "GET /.*\.\(css\|js\|jpg\|gif\|png\|swf\|ico\)\ HTTP"
    • 静的ファイルへのアクセス除外
  • awk '{if($9!=200)print $0}
    • 9個目=ステータスコード200を除外
  • awk '{print $1}'
    • さらに、1個目=IPアドレス部分を抽出
  • sort | uniq -c | wc -l
    • 重複削除・出現回数し、何種類のIPアドレスがアクセスしたか出力
cat test_access_log.date |  awk '"[29/Nov/2023:14:45:00" <= $4 && $4 < "[29/Nov/2023:17:10:00"' | grep -ive "GET /.*\.\(css\|js\|jpg\|gif\|png\|swf\|ico\)\ HTTP" | awk '{if($9!=200)print $0}' | awk '{print $1}' | sort | uniq -c | wc
 -l
2389

最後に

Linuxのテキスト処理系基本コマンド、それを活用したシェルワンライナーを紹介いたしました。
皆さんのログ調査を30分救えれば幸いです!

この記事を書いた人

水野 凜
2023年新卒入社エンジニア
年明けの暴飲暴食が祟ったため、先日1年ぶりにフィットボクシングを起動しました。

FORCIA Tech Blog

Discussion