[PHP]sscanf関数を完全解説!文字列から値を解析・抽出する方法

PHP

こんにちは!今回は、PHPの標準関数であるsscanf()について詳しく解説していきます。sprintf()の逆の操作で、フォーマット指定に従って文字列から値を抽出できる便利な関数です!

sscanf関数とは?

sscanf()関数は、フォーマット文字列に従って文字列を解析し、値を抽出する関数です。

sprintf()が値を文字列にフォーマットするのに対し、sscanf()は文字列から値を取り出します。ログファイルの解析や、構造化されたテキストデータの処理に非常に便利です!

基本的な構文

// 方法1: 変数に代入
sscanf(string $string, string $format, mixed &...$vars): int|null

// 方法2: 配列として取得
sscanf(string $string, string $format): array|null
  • $string: 解析対象の文字列
  • $format: フォーマット文字列(変換指定子を含む)
  • $vars: 抽出した値を格納する変数(参照渡し)
  • 戻り値:
    • 引数指定時: 代入された引数の数(int)または失敗時はnull
    • 引数省略時: 抽出された値の配列(array)または失敗時はnull

主なフォーマット指定子

型指定子説明
%s文字列(空白まで)“hello world” → “hello”
%d整数(10進数)“123” → 123
%f浮動小数点数“3.14” → 3.14
%x16進数“ff” → 255
%o8進数“10” → 8
%c1文字“A” → “A”
%[...]文字集合(正規表現的)“%[0-9]” → 数字のみ
%[^...]文字集合の否定“%[^,]” → カンマ以外

基本的な使用例

シンプルな解析

// 方法1: 変数に代入
$str = "田中太郎 30";
sscanf($str, "%s %d", $name, $age);

echo "名前: {$name}\n";  // 田中太郎
echo "年齢: {$age}\n";   // 30

// 方法2: 配列として取得
$result = sscanf($str, "%s %d");
print_r($result);
// Array ( [0] => 田中太郎 [1] => 30 )

日付の解析

$dateStr = "2024-02-13";
sscanf($dateStr, "%d-%d-%d", $year, $month, $day);

echo "年: {$year}\n";    // 2024
echo "月: {$month}\n";   // 2
echo "日: {$day}\n";     // 13

// または
list($year, $month, $day) = sscanf($dateStr, "%d-%d-%d");
echo "日付: {$year}年{$month}月{$day}日\n";

複数形式の解析

// IPアドレスの解析
$ip = "192.168.1.100";
sscanf($ip, "%d.%d.%d.%d", $a, $b, $c, $d);
echo "セグメント: {$a}, {$b}, {$c}, {$d}\n";
// セグメント: 192, 168, 1, 100

// カラーコードの解析
$color = "#FF5733";
sscanf($color, "#%2x%2x%2x", $r, $g, $b);
echo "RGB: ({$r}, {$g}, {$b})\n";
// RGB: (255, 87, 51)

実践的な使用例

例1: ログファイルの解析

class LogParser {
    public static function parseApacheLog($line) {
        // Apache Combined Log Format
        // 192.168.1.1 - - [13/Feb/2024:10:30:45 +0900] "GET /index.html HTTP/1.1" 200 1234
        
        $pattern = '%s - - [%[^]]] "%s %s %[^"]" %d %d';
        
        $result = sscanf($line, $pattern);
        
        if ($result === null || count($result) < 6) {
            return null;
        }
        
        return [
            'ip' => $result[0],
            'datetime' => $result[1],
            'method' => $result[2],
            'path' => $result[3],
            'status' => $result[4],
            'size' => $result[5]
        ];
    }
    
    public static function parseCustomLog($line) {
        // [2024-02-13 10:30:45] INFO: User login - UserID: 123
        
        sscanf($line, "[%[^]]] %[^:]: %[^-] - UserID: %d",
            $timestamp, $level, $message, $userId
        );
        
        return [
            'timestamp' => $timestamp,
            'level' => trim($level),
            'message' => trim($message),
            'user_id' => $userId
        ];
    }
    
