404 Not Found

404 Not Found


nginx

Rの正規表現:grep、str_match、stringrに関する完全ガイド

前回のレッスンでは、文字列操作の基本を学びましたが、実際のシナリオの80%ではパターンマッチングが必要となります。これは、固定された文字列と照合するのではなく、「特定のパターンに従う文字列」と照合するものです。これこそが正規表現(regex)の真価です。

このレッスンを修了すると、Rの正規表現を使用して、電話番号の検証、メールアドレスの抽出、URLの解析、ログの分析、テキストマイニングを行うことができるようになります。

1. 学習内容



2. ログ分析に関する話

(1) 課題:100万件ものログエントリをどう扱うか

アリスは運用エンジニアで、1 GBのNginxログからすべての404エラーのURLを抽出する必要があります:

192.168.1.1 - - [15/Jan/2024:10:30:45 +0800] "GET /api/users/123 HTTP/1.1" 404 512 192.168.1.2 - - [15/Jan/2024:10:30:46 +0800] "POST /api/login HTTP/1.1" 200 1024 192.168.1.3 - - [15/Jan/2024:10:30:47 +0800] "GET /api/products/456 HTTP/1.1" 404 256 ...

@,000行の中から404 error all URLsを探そうとしたら、手が疲れてしまうだろう――

(2) Rを用いた解決策

R
library(stringr)

# 1. Read the log
log <- readLines("nginx.log")

# 2. Match using regular expressions 404 Incorrect URL(One line)
pattern <- '"GET (\\S+) HTTP.*" 404'
urls_404 <- str_match(log, pattern)[, 2]

# 3. Track Traffic
url_table <- table(urls_404)
sort(url_table, decreasing = TRUE)[1:10]

たった3行のコードで100万件のログエントリを処理。これこそが正規表現の威力の証です。



3. 正規表現の基本構文

(1) 四大元素

100%
graph TB
    A[Regular Expressions] --> B[Literal characters<br/>abc 123]
    A --> C[Metacharacter<br/>. \\d \\s]
    A --> D[Measure Words<br/>* + ? {n}]
    A --> E[Anchor<br/>^ $ \\b]
    
    style A fill:#fff3cd
    style B fill:#cce5ff
    style C fill:#d4edda
    style D fill:#f8d7da
    style E fill:#e1d4ff

(2) リテラル文字

R
# Exact match of literal characters
"cat"  # Matches "cat"
"123"  # Matches "123"

(3) メタ文字 (コア)

メタ文字 意味 一致条件
. 任意の単一文字(改行を除く) a.c "abc", "axc", "a9c"
\d 数字 [0-9] \d+ "123", "9"
\D 非数値 \D+ "abc", "—"
\s スペース (スペース/タブ/改行) \s+ " ", "\t"
\w 英数字およびアンダースコア \w+ "abc_123"
\W なし \w \W+ "—", "@"
[abc] 文字(a、b、cのいずれか) [aeiou] 任意の母音
[^abc] 文字以外 [^0-9] 数値以外
[a-z] 範囲 [a-z] 任意の小文字
⚠️ 注意:Rの文字列において、\はエスケープシーケンスであるため、正規表現 \d はRでは "\\d" と記述する必要があります。

(4) 量詞

量詞 意味 対応
* 0回以上 ab* "a", "ab", "abb"
+ 1回以上 ab+ "ab"、"abb"("a"とは一致しない)
? 0 回または 1 回 ab? "a", "ab"
{n} ちょうど n 回 \d{3} "123"
{n,} 少なくとも n 回 \d{2,} "12", "1234"
{n,m} nからm回 \d{2,4} "12", "1234"

(5) アンカー

アンカー 意味 一致
^ 文字列の先頭 ^Hello "Hello..."
$ 文字列の終わり World$ "...World"
\b 単語の境界 \bcat\b 「cat」(「concatenate」とは一致しない)


4. グループ化とキャプチャ

(1) キャプチャグループ (...)

