![見出し画像](https://assets.st-note.com/production/uploads/images/125258414/rectangle_large_type_2_3f3919f9e8e221555c72d89219a77715.png?width=1200)
sedで躓くLinux(POSIX)の正規表現
この記事はjig.jp Advent Calendar 2023の12月22日(金)の記事です
はじめまして。
新米サーバ開発担当のmockです。
突然ですが、皆さんはサーバのログを見てるとき、2つのログを比較したいことってありますよね?
ない?いや、あるんです。あるはずです。
この記事では、ログを比較しようとしたときに躓いた話をしていこうと思います。
ログを比較したい
ここに2つのログがあります。
$ ls
1.log 2.log
$ cat 1.log
2023/12/22 00:00:00.000000 [SUCCESS] hogehoge
2023/12/22 00:00:00.345908 [SUCCESS] fugafuga
2023/12/22 00:00:01.124643 [SUCCESS] fugofugo
2023/12/22 00:00:05.325235 [SUCCESS] gehogeho
$ cat 2.log
2023/12/22 00:10:00.013241 [SUCCESS] hogehoge
2023/12/22 00:10:00.547454 [SUCCESS] fugafuga
2023/12/22 00:10:01.249008 [FAILURE] fugofugo
2023/12/22 00:10:01.345292 [FAILURE] gehogeho
これらを比較して、どの処理の結果が違うのかを確認したいです。
そこで、おもむろにファイルの内容を比較してくれるdiffコマンドを使います。
$ diff 1.log 2.log
1,4c1,4
< 2023/12/22 00:00:00.000000 [SUCCESS] hogehoge
< 2023/12/22 00:00:00.345908 [SUCCESS] fugafuga
< 2023/12/22 00:00:01.124643 [SUCCESS] fugofugo
< 2023/12/22 00:00:05.325235 [SUCCESS] gehogeho
---
> 2023/12/22 00:10:00.013241 [SUCCESS] hogehoge
> 2023/12/22 00:10:00.547454 [SUCCESS] fugafuga
> 2023/12/22 00:10:01.249008 [FAILURE] fugofugo
> 2023/12/22 00:10:01.345292 [FAILURE] gehogeho
時刻が違うのでうまく比較できませんでした。
〜完〜
……だと困るので、sedコマンドを使って日時を削除します。
sedコマンドは正規表現を用いて文字列を置換・抽出できる便利コマンドです。
# 最初にマッチした箇所のみ置き換える
$ sed "s/[置き換えたい文字列・正規表現]/[置き換える文字列]/"
# マッチしたすべての箇所を置き換える
$ sed "s/[置き換えたい文字列・正規表現]/[置き換える文字列]/g"
単体で使うこともできますが、主に標準出力にパイプで繋げて使うことが多いのではないかと思います。
とりあえず、sedコマンドを使って日時を削除したログを1-1.logに出力させてっと……
$ cat 1.log | sed "/(\d{2,4}\/?){3} (\d{2,6}(:|\.)?){4}//g" > 1-1.log
$ cat 1-1.log
2023/12/22 00:00:00.000000 [SUCCESS] hogehoge
2023/12/22 00:00:00.345908 [SUCCESS] fugafuga
2023/12/22 00:00:01.124643 [SUCCESS] fugofugo
2023/12/22 00:00:05.325235 [SUCCESS] gehogeho
🤔
置換されて、ない?
どういうこっちゃ。
正規表現を間違えたのかもしれないので、確認します。
$ deno
Deno 1.35.0
exit using ctrl+d, ctrl+c, or close()
> var pattern = /(\d{2,4}\/?){3} (\d{2,6}(:|\.)?){4}/g
undefined
> var text = "2023/12/22 00:00:00.000000 [SUCCESS] hogehoge"
undefined
> var result = text.match(pattern)
undefinde
> result
[ "2023/12/22 00:00:00.000000" ]
>
正規表現は間違えてなさそうですね。
ではなぜ動かないのか?
基本正規表現と拡張正規表現
実はLinux(POSIX)で標準化されている正規表現には基本正規表現(以下「BRE」)と拡張正規表現(以下「ERE」)という2種類の記法があるのです。
2つの記法では、エスケープしないといけない文字が違います。
そのため、同じ意味の正規表現でも書き方が変わってきます。
以下の表が、それぞれの記法で使える文字の例です。
![](https://assets.st-note.com/img/1703215825018-icONI8HXdq.png?width=1200)
※のついている箇所は、GNUの拡張によってBREで使用できるようになっている文字です。
BREの方は、普段扱う正規表現の記法と結構違います。
EREの方が、見慣れた正規表現の記法に近いです。
sedコマンドはデフォルトの状態ではBREで検索します。
その結果、エスケープする文字の違いから日時の部分に正しくマッチされず、置換が行われないまま出力されてしまったのです。
ではEREで検索するにはどうするか。
-Eオプションを付けてsedコマンドを実行するとEREで検索することができます。
ということで、今度こそ置換できるはず!
$ cat 1.log | sed -E "/(\d{2,4}\/?){3} (\d{2,6}(:|\.)?){4}//g" > 1-1.log
$ cat 1-1.log
2023/12/22 00:00:00.000000 [SUCCESS] hogehoge
2023/12/22 00:00:00.345908 [SUCCESS] fugafuga
2023/12/22 00:00:01.124643 [SUCCESS] fugofugo
2023/12/22 00:00:05.325235 [SUCCESS] gehogeho
🤔🤔🤔
なぜかまだ動きませんでした。
BREとEREでは使えない正規表現がある
なんとBREとEREでは、JavasSript等で使われる正規表現、例えば数字にマッチする「\d」や、アルファベットとアンダースコアにもマッチする「\w」が使えません。
なので、下の表のように置き換える必要があります。
![](https://assets.st-note.com/img/1703229963446-2lv696dcdQ.png?width=1200)
なので、今回は数字のマッチ\dを[0-9]に書き換える必要があります。
最終的に以下のようなコマンドになりました。
$ cat 1.log | sed -E "s/([0-9]{2,4}\/?){3} ([0-9]{2,6}(:|\.)?){4}//g" > 1-1.log
$ cat 1-1.log
[SUCCESS] hogehoge
[SUCCESS] fugafuga
[SUCCESS] fugofugo
[SUCCESS] gehogeho
良さそうですね。
2.logも同じように置換して比較してみましょう。
$ cat 2.log | sed -E "s/([0-9]{2,4}\/?){3} ([0-9]{2,6}(:|\.)?){4}//g" > 2-1.log
$ cat 2-1.log
[SUCCESS] hogehoge
[SUCCESS] fugafuga
[FAILURE] fugofugo
[FAILURE] gehogeho
$ diff 1-1.log 2-1.log
3,4c3,4
< [SUCCESS] fugofugo
< [SUCCESS] gehogeho
---
> [FAILURE] fugofugo
> [FAILURE] gehogeho
今度こそ、無事比較できました。
これで、サーバのログを比較しないといけないときも安心?
おまけ ワンライナー
中間ファイルを作成するのは無駄なので、コマンド一行で比較できると嬉しいですね。
ということで書きました。
$ diff <(cat 1.log | sed -E "s/([0-9]{2,4}\/?){3} ([0-9]{2,6}(:|\.)?){4}//g") <(cat 2.log | sed -E "s/([0-9]{2,4}\/?){3} ([0-9]{2,6}(:|\.)?){4}//g")
長いですね。
入力対象としてのプロセス置換である<(command)を使うことで、コマンドの結果をそのまま引数として渡すことができます。
今回は置換結果をそのまま引数として渡すことで中間ファイルを残すことなくログを比較できます。
やってみましょう。
$ diff <(cat 1.log | sed -E "s/([0-9]{2,4}\/?){3} ([0-9]{2,6}(:|\.)?){4}//g") <(cat 2.log | sed -E "s/([0-9]{2,4}\/?){3} ([0-9]{2,6}(:|\.)?){4}//g")
3,4c3,4
< [SUCCESS] fugofugo
< [SUCCESS] gehogeho
---
> [FAILURE] fugofugo
> [FAILURE] gehogeho
完璧です。
今度こそ、サーバのログを比較するときも安心です。
ということで、sedとdiffでよいサーバ運用を。
参考
この記事が気に入ったらサポートをしてみませんか?