    public static function parseErrorLog($line) {
        // [error] 2024/02/13 10:30:45 [client 192.168.1.1] File not found: /var/www/html/test.php
        
        $pattern = '[%[^]]] %d/%d/%d %d:%d:%d [client %[^]]] %[^:]';
        
        $result = sscanf($line, $pattern);
        
        if (!$result) {
            return null;
        }
        
        return [
            'level' => $result[0],
            'year' => $result[1],
            'month' => $result[2],
            'day' => $result[3],
            'hour' => $result[4],
            'minute' => $result[5],
            'second' => $result[6],
            'client_ip' => $result[7],
            'message' => $result[8]
        ];
    }
}

// 使用例
$apacheLog = '192.168.1.1 - - [13/Feb/2024:10:30:45 +0900] "GET /index.html HTTP/1.1" 200 1234';
$parsed = LogParser::parseApacheLog($apacheLog);
print_r($parsed);

$customLog = '[2024-02-13 10:30:45] INFO: User login - UserID: 123';
$parsed = LogParser::parseCustomLog($customLog);
print_r($parsed);

例2: CSVやTSVの解析

class DataParser {
    public static function parseCSVLine($line) {
        // 簡易的なCSV解析(クォートなし)
        $values = sscanf($line, "%[^,],%[^,],%[^,],%d");
        
        return [
            'name' => $values[0] ?? '',
            'email' => $values[1] ?? '',
            'city' => $values[2] ?? '',
            'age' => $values[3] ?? 0
        ];
    }
    
    public static function parseKeyValue($line, $separator = '=') {
        // key=value 形式の解析
        $pattern = "%[^{$separator}]{$separator}%[^\n]";
        
        $result = sscanf($line, $pattern);
        
        if ($result && count($result) === 2) {
            return [
                'key' => trim($result[0]),
                'value' => trim($result[1])
            ];
        }
        
        return null;
    }
    
    public static function parseIniLine($line) {
        // INIファイルの行を解析
        $line = trim($line);
        
        // コメント行をスキップ
        if (empty($line) || $line[0] === ';' || $line[0] === '#') {
            return null;
        }
        
        // セクション
        if ($line[0] === '[') {
            $result = sscanf($line, "[%[^]]]");
            return ['type' => 'section', 'value' => $result[0]];
        }
        
        // キー=値
        $result = sscanf($line, "%[^=]=%[^\r\n]");
        if ($result && count($result) === 2) {
            return [
                'type' => 'pair',
                'key' => trim($result[0]),
                'value' => trim($result[1])
            ];
        }
        
        return null;
    }
}

// 使用例
$csvLine = "田中太郎,tanaka@example.com,東京,30";
$data = DataParser::parseCSVLine($csvLine);
print_r($data);

$kvLine = "username=tanaka";
$kv = DataParser::parseKeyValue($kvLine);
print_r($kv);

$iniLines = [
    "[database]",
    "host=localhost",
    "port=3306",
    "; this is a comment",
    "name=mydb"
];

foreach ($iniLines as $line) {
    $parsed = DataParser::parseIniLine($line);
    if ($parsed) {
        print_r($parsed);
    }
}

例3: 座標・測定値の解析

class CoordinateParser {
    public static function parseLatLng($str) {
        // 緯度経度の解析
        // 例: "35.6895,139.6917" または "35.6895N,139.6917E"
        
        $result = sscanf($str, "%f,%f");
        
        if ($result && count($result) === 2) {
            return [
                'latitude' => $result[0],
                'longitude' => $result[1]
            ];
        }
        
        // 方位付き形式
        $result = sscanf($str, "%f%c,%f%c");
        
        if ($result && count($result) === 4) {
            $lat = $result[0] * ($result[1] === 'S' ? -1 : 1);
            $lng = $result[2] * ($result[3] === 'W' ? -1 : 1);
            
            return [
                'latitude' => $lat,
                'longitude' => $lng
            ];
        }
        
        return null;
    }
    
