0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

必要最低限で覚えるLinuxテキスト処理超入門

Posted at

はじめに

Linuxにはテキスト処理を行うコマンドがたくさんあります。コマンドには多彩は書式やオプションが用意されており、複雑な処理ができるものが多くあります。

ただ、初歩的な書式やオプションだけでも パイプで複数連結すればなんとかなる ことが多いので、基本的な書式だけでも無意識に使えるようにすると効率が桁違いです。

最初から複雑な書式を使いこなすのは難しく、すぐに忘れてしまします。チートシートを片手に多彩なオプションで悩む時間も無駄です。

パイプでつなげればなんとかなる

  • grep 1回ですべて検索しようとしない
    パイプでつなげればなんとかなる
  • sed 1回ですべて置換しようとしない
    パイプでつなげればなんとかなる
  • awk 1回でなんでもかんでもやろうとしない
    他コマンドをパイプでつなげればなんとかなる。

このエントリで記載している各コマンドの使い方は、ごく基本的な書式と初歩的なオプションに限定しています。

説明文は文献をあたっていないので、いいかげんなものが含まれている可能性があります。ごめんちょ。

処理対象のテキストファイル

Splunkのチュートリアルデータを利用してテキスト処理のためのコマンドを解説します。
あ、いや、Splunkの活用法と違うってのはわかってるんですが。今回はすいません、データだけ利用させてください。

curl https://docs.splunk.com/images/Tutorial/tutorialdata.zip -o ~/tmp/tutorialdata.zip
unzip ~/tmp/tutorialdata.zip -d ~/tmp/sample
cd ~/tmp/sample

テキスト処理コマンドいろいろ

テキスト処理のコマンドは、標準入力→処理→標準出力が多い。なので、パイプで連結できるのです。

cat

cat(きゃっと):conCATenate
ファイルか標準入力を連結して標準出力に出力する
→ ファイルの中身を出力

基本書式
cat <ファイル名>
cat <STDIN>
基本の使い方
$ cat www1/secure.log

grep

grep(ぐれっぷ):Global Regular Expression Print
ファイルか標準入力から正規表現に一致する行を標準出力に出力する
→ 入力されてきた行に対して、フィルタして出力

正規表現はregex(Regular Expression)と表記されることが多くあります。

複数ファイルがある場合は、先にgrepで検索すると、先頭にファイル名がつくので cat 全ファイル | grep xxx としてもよいです。

基本書式
grep <正規表現> <ファイル>
grep <正規表現> <STDIN>
基本の使い方
$ grep sudo: */secure.log
$ cat */secure.log | grep sudo:

よく使うオプション

オプション 略称 意味
-v inVert 指定条件以外を検索
-i Ignore case 大文字/小文字を無視
-r Recursive ディレクトリを再帰的に検索
-E Extended 拡張正規表現を利用

拡張正規表現は (ABC|XYZ) などが利用できる書式のことで egrep コマンドでも同じ効果が得られます。

オプションを利用した書式
# sudo: という文字列を「含まない」行を抽出
$ cat */secure.log | grep -v sudo:

# ssh という文字列の大文字/小文字を無視する
$ cat */secure.log | grep -i ssh

# カレントディレクトリ配下の全ファイルに対して検索する
$ grep ssh * -ri

その他の書式

以下の「ステータスコード200」は、想定しない場所も抽出してしまう可能性があるので、完璧ではないですが漏れは無いかと。

ログを抽出する
# ステータスコード200以外を抽出
$ cat */access.log | egrep -v " 200 [1-9][0-9]* "

# さらに特定のUAを除外する
$ cat */access.log | egrep -v " 200 [1-9][0-9]* " | grep -v Mozilla | grep -v Opera

# さらに特定のUAを除外する
$ cat */access.log | egrep -v " 200 [1-9][0-9]* " | grep -v Mozilla | grep -v Opera

# さらにPOSTリクエストのみに限定する
$ cat */access.log | egrep -v " 200 [1-9][0-9]* " | grep -v Mozilla | grep -v Opera | grep POST