R
# Extract the year, month, and day of the date
str_match("2024-01-15", "(\\d{4})-(\\d{2})-(\\d{2})")
#      [,1]         [,2]   [,3]   [,4]
# [1,] "2024-01-15" "2024" "01"   "15"

# The first column is an exact match,Below are the capture groups

(2) 後方参照 \1

R
# Match Duplicate Words(e.g. "the the")
str_detect("the the cat", "\\b(\\w+)\\s+\\1\\b")
# [1] TRUE

# Match XML/HTML Tags
str_detect("<div>content</div>", "<(\\w+)>.*</\\1>")
# [1] TRUE

(3) 非捕捉群 (?:...)

R
# Uncaptured groups(Use ?:)
str_match("John, Smith", "(\\w+)(?:,\\s+)(\\w+)")
# [,1]         [,2]   [,3]
# "John, Smith" "John" "Smith"  ← Commas and spaces are not grouped separately


5. 貪欲法と非貪欲法

(1) 標準的な貪欲法

R
# Default Greedy:.* Match as many as possible
str_match("<b>text1</b><b>text2</b>", "<b>(.*)</b>")
# [,1]                          [,2]
# "<b>text1</b><b>text2</b>"    "text1</b><b>text2"
#                                 ↑ Greedy matching until the last one </b>

(2) 非貪欲法 *?

R
# Add ? to change to "non-greedy":.*? Match as few as possible
str_match("<b>text1</b><b>text2</b>", "<b>(.*?)</b>")
# [,1]            [,2]
# "<b>text1</b>"  "text1"  ← Matched the first one </b>
💡 ヒント: HTMLの解析には正規表現を使わないでください。専用のパーサー(xml2、第13課)を使用してください。正規表現は、単純なパターンマッチングに最も適しています。



6. stringr 正規表現関数

(1) 関数クイックリファレンス表

関数 目的 戻り値
str_detect() 一致 論理ベクトル
str_extract() 最初の一致を抽出 ベクトル
str_extract_all() 一致するものをすべて抽出 リスト
str_match() 最初の一致とキャプチャグループを抽出 行列
str_match_all() すべての一致を抽出 + キャプチャグループ リスト
str_replace() 最初の一致を置換 ベクトル
str_replace_all() すべての一致を置換 Vector
str_split() 試合別 一覧

(2) str_detect() による検出

R
# Which strings start with "a" Introduction
str_detect(c("apple", "banana", "cherry"), "^a")
# [1]  TRUE FALSE FALSE

# Contains numbers
str_detect(c("abc", "a1c", "123"), "\\d")
# [1] FALSE  TRUE  TRUE

(3) str_extract() による抽出

R
# Extract the number
str_extract(c("abc 123", "def 456", "no digits"), "\\d+")
# [1] "123"  "456"  NA

# Extract all numbers(List)
str_extract_all(c("a1b2c3", "no digits"), "\\d")
# [[1]] "1" "2" "3"
# [[2]] character(0)

(4) str_match() のキャプチャグループ

R
# Extraction Date: Year/Month/Day
str_match("2024-01-15", "(\\d{4})-(\\d{2})-(\\d{2})")
#      [,1]         [,2]   [,3]   [,4]
# [1,] "2024-01-15" "2024" "01"   "15"

# Vectorization
dates <- c("2024-01-15", "2024-02-20", "2024-03-25")
str_match(dates, "(\\d{4})-(\\d{2})-(\\d{2})")
#      [,1]         [,2]   [,3]   [,4]
# [1,] "2024-01-15" "2024" "01"   "15"
# [2,] "2024-02-20" "2024" "02"   "20"
# [3,] "2024-03-25" "2024" "03"   "25"

(5) str_replace_all() による置換

R
# Replace all numbers with X
str_replace_all("abc 123 def 456", "\\d+", "X")
# [1] "abc X def X"

# Complex Replacements:Using capture groups
str_replace_all("John Smith", "(\\w+) (\\w+)", "\\2, \\1")
# [1] "Smith, John"  ← Last Name First


7. 実践編:一般的な検証パターン