    public static function parseGPSData($line) {
        // GPS NMEA形式の簡易解析
        // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
        
        $pattern = "\$GPGGA,%f,%f,%c,%f,%c,%d,%d,%f,%f";
        
        $result = sscanf($line, $pattern);
        
        if (!$result || count($result) < 9) {
            return null;
        }
        
        return [
            'time' => $result[0],
            'latitude' => $result[1] / 100,  // 度分形式を10進数に
            'lat_dir' => $result[2],
            'longitude' => $result[3] / 100,
            'lng_dir' => $result[4],
            'quality' => $result[5],
            'satellites' => $result[6],
            'hdop' => $result[7],
            'altitude' => $result[8]
        ];
    }
    
    public static function parseMeasurement($str) {
        // 測定値の解析
        // 例: "Temperature: 25.5C" または "Pressure: 1013 hPa"
        
        $result = sscanf($str, "%[^:]: %f %s");
        
        if ($result && count($result) === 3) {
            return [
                'type' => $result[0],
                'value' => $result[1],
                'unit' => $result[2]
            ];
        }
        
        return null;
    }
}

// 使用例
$coords1 = "35.6895,139.6917";
print_r(CoordinateParser::parseLatLng($coords1));

$coords2 = "35.6895N,139.6917E";
print_r(CoordinateParser::parseLatLng($coords2));

$measurement = "Temperature: 25.5 C";
print_r(CoordinateParser::parseMeasurement($measurement));

例4: バージョン番号やIDの解析

class IdentifierParser {
    public static function parseVersion($version) {
        // セマンティックバージョニング
        // 例: "2.1.3" または "2.1.3-beta.1"
        
        $result = sscanf($version, "%d.%d.%d");
        
        if (!$result || count($result) < 3) {
            return null;
        }
        
        $parsed = [
            'major' => $result[0],
            'minor' => $result[1],
            'patch' => $result[2]
        ];
        
        // プレリリース情報があれば抽出
        if (strpos($version, '-') !== false) {
            $parts = explode('-', $version, 2);
            $parsed['prerelease'] = $parts[1];
        }
        
        return $parsed;
    }
    
    public static function parseProductCode($code) {
        // 商品コードの解析
        // 例: "ABC-12345-XL-RED"
        
        $result = sscanf($code, "%3s-%5d-%[^-]-%s");
        
        if (!$result || count($result) < 4) {
            return null;
        }
        
        return [
            'category' => $result[0],
            'id' => $result[1],
            'size' => $result[2],
            'color' => $result[3]
        ];
    }
    
    public static function parseISBN($isbn) {
        // ISBN-13の解析
        // 例: "978-4-12-345678-9"
        
        // ハイフンを削除
        $cleanIsbn = str_replace('-', '', $isbn);
        
        if (strlen($cleanIsbn) === 13) {
            $result = sscanf($cleanIsbn, "%3d%1d%2d%6d%1d");
            
            if ($result && count($result) === 5) {
                return [
                    'prefix' => $result[0],
                    'group' => $result[1],
                    'publisher' => $result[2],
                    'title' => $result[3],
                    'check' => $result[4]
                ];
            }
        }
        
        return null;
    }
    
    public static function parseMACAddress($mac) {
        // MACアドレスの解析
        // 例: "00:1A:2B:3C:4D:5E" または "00-1A-2B-3C-4D-5E"
        
        $mac = strtoupper($mac);
        
        // コロン区切り
        $result = sscanf($mac, "%2x:%2x:%2x:%2x:%2x:%2x");
        
        if (!$result || count($result) !== 6) {
            // ハイフン区切り
            $result = sscanf($mac, "%2x-%2x-%2x-%2x-%2x-%2x");
        }
        
        if ($result && count($result) === 6) {
            return [
                'bytes' => $result,
                'vendor' => sprintf("%02X:%02X:%02X", $result[0], $result[1], $result[2])
            ];
        }
        
        return null;
    }
}

// 使用例
$version = "2.1.3-beta.1";
print_r(IdentifierParser::parseVersion($version));

$productCode = "ABC-12345-XL-RED";
print_r(IdentifierParser::parseProductCode($productCode));

$isbn = "978-4-12-345678-9";
print_r(IdentifierParser::parseISBN($isbn));