sed

sed(せっど):Stream EDitor
ファイルか標準入力に対して条件文を利用して変換する
→ 入力されてきた行に対して、条件式で置換して出力

だいたい正規表現で置換する用途で利用します。

基本書式
# 正規表現で置換
sed 's/<正規表現>/<置換後文字列>/' <ファイル>
sed 's/<正規表現>/<置換後文字列>/' <STDIN>

# 正規表現で置換(1行中に出現する複数の正規表現の全てを置換する)
sed 's/<正規表現>/<置換後文字列>/g' <ファイル>
sed 's/<正規表現>/<置換後文字列>/g' <STDIN>

# 正規表現の大文字/小文字を判定しない
sed 's/<正規表現>/<置換後文字列>/gi' <ファイル>
sed 's/<正規表現>/<置換後文字列>/gi' <STDIN>
基本の使い方
$ cat */secure.log | grep sudo: | sed 's/;..*//'

よく使うオプション

オプション 略称 意味
-E Extended 拡張正規表現

-iでファイル指定して上書き、-eで複数条件の指定などありますが、代替手段でなんとかなるので覚えなくてよいです。

その他の書式

正規表現パターンを \(\) で囲むことでグループ化して、置換文字列として参照させることができます。
「マッチさせたうえで、抽出後にその文字をそのまま残したい」というときに利用します。

グループ化と後方参照
$ cat */secure.log | grep sudo: | sed 's/..*sudo: \(..*\) ;/\1/'

一部の文字はエスケープが必要です。

特殊文字のエスケープ
$ cat */secure.log | grep sudo: | sed 's/..*\/home\/\(..*\) ;/\1/'

セパレータは任意に変更できます。

セパレータの変更
$ cat */secure.log | grep sudo: | sed 's:..*/home/\(..*\) ;:\1:'

# 拡張正規表現のオプションだとグループの()がエスケープ不要になる
$ cat */secure.log | grep sudo: | sed -E 's:..*/home/(..*) ;:\1:'

cut

cut(かっと):CUT
入力されていた行に対して、文字数やデリミタなどの条件式で抽出して出力

基本書式
cut -d<delimiter> -f<field> <STDIN>
基本の使い方
$ cat */secure.log | grep sudo: | cut -d: -f4

# デリミタを空白とする場合はエスケープする
$ cat */secure.log | grep sudo: | sed 's/;..*//' | cut -d\  -f6,8

よく使うオプション

オプション 略称 意味
-d delimiter デリミタ指定(複数文字NG)
-f field 抽出するフィールドを指定

sort

sort(そーと):SORT
入力されてきた行をソートして出力

基本書式
sort <ファイル名>
sort <STDIN>
基本の使い方
$ cat */secure.log | grep sudo: | sed 's/;..*//' | awk '{print $6,$8}' | sort

よく使うオプション

オプション 略称 意味
-n number 数値として判定
-r reverse 逆順(降順)に表示

uniq

uniq(ゆにーく):UNIQue
前後の行が重複している場合に行をまとめる

基本書式
uniq <ファイル名>
uniq <STDIN>
基本の使い方
$ cat */secure.log | grep sudo: | sed 's/;..*//' | awk '{print $6,$8}' | sort | uniq

よく使うオプション

オプション 略称 意味
-c count 重複回数を表示
$ grep sudo: secure.log | awk -F\; '{print $3}' | sort | uniq -c
$ grep sudo: secure.log | awk -F\; '{print $3}' | sort | uniq -c | sort -nr

awk

awk(おーく):Aho Weinberger Kernighan
テキストファイル処理に特化したプログラミング言語
→ 入力されてきた行に対して、いろいろ処理して表示する

基本書式
awk <code> <ファイル>
awk <code> <STDIN>
基本の使い方
# print $n で、n番目のフィールドを抜き出します
$ cat */secure.log | grep sudo: | sed 's/;..*//' | awk '{print $6,$8}'