(1) さまざまな検証用正規表現

R
# Cell phone number
phone_pat <- "^1[3-9]\\d{9}$"

# Email
email_pat <- "^[\\w.+-]+@[\\w.-]+\\.[a-zA-Z]{2,}$"

# ID Number(18 bit)
id_pat <- "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$"

# URL
url_pat <- "^https?://[\\w.-]+(:\\d+)?(/\\S*)?$"

# IPv4
ipv4_pat <- "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$"

# Date
date_pat <- "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"

# Test
str_detect("13800001234", phone_pat)    # TRUE
str_detect("user@example.com", email_pat)  # TRUE
str_detect("110101199003078888", id_pat)  # TRUE
str_detect("https://example.com", url_pat)  # TRUE

(2) URLの一部を抽出する

R
url <- "https://www.example.com:8080/path/to/page?query=1#section"

# Extraction Agreement,Domain Name,Port,Path,Parameters
pattern <- "^(https?)://([^:/]+)(?::(\\d+))?(/[^?#]*)?(?:\\?([^#]*))?(?:#(.*))?$"
matches <- str_match(url, pattern)
# [,1]                            [,2]      [,3]              [,4]   [,5]            [,6]      [,7]
# "https://..."                    "https"   "www.example.com" "8080" "/path/to/page" "query=1" "section"


8. 実践:Nginxのログの分析

以下は、このレッスンで取り上げた正規表現の概念をすべて結びつけた完全なワークフローの例です。

▶ サンプル:Nginxのアクセスログの分析

R
# ============================================
# Nginx Access Log Analysis
# Features:Parse using regular expressions @,000 rows ofLog,Key Statistical Indicators
# ============================================

library(stringr)
library(dplyr)

# 1. Simulation 1000 row Nginx Log(For use in actual projects readLines Read File)
set.seed(42)
n <- 1000
ips <- paste0("192.168.", sample(1:255, n, replace = TRUE), ".",
              sample(1:255, n, replace = TRUE))
methods <- sample(c("GET", "POST", "PUT", "DELETE"), n, replace = TRUE,
                  prob = c(0.7, 0.2, 0.05, 0.05))
paths <- paste0("/api/", sample(c("users", "products", "orders", "login"),
                                n, replace = TRUE), "/",
                sample(1:1000, n, replace = TRUE))
status_codes <- sample(c(200, 201, 301, 400, 404, 500), n, replace = TRUE,
                       prob = c(0.7, 0.05, 0.05, 0.05, 0.1, 0.05))
sizes <- sample(100:5000, n)

# Concatenate into log lines
log_lines <- sprintf(
  '%s - - [15/Jan/2024:10:30:%02d +0800] "%s %s HTTP/1.1" %d %d',
  ips, sample(0:59, n, replace = TRUE), methods, paths, status_codes, sizes
)

# 2. Write to the log file
writeLines(log_lines, "nginx_demo.log")
cat("Generated 1000 Transaction Log\n")

# 3. Parsing Logs with Regular Expressions
# Format:IP - - [Time] "Methods URL Agreement" Status Code Size
log_pattern <- '^(\\S+) - - \\[([^\\]]+)\\] "(\\S+) (\\S+) (\\S+)" (\\d+) (\\d+)$'

# Extract all capture groups
matches <- str_match(log_lines, log_pattern)
colnames(matches) <- c("full", "ip", "time", "method", "path", "protocol",
                       "status", "size")

# Rotate DataFrame
logs <- as_tibble(matches) |> select(-full) |>
  mutate(
    status = as.integer(status),
    size = as.integer(size)
  )
▶ 試してみよう

cat("\n=== First 6 Analysis results (entries) ===\n") print(head(logs, 6))

R
# 4. Analysis 1:Status Code Distribution
cat("\n=== Status Code Distribution ===\n")
status_summary <- logs |>
  group_by(status) |>
  summarise(Number of times = n(), Percentage = round(n() / nrow(logs) * 100, 2)) |>
  arrange(desc(Number of times))