$mac = "00:1A:2B:3C:4D:5E";
print_r(IdentifierParser::parseMACAddress($mac));

例5: ファイル名・パスの解析

class PathParser {
    public static function parseFilename($filename) {
        // ファイル名のパターン解析
        // 例: "document_2024-02-13_v1.2.pdf"
        
        $result = sscanf($filename, "%[^_]_%d-%d-%d_v%f.%s");
        
        if ($result && count($result) >= 6) {
            return [
                'basename' => $result[0],
                'year' => $result[1],
                'month' => $result[2],
                'day' => $result[3],
                'version' => $result[4],
                'extension' => $result[5]
            ];
        }
        
        return null;
    }
    
    public static function parseImageFilename($filename) {
        // 画像ファイル名のパターン
        // 例: "IMG_20240213_103045.jpg" または "photo_001_thumb_150x150.png"
        
        // パターン1: カメラ形式
        $result = sscanf($filename, "IMG_%8d_%6d.%s");
        if ($result && count($result) === 3) {
            return [
                'type' => 'camera',
                'date' => $result[0],
                'time' => $result[1],
                'extension' => $result[2]
            ];
        }
        
        // パターン2: サムネイル形式
        $result = sscanf($filename, "%[^_]_%d_thumb_%dx%d.%s");
        if ($result && count($result) === 5) {
            return [
                'type' => 'thumbnail',
                'basename' => $result[0],
                'number' => $result[1],
                'width' => $result[2],
                'height' => $result[3],
                'extension' => $result[4]
            ];
        }
        
        return null;
    }
    
    public static function parseLogFilename($filename) {
        // ログファイル名
        // 例: "app_2024-02-13.log" または "error_20240213_103045.log"
        
        $result = sscanf($filename, "%[^_]_%d-%d-%d.%s");
        
        if ($result && count($result) === 5) {
            return [
                'type' => $result[0],
                'year' => $result[1],
                'month' => $result[2],
                'day' => $result[3],
                'extension' => $result[4]
            ];
        }
        
        $result = sscanf($filename, "%[^_]_%8d_%6d.%s");
        
        if ($result && count($result) === 4) {
            return [
                'type' => $result[0],
                'date' => $result[1],
                'time' => $result[2],
                'extension' => $result[3]
            ];
        }
        
        return null;
    }
}

// 使用例
$filename1 = "document_2024-02-13_v1.2.pdf";
print_r(PathParser::parseFilename($filename1));

$filename2 = "IMG_20240213_103045.jpg";
print_r(PathParser::parseImageFilename($filename2));

$filename3 = "photo_001_thumb_150x150.png";
print_r(PathParser::parseImageFilename($filename3));

$filename4 = "app_2024-02-13.log";
print_r(PathParser::parseLogFilename($filename4));

例6: ネットワークデータの解析

class NetworkParser {
    public static function parseHTTPHeader($header) {
        // HTTPヘッダーの解析
        // 例: "HTTP/1.1 200 OK"
        
        $result = sscanf($header, "HTTP/%f %d %[^\r\n]");
        
        if ($result && count($result) === 3) {
            return [
                'version' => $result[0],
                'status_code' => $result[1],
                'status_text' => $result[2]
            ];
        }
        
        // ヘッダーフィールド
        // 例: "Content-Type: text/html; charset=UTF-8"
        $result = sscanf($header, "%[^:]: %[^\r\n]");
        
        if ($result && count($result) === 2) {
            return [
                'field' => trim($result[0]),
                'value' => trim($result[1])
            ];
        }
        
        return null;
    }
    
    public static function parseURL($url) {
        // URLの解析(簡易版)
        // 例: "https://example.com:8080/path/to/page?key=value"
        
        $result = sscanf($url, "%[^:]://%[^:]:%d/%[^?]?%s");
        
        if ($result && count($result) === 5) {
            return [
                'scheme' => $result[0],
                'host' => $result[1],
                'port' => $result[2],
                'path' => $result[3],
                'query' => $result[4]
            ];
        }
        
        // ポートなしの場合
        $result = sscanf($url, "%[^:]://%[^/]/%[^?]?%s");
        
        if ($result && count($result) === 4) {
            return [
                'scheme' => $result[0],
                'host' => $result[1],
                'path' => $result[2],
                'query' => $result[3]
            ];
        }
        
        return null;
    }
    