awkは使いこなせれば、sort/uniqなどをまとめて処理できるようになりますが、個人的には print で使うだけです。
$NFで最後のフィールド、$(NF-1)で最後から1番目、という使い方ができます。

よく使うオプション

オプション 略称 意味
-F Field デリミタ指定(複数文字OK)

デリミタ(=delimiter/de,limiter)は「区切り文字」のことで、セパレータということもあります。デフォルトはスペースです。

nl

nl(えぬえる):Number of Line
入力されてきた行に対して行番号を添えて出力

基本書式
nl <ファイル名>
nl <STDIN>
基本の使い方
$ cat */secure.log | grep sudo: | sed 's/;..*//' | awk '{print $6,$8}' | sort | uniq | nl

head

head(へっど):HEAD
入力されてきた行の「先頭」から指定した分だけ出力

基本書式
head <ファイル名>
head <STDIN>
基本の使い方
$ cat */secure.log | grep sudo: | sed 's/;..*//' | awk '{print $6,$8}' | sort | uniq -c | sort -nr | head

よく使うオプション

オプション 略称 意味
-n number 表示させる行数を指定
$ head access.log -n 30

tail

tail(ている):TAIL
入力されてきた行の「末尾」から指定した分だけ出力

基本書式
tail <ファイル名>
tail <STDIN>
基本の使い方
$ cat */secure.log | grep sudo: | sed 's/;..*//' | awk '{print $6,$8}' | sort | uniq -c | sort -n | tail

よく使うオプション

オプション 略称 意味
-n number 表示させる行数を指定
-f follow 入力が続く限り更新して表示
$ tail access.log -n 30

読み込み対象ファイルが継続的に追記されるようなファイルの場合は -f で逐次更新することもできます。

$ tail -f /var/log/nginx/access.log
$ tail -f /var/log/nginx/access.log | grep POST

ログ検索のサンプル問題

セキュアログ調査

Linuxシステムのセキュアログと思われる */secure.log について、それぞれ次のログ調査を実施してください。

$ head */secure.log -n 2
==> mailsv/secure.log <==
Thu Apr 13 2023 13:34:34 mailsv1 sshd[4351]: Failed password for invalid user guest from 86.212.199.60 port 3771 ssh2
Thu Apr 13 2023 13:34:34 mailsv1 sshd[2716]: Failed password for invalid user postgres from 86.212.199.60 port 4093 ssh2

==> www1/secure.log <==
Thu Apr 13 2023 13:34:30 www1 sshd[4747]: Failed password for invalid user jabber from 118.142.68.222 port 3187 ssh2
Thu Apr 13 2023 13:34:30 www1 sshd[4111]: Failed password for invalid user db2 from 118.142.68.222 port 4150 ssh2

==> www2/secure.log <==
Thu Apr 13 2023 13:34:33 www2 sshd[1319]: Failed password for invalid user varnish from 209.160.24.63 port 3459 ssh2
Thu Apr 13 2023 13:34:33 www2 sshd[1390]: Failed password for invalid user icinga from 209.160.24.63 port 2678 ssh2

==> www3/secure.log <==
Thu Apr 13 2023 13:34:31 www3 sshd[3163]: Failed password for invalid user sysadmin from 202.91.242.117 port 1994 ssh2
Thu Apr 13 2023 13:34:31 www3 sshd[3301]: Failed password for games from 202.91.242.117 port 3741 ssh2
すべての secure.log からSSHのパスワード認証に成功したアカウント名と、そのを調査する
grep sshd */secure.log | grep Accepted \
| awk '{print $11}' \
| sort | uniq -c
すべての secure.log からSSHのパスワード認証に成功したアカウント名接続元IPアドレスと、そのを調査する
grep sshd */secure.log | grep Accepted \
| awk '{print $11,$13}' \
| sort | uniq -c
すべての secure.log からSSHのパスワード認証に失敗したアカウント名と、そのを調査する

これだと invalid というユーザ名が多いように見えてしまう。

grep sshd */secure.log | grep Failed \
| awk '{print $11}' \
| sort | uniq -c | sort -n