print(status_summary)

# 5. Analysis 2:Find 404 All Errors URL
cat("\n=== 404 Error Top 5 ===\n")
errors_404 <- logs |>
  filter(status == 404) |>
  group_by(path) |>
  summarise(Number of times = n()) |>
  arrange(desc(Number of times)) |>
  head(5)
print(errors_404)

# 6. Analysis 3:Each IP traffic
cat("\n=== Top 5 Active IP ===\n")
top_ips <- logs |>
  group_by(ip) |>
  summarise(
    Number of visits = n(),
    Number of errors = sum(status >= 400),
    Total Traffic = sum(size)
  ) |>
  arrange(desc(Number of visits)) |>
  head(5)
print(top_ips)

# 7. Analysis 4:Hourly traffic
cat("\n=== Visits per minute(First 10 minutes)===\n")
logs <- logs |>
  mutate(minute = str_sub(time, 15, 16))

per_minute <- logs |>
  group_by(minute) |>
  summarise(Number of visits = n(), Number of errors = sum(status >= 400)) |>
  head(10)
print(per_minute)

# 8. Retrieve Email Address(If the log contains an email address)
text_with_emails <- "Contact alice@example.com or bob@test.org for support."
email_pat <- "[\\w.+-]+@[\\w.-]+\\.[a-zA-Z]{2,}"
emails <- str_extract_all(text_with_emails, email_pat)
cat("\n=== Extracted email addresses ===\n")
print(emails)

# 9. Cleanup
file.remove("nginx_demo.log")

期待される出力(抜粋):

TEXT
=== Status Code Distribution ===
# A tibble: 6 × 3
  status  Number of times Percentage
   <int> <int> <dbl>
1    200   698    70
2    404   105    10
3    201    52     5
...

=== Top 5 Active IP ===
# A tibble: 5 × 4
  ip           Number of visits Number of errors Total Traffic
  <chr>            <int>  <int>  <int>
1 192.168.97.1        8      1   18499
2 192.168.52.244      7      2   18438
...

❓ よくある質問

Q \\d なぜバックスラッシュが2つあるのですか?
A Rの文字列 "\\d" は、実際には正規表現 \d です。Rは \\ を単一の \ として解析します。同様に、"\\s"も実際には正規表現\sです。
Q 漢字はどのように扱えばよいですか?
A R 4.x UTF-8 では、リテラル [\u4e00-\u9fff] または "Chinese" を直接使用してください。GBK ファイルの場合は、まず UTF-8 に変換してください。

📖 まとめ


📝 練習問題

  1. 基本問題:正規表現を使用して、文字列 "Price: $99.50, Quantity: 3 pcs, Total: $298.50" からすべての金額("Price: $99.50, Quantity: 3 pcs, Total: $298.50" の形式)を抽出し、数値のリストを出力してください。

  2. 基本問題:正規表現を用いて、以下のデータが電子メールの形式に準拠しているかどうかを検証してください:① user@example.comuser.name+tag@sub.example.co.ukinvalid@@example.com。ブール値のベクトルを出力してください。

  3. 基本的な問題:正規表現を使用して、"Today is 2024-01-15, weather Sunny; Tomorrow is 2024-01-16, Cloudy" からすべての日付を抽出し、文字ベクトルとして出力してください。

  4. 応用演習:注文番号、金額、日付、商品名を含む「注文確認メール」を100通シミュレートし、正規表現を使用してこれら4つのフィールドをデータフレームに抽出するとともに、各フィールドが正常に抽出されたことを確認してください。

  5. 課題:Nginxのアクセスログ1,000行を読み込み、正規表現を使用して以下の処理を行うスクリプトを作成してください。① 各行のフィールド(IP/時刻/メソッド/URL/ステータスコード/サイズ)を解析する。② 4xxエラーが発生したURLの上位10件を一覧表示する。③ アクセス頻度が最も高いIPアドレスを特定する。④ 1時間あたりの平均トラフィック量を算出する。スクリーンショットを撮影し、分析結果を保存してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%