    public static function parseUserAgent($ua) {
        // User-Agentの簡易解析
        // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
        
        $result = sscanf($ua, "Mozilla/%f (%[^)]) %[^/]/%f");
        
        if ($result && count($result) === 4) {
            return [
                'mozilla_version' => $result[0],
                'platform' => $result[1],
                'engine' => $result[2],
                'engine_version' => $result[3]
            ];
        }
        
        return null;
    }
}

// 使用例
$httpStatus = "HTTP/1.1 200 OK";
print_r(NetworkParser::parseHTTPHeader($httpStatus));

$httpHeader = "Content-Type: text/html; charset=UTF-8";
print_r(NetworkParser::parseHTTPHeader($httpHeader));

$url1 = "https://example.com:8080/path/to/page?key=value";
print_r(NetworkParser::parseURL($url1));

$url2 = "https://example.com/path?query=test";
print_r(NetworkParser::parseURL($url2));

例7: センサーデータの解析

class SensorDataParser {
    public static function parseTemperatureReading($data) {
        // 温度センサーデータ
        // 例: "TEMP:25.5C HUMIDITY:60%"
        
        $temp = sscanf($data, "TEMP:%f%c HUMIDITY:%d%%");
        
        if ($temp && count($temp) === 3) {
            return [
                'temperature' => $temp[0],
                'temp_unit' => $temp[1],
                'humidity' => $temp[2]
            ];
        }
        
        return null;
    }
    
    public static function parseAccelerometer($data) {
        // 加速度センサーデータ
        // 例: "ACC:X=0.15,Y=-0.23,Z=9.81"
        
        $result = sscanf($data, "ACC:X=%f,Y=%f,Z=%f");
        
        if ($result && count($result) === 3) {
            return [
                'x' => $result[0],
                'y' => $result[1],
                'z' => $result[2],
                'magnitude' => sqrt(
                    $result[0]**2 + 
                    $result[1]**2 + 
                    $result[2]**2
                )
            ];
        }
        
        return null;
    }
    
    public static function parseBatteryStatus($data) {
        // バッテリー状態
        // 例: "BAT:85% VOLTAGE:3.7V TEMP:28C"
        
        $result = sscanf($data, "BAT:%d%% VOLTAGE:%f%c TEMP:%d%c");
        
        if ($result && count($result) === 5) {
            return [
                'percentage' => $result[0],
                'voltage' => $result[1],
                'voltage_unit' => $result[2],
                'temperature' => $result[3],
                'temp_unit' => $result[4]
            ];
        }
        
        return null;
    }
    
    public static function parseTimestampedData($data) {
        // タイムスタンプ付きデータ
        // 例: "[2024-02-13 10:30:45.123] SENSOR_01: VALUE=123.45"
        
        $pattern = "[%d-%d-%d %d:%d:%f] %[^:]: VALUE=%f";
        $result = sscanf($data, $pattern);
        
        if ($result && count($result) === 8) {
            return [
                'timestamp' => sprintf(
                    "%04d-%02d-%02d %02d:%02d:%06.3f",
                    $result[0], $result[1], $result[2],
                    $result[3], $result[4], $result[5]
                ),
                'sensor_id' => $result[6],
                'value' => $result[7]
            ];
        }
        
        return null;
    }
}

// 使用例
$tempData = "TEMP:25.5C HUMIDITY:60%";
print_r(SensorDataParser::parseTemperatureReading($tempData));

$accData = "ACC:X=0.15,Y=-0.23,Z=9.81";
print_r(SensorDataParser::parseAccelerometer($accData));

$batData = "BAT:85% VOLTAGE:3.7V TEMP:28C";
print_r(SensorDataParser::parseBatteryStatus($batData));

$tsData = "[2024-02-13 10:30:45.123] SENSOR_01: VALUE=123.45";
print_r(SensorDataParser::parseTimestampedData($tsData));

