正規表現によるテキスト処理

LinuxBeginner
オンラインで実践に進む

はじめに

正規表現は、特定の文字列そのものではなく、テキストの構造によって文字列を表します。同じパターン言語が多くの Linux ツールで使われていますが、ツールごとに用途は異なります。grep は一致する行を選択または抽出し、sed はストリームを変換し、awk はパターンとフィールドを意識した処理を組み合わせます。

この実験では、アンカーと文字クラスから始め、拡張された繰り返しと抽出へ進みます。その後、変換結果を保存する前に確認し、最後にフィールドの選択と集計を行います。パターンは引用符で囲み、特殊文字をシェル自身が解釈せず、テキスト処理ツールへ渡すようにします。

基本的な正規表現の構造で行を照合する

このステップでは、リテラルテキスト、アンカー、文字クラスを使って、行全体の位置を表します。

練習用ファイルを確認します。

cd /home/labex/project/regex-lab
nl -ba contacts.txt

リテラルテキストは、行内のどこにあっても一致します。

grep 'active' contacts.txt

^ はパターンを行の先頭に固定し、$ は行末に固定します。Alice のレコードと、2 桁の数字で終わるレコードを検索します。

grep '^Alice|' contacts.txt
grep '[0-9][0-9]$' contacts.txt

角括弧内では、文字クラスによって 1 文字を照合します。[ABC] は A、B、C のいずれかを意味します。

grep '^[ABC]' contacts.txt | tee abc-contacts.txt

このコマンドにより、Alice、Bob、Carol のレコードが出力されます。アンカー付きの数値に一致する行も保存します。

grep '[0-9][0-9]$' contacts.txt > two-digit-values.txt

拡張正規表現を使って一致箇所を抽出する

このステップでは、拡張正規表現を使って選択肢と繰り返しを表し、一致した部分文字列だけを抽出します。

grep -E を使うと、+(1 回以上)、?(0 回または 1 回)、|(選択)などの演算子を使用できます。active または pending のステータスフィールド全体に一致させます。

cd /home/labex/project/regex-lab
grep -E '\|(active|pending)\|' contacts.txt

メールアドレス形式のパターンは、次の要素から組み立てられます。

  • [[:alnum:]._-]+ は、ローカル部で使用できる文字を 1 文字以上照合します。
  • @ はリテラル文字です。
  • [[:alnum:].-]+ は、ドメイン本体を 1 文字以上照合します。
  • \.[[:alpha:]]{2,} は、ドットと 2 文字以上の英字によるサフィックスを照合します。

-o を使うと、一致した部分文字列だけを出力できます。

grep -Eo '[[:alnum:]._-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' contacts.txt | tee emails.txt

一致した行に番号を付け、抽出したアドレスの数を数えます。

grep -En '\|(active|pending)\|' contacts.txt
wc -l < emails.txt | tr -d ' ' > email-count.txt
cat email-count.txt

dave_at_example.com@ を含まないため、このパターンには一致しません。

Sed で変換結果を確認する

このステップでは、入力を変更せずに sed でストリームを変換します。

置換の形式は s/pattern/replacement/flags です。各行の最初の a だけを置換し、その後、g フラグを使って小文字の a をすべて置換します。

cd /home/labex/project/regex-lab
sed 's/a/A/' contacts.txt
sed 's/a/A/g' contacts.txt

テキスト自体にスラッシュが含まれる場合や、別の区切り文字の方が分かりやすい場合は、異なる区切り文字を使えます。メールドメインを redacted.invalid に置換します。

sed -E 's#@[[:alnum:].-]+\.[[:alpha:]]{2,}#@redacted.invalid#g' contacts.txt

Sed のアドレスを使うと、コマンドを適用する行を選択できます。正確な |inactive| フィールドを含む行だけで、ステータスを置換します。

sed '/|inactive|/s/|inactive|/|disabled|/' contacts.txt | tee contacts-preview.txt

元のファイルにまだ inactive が含まれていることを確認します。

grep '|inactive|' contacts.txt

Sed でクリーンアップ済みファイルを作成する

このステップでは、複数の sed コマンドを組み合わせ、変換したストリームを新しいファイルに保存します。

services.csv のヘッダーは変更しません。データ行では、warnwarning に正規化し、最後の数値フィールドに ms サフィックスを追加します。-e で複数のコマンドを指定し、アドレス 2,$ で 2 行目から末尾までに適用範囲を限定します。

cd /home/labex/project/regex-lab
sed -E -e '2,$ s/,warn,/,warning,/' -e '2,$ s/([0-9]+)$/\1ms/' services.csv | tee services-clean.csv

ここでは、括弧で最後の数字をキャプチャし、置換文字列内で \1 を使ってそのキャプチャを再利用しています。

元のファイルと新しいファイルを比較します。

diff -u services.csv services-clean.csv || true

出力は意図的に変更されているため、diff が 0 以外のステータスを返すのは想定どおりです。|| true により、コピーしたコマンド列を続けて実行できます。

Awk でフィールドを選択して集計する

このステップでは、フィールド構造と計算の両方が必要な処理に awk を使います。

-F, は入力区切り文字をカンマに設定します。データ行だけを対象に、サービスとステータスを出力します。NR は現在のレコード番号です。

cd /home/labex/project/regex-lab
awk -F, 'NR > 1 {print $1, $2}' services.csv

3 番目の数値フィールドが 200 を超える行を選択します。

awk -F, 'NR > 1 && $3 > 200 {print $1, $3}' services.csv | tee slow-services.txt

合計と件数を累積し、END ブロックで平均を計算します。printf で数値の形式を指定します。

awk -F, 'NR > 1 {sum += $3; count++} END {printf "average_latency=%.1f\n", sum/count}' services.csv | tee latency-summary.txt

Awk のパターンによってアクションを実行するレコードを決めます。$1$3 などのフィールドを使うことで、構造化された計算を読みやすく記述できます。

まとめ

アンカー、文字クラス、選択肢、繰り返しから正規表現パターンを組み立てました。また、grep で行の選択と抽出を行い、sed で変換結果を確認して保存し、awk でフィールドを意識した選択と集計を行いました。次のチャレンジでは、完全なコマンド列を示されていない状態で、これらの考え方を適用します。