解決方法その1
ログを見ると以下のことがわかる。

  • ユーザ名が存在するときのログは、最後のフィールドから数えて5番目がユーザ名
  • ユーザ名が存在しないときのログも、最後のフィールドから数えて5番目がユーザ名
Mon Apr 17 2023 13:34:34 mailsv1 sshd[5586]: Failed password for mail from 142.233.200.21 port 3967 ssh2
Mon Apr 17 2023 13:34:34 mailsv1 sshd[3093]: Failed password for invalid user administrator from 142.233.200.21 port 3229 ssh2

awkの$NFを利用することで最後から〇番目というフィルタができる。

grep sshd */secure.log | grep Failed \
| awk '{print $(NF-5)}' \
| sort | uniq -c | sort -n

解決方法その2
ログを見ると以下のことがわかる。

  • ユーザ名が存在するときのログはFailed password for XXX from
  • ユーザ名が存在しないときのログはFailed password for invalid user XXX from
Mon Apr 17 2023 13:34:34 mailsv1 sshd[5586]: Failed password for mail from 142.233.200.21 port 3967 ssh2
Mon Apr 17 2023 13:34:34 mailsv1 sshd[3093]: Failed password for invalid user administrator from 142.233.200.21 port 3229 ssh2

Failed password forのあとにinvalid user が0回以上存在するとき、を条件に含めるようにすることでユーザ名だけを抽出できる。

grep sshd */secure.log | grep Failed \
| sed 's/.*Failed password for \(invalid user \)*\(.*\) from.*/\2/' \
| sort | uniq -c | sort -n
すべての secure.log からSSHのパスワード認証に失敗したアカウント名接続元IPアドレスと、そのを調査する

前述の解決方法その1の手法をもとに、IPアドレスが最後から3番目なので以下のように抽出できる

grep sshd */secure.log | grep Failed \
| awk '{print $(NF-5),$(NF-3)}' \
| sort | uniq -c | sort -n

前述の解決方法その1の手法をもとに、IPアドレスの前後の文字からグループ化することで以下のように抽出できる

grep sshd */secure.log | grep Failed \
| sed 's/.*Failed password for \(invalid user \)*\(.*\) from \(.*\) port.*/\2 \3/' \
| sort | uniq -c | sort -n

アクセスログ調査

Webサーバのアクセスログと思われる www[1-3]/access.log について、それぞれ次のログ調査を実施してください。

$ head www[1-3]/access.log -n 2
==> www1/access.log <==
209.160.24.63 - - [13/Apr/2023:18:22:16] "GET /product.screen?productId=WC-SH-A02&JSESSIONID=SD0SL6FF7ADFF4953 HTTP 1.1" 200 3878 "http://www.google.com" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46 Safari/536.5" 349
209.160.24.63 - - [13/Apr/2023:18:22:16] "GET /oldlink?itemId=EST-6&JSESSIONID=SD0SL6FF7ADFF4953 HTTP 1.1" 200 1748 "http://www.buttercupgames.com/oldlink?itemId=EST-6" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46 Safari/536.5" 731

==> www2/access.log <==
199.15.234.66 - - [13/Apr/2023:18:24:31] "GET /cart.do?action=view&itemId=EST-6&productId=SC-MG-G10&JSESSIONID=SD5SL9FF2ADFF4958 HTTP 1.1" 200 3033 "http://www.google.com" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.28) Gecko/20120306 YFF3 Firefox/3.6.28 ( .NET CLR 3.5.30729; .NET4.0C)" 177
175.44.24.82 - - [13/Apr/2023:18:44:36] "GET /category.screen?categoryId=SHOOTER&JSESSIONID=SD7SL9FF5ADFF5066 HTTP 1.1" 200 2334 "http://www.google.com" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; BOIE9;ENUS)" 546