文字集合の指定

正規表現的なパターン

// 数字のみ抽出
$str = "ABC123DEF456";
$result = sscanf($str, "%[A-Z]%[0-9]%[A-Z]%[0-9]");
print_r($result);
// Array ( [0] => ABC [1] => 123 [2] => DEF [3] => 456 )

// カンマ以外を抽出(カンマ区切りCSV)
$csv = "田中,太郎,30,東京";
$result = sscanf($csv, "%[^,],%[^,],%[^,],%[^,]");
print_r($result);
// Array ( [0] => 田中 [1] => 太郎 [2] => 30 [3] => 東京 )

// 空白以外を抽出
$str = "hello world test";
$result = sscanf($str, "%[^ ] %[^ ] %[^ ]");
print_r($result);
// Array ( [0] => hello [1] => world [2] => test )

エラーハンドリング

class SafeParser {
    public static function parse($string, $format, $expectedCount) {
        $result = sscanf($string, $format);
        
        if ($result === null) {
            throw new Exception("解析に失敗しました");
        }
        
        if (count($result) !== $expectedCount) {
            throw new Exception(
                sprintf(
                    "期待される要素数: %d, 実際: %d",
                    $expectedCount,
                    count($result)
                )
            );
        }
        
        return $result;
    }
    
    public static function parseOrDefault($string, $format, $default = []) {
        $result = sscanf($string, $format);
        
        if ($result === null || empty($result)) {
            return $default;
        }
        
        return $result;
    }
}

// 使用例
try {
    $data = SafeParser::parse("田中 30", "%s %d", 2);
    print_r($data);
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}

// デフォルト値を使用
$data = SafeParser::parseOrDefault("invalid data", "%d %d", [0, 0]);
print_r($data);

戻り値の扱い

// パターン1: 変数に代入
$count = sscanf("田中 30", "%s %d", $name, $age);
echo "抽出数: {$count}\n";  // 2
echo "名前: {$name}, 年齢: {$age}\n";

// パターン2: 配列として取得
$data = sscanf("田中 30", "%s %d");
echo "名前: {$data[0]}, 年齢: {$data[1]}\n";

// パターン3: list()と組み合わせ
list($name, $age) = sscanf("田中 30", "%s %d");
echo "名前: {$name}, 年齢: {$age}\n";

preg_match()との比較

// sscanf()の場合
$str = "田中 30";
list($name, $age) = sscanf($str, "%s %d");

// preg_match()の場合
$str = "田中 30";
preg_match('/^(\S+) (\d+)$/', $str, $matches);
$name = $matches[1];
$age = $matches[2];

// sscanf()の利点:
// - シンプルで読みやすい
// - 型変換が自動(%d は整数に変換)
// - フォーマット指定が直感的

// preg_match()の利点:
// - より複雑なパターンマッチング
// - 柔軟性が高い
// - 条件付きマッチング

まとめ

sscanf()関数の特徴をまとめると:

できること:

  • フォーマット指定による文字列解析
  • 型の自動変換(文字列→整数、浮動小数点数など)
  • 複雑なパターンからの値抽出
  • ログファイルやデータファイルの解析

主な型指定子:

  • %s: 文字列
  • %d: 整数
  • %f: 浮動小数点数
  • %x: 16進数
  • %[...]: 文字集合
  • %[^...]: 文字集合の否定

戻り値の取得方法:

  • 変数に代入(参照渡し)
  • 配列として取得
  • list()との組み合わせ

推奨される使用場面:

  • ログファイルの解析
  • 構造化テキストデータの処理
  • 座標や測定値の抽出
  • ファイル名やIDのパース
  • センサーデータの解析

関連関数:

  • sprintf(): 値を文字列にフォーマット
  • fscanf(): ファイルから読み込んで解析
  • preg_match(): 正規表現でのマッチング

sscanf()は、固定フォーマットのテキストデータを扱う際に非常に便利です。正規表現よりもシンプルで読みやすく、型変換も自動で行われるため、適切な場面で使うことで効率的なコードを書くことができます!

タイトルとURLをコピーしました