==> www3/access.log <==
74.125.19.106 - - [13/Apr/2023:18:27:48] "GET /product.screen?productId=FS-SG-G03&JSESSIONID=SD10SL4FF4ADFF4976 HTTP 1.1" 200 3770 "http://www.buttercupgames.com/category.screen?categoryId=STRATEGY" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46 Safari/536.5" 667
74.125.19.106 - - [13/Apr/2023:18:27:50] "POST /cart.do?action=addtocart&itemId=EST-26&productId=FS-SG-G03&JSESSIONID=SD10SL4FF4ADFF4976 HTTP 1.1" 200 293 "http://www.buttercupgames.com/product.screen?productId=FS-SG-G03" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46 Safari/536.5" 100
アクセスのピークは何時ごろか?

調査から除外する日時を判断する
調査対象は複数日にわたったログであり、最初の日時以前のログと最後の日時以降のログは存在しないため、その日付以外のログを対象とする。
(現実世界であれば曜日による増減を考慮する必要があるが、ここでは考慮しないことにする)

以下の結果から、2023/4/14~2023/4/19のログを調査対象とする。

$ cat www[1-3]/access.log | awk '{print $4}' | sort | head -n 1
[13/Apr/2023:18:22:16]

$ cat www[1-3]/access.log | awk '{print $4}' | sort | tail -n 1
[20/Apr/2023:18:22:16]

以下のように、egrep -v '(13|20)/Apr'とすることで除外できることが確認できた。

$ cat www[1-3]/access.log | awk '{print $4}' | egrep -v '(13|20)/Apr' | sort | head -n 1
[14/Apr/2023:00:00:19]

$ cat www[1-3]/access.log | awk '{print $4}' | egrep -v '(13|20)/Apr' | sort | tail -n 1
[19/Apr/2023:23:58:49]

時間帯の順にアクセス数量を表示

cat www[1-3]/access.log | awk '{print $4}' \
| egrep -v '(13|20)/Apr' \
| awk -F: '{print $2}' \
| sort | uniq -c

アクセス数量の順に時間帯を表示

cat www[1-3]/access.log | awk '{print $4}' \
| egrep -v '(13|20)/Apr' \
| awk -F: '{print $2}' \
| sort | uniq -c \
| sort -n

タイムゾーンは不明だが、午前3時がアクセスのピークの模様

正規表現

このエントリでは、様々な正規表現を使ってテキストを操作するものではないので、正規表現自体の解説は本筋ではありません。(として逃げます。)

正規表現の解説記事は世の中にわんさかあるので、ここではよく利用するパターンを記載するだけにしています。

正規表現をチェックできるサイト

よく利用する正規表現

正規表現/拡張正規表現がごちゃまぜに書いてあるので grep で利用できなかったら egrep で利用するなど、適宜工夫してください。

表記 意味
^ 先頭
$ 末尾
. なにかの文字
[ab] aもしくはb
[a-z] aからzまでの英字
[-az] -もしくはaもしくはz
[^] 以外
() グループ
(abc|xyz) abcもしくはxyz
\d 数字
\w 英字
\s スペース(空白、タブ、改行)
* 0以上の繰り返し
+ 1以上の繰り返し
{n} 直前のn回繰り返し
{n,m} 直前のn~m回の繰り返し
{n,} 直前のn回以上の繰り返し
{,m} 直前のm回以下の繰り返し

コンフィグファイルで必要な部分を抜き出すのに

  • 空行(^$
  • 先頭に0個以上のスペースがあって、そのあとが#

になっていない行を表示する。とかはよくやりますよね。

cat <config> | egrep -v "^($|\s*#)"

# 文字数は多いけど、個人的にはこっちのほうが好き
cat <config> | egrep -v "(^$|^\s*#)"

さいごに

とある勉強会の用途でこの資料を作成しました。

コマンドの活用方法に唯一解は存在しません。

最初は基本のコマンドだけで、初歩のオプションだけで、自然に手が動くようになることが最初の一歩でいいのです。そこから「必要と感じたら」少しずつアレンジしていけばいいし、「それ、違うよ」と言われたらその時にちゃんと調べて使い方をなおせばいいのです。
と私は考えます。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?