Sscanf: примеры (PHP)

Разбор строк функцией sscanf в PHP на практике
Раздел: Ввод данных
sscanf(string string, string format [, mixed &...vars]): mixed

Основы функции sscanf

Назначение и принцип работы

Функция sscanf() выполняет форматированный разбор строки, считывая данные из неё согласно заданному шаблону. Эта функция часто применяется для извлечения структурированных данных из текста, например, логов, конфигурационных строк или пользовательского ввода, когда известно ожидаемое форматирование.

Синтаксис и параметры
mixed sscanf(string $str, string $format, mixed &...$vars)

Функция принимает следующие аргументы:

  • $str (string) – исходная строка для разбора. Обязательный параметр.
  • $format (string) – строка формата, определяющая, как интерпретировать данные в $str. Состоит из спецификаторов преобразования, начинающихся с символа процента (%). Обязательный параметр.
  • &...$vars (mixed) – необязательные переменные, передаваемые по ссылке. В них будут сохранены извлечённые значения. Если параметры не указаны, функция возвращает массив.
Спецификаторы формата

Наиболее используемые спецификаторы:

  • %d – знаковое целое число (decimal).
  • %u – беззнаковое целое число.
  • %f – число с плавающей точкой (float).
  • %s – строка (string). Чтение останавливается на пробельном символе.
  • %[^...] – чтение строки, состоящей из символов, не указанных в скобках.
  • %c – чтение одного символа, включая пробельные.

Между % и спецификатором можно указывать:

  • * – подавление присваивания (значение считывается, но не сохраняется).
  • Число – максимальная ширина поля.

Простые примеры использования

Базовый разбор даты
$dateString = "2023-12-25";
$result = sscanf($dateString, "%d-%d-%d", $year, $month, $day);
echo "Год: $year, Месяц: $month, День: $day";
print_r($result);
Год: 2023, Месяц: 12, День: 25
Array
(
    [0] => 2023
    [1] => 12
    [2] => 25
)
Использование ширины поля и подавления присваивания
$log = "ERROR 404: Not Found";
// Считываем код ошибки, но подавляем сохранение слова "404", затем считываем сообщение.
sscanf($log, "%s %*d: %[^"]", $level, $message);
echo "Уровень: $level\n";
echo "Сообщение: $message";
Уровень: ERROR
Сообщение: Not Found
Чтение строки до определённых символов
$data = "Иванов,Петр,Сергеевич";
sscanf($data, "%[^,],%[^,],%s", $lastName, $firstName, $patronymic);
echo "Фамилия: $lastName, Имя: $firstName, Отчество: $patronymic";
Фамилия: Иванов, Имя: Петр, Отчество: Сергеевич
Разбор чисел с различными основаниями
$input = "255 0xFF 0377";
// %d - десятичное, %x - шестнадцатеричное, %o - восьмеричное
sscanf($input, "%d %x %o", $dec, $hex, $oct);
echo "Десятичное: $dec, Шестнадцатеричное: $hex, Восьмеричное: $oct";
Десятичное: 255, Шестнадцатеричное: 255, Восьмеричное: 255

Альтернативные функции в PHP

explode()

Разбивает строку на массив по заданному разделителю. Проще в использовании для равномерно разделённых данных, но не подходит для сложных или вариативных шаблонов.

$str = "яблоко,груша,вишня";
$array = explode(",", $str);
print_r($array);
Array
(
    [0] => яблоко
    [1] => груша
    [2] => вишня
)
preg_match() и регулярные выражения

Обеспечивают максимальную гибкость для сложных шаблонов поиска и извлечения данных. preg_match() мощнее, но синтаксис регулярных выражений сложнее для чтения и написания по сравнению с простыми шаблонами sscanf().

$str = "Температура: +25.5C";
preg_match('/[+-]?\d+(?:\.\d+)?/', $str, $matches);
echo $matches[0];
+25.5
strtok() и substr()

Функции для посимвольного или поточного разбора строк. Требуют больше кода для структурированных данных, но дают полный контроль на низком уровне.

sscanf() предпочтительнее, когда формат строки хорошо определён и относительно прост. Для жёстко структурированных данных (как CSV, фиксированные поля) explode() или fgetcsv() могут быть проще. Для очень сложных или нерегулярных шаблонов лучше подходят регулярные выражения.

Аналоги в других языках программирования

Python: метод str.format() и модуль re

Прямого аналога sscanf() в стандартной библиотеке Python нет. Для похожего поведения используют re.match() или re.search() с группами захвата.

import re
log_line = "ERROR 404: Not Found"
match = re.match(r"(\w+) (\d+): (.+)", log_line)
if match:
    level, code, message = match.groups()
    print(f"Уровень: {level}, Код: {code}, Сообщение: {message}")
Уровень: ERROR, Код: 404, Сообщение: Not Found

Sscanf в Javascript

В JavaScript отсутствует встроенная функция, аналогичная sscanf(). Для разбора строк используют String.prototype.match() с регулярными выражениями или комбинацию методов split(), slice().

let data = "2023-12-25";
let regex = /^(\d{4})-(\d{2})-(\d{2})$/;
let match = data.match(regex);
if (match) {
    let [, year, month, day] = match;
    console.log(`Год: ${year}, Месяц: ${month}, День: ${day}`);
}
Год: 2023, Месяц: 12, День: 25
C / C++: функция sscanf()

Функция sscanf() в языке C имеет почти идентичный синтаксис и семантику, что и в PHP, так как PHP позаимствовал её оттуда. Основное отличие в строгой типизации и работе с указателями в C.

#include <stdio.h>
int main() {
    char str[] = "42 3.14 hello";
    int i;
    float f;
    char s[20];
    sscanf(str, "%d %f %s", &i, &f, s);
    printf("%d %.2f %s\n", i, f, s);
    return 0;
}
42 3.14 hello
MySQL: Функция STR_TO_DATE()

В контексте СУБД MySQL для разбора строк в даты существует функция STR_TO_DATE(), которая использует спецификаторы формата, схожие с sscanf(), но специализирована под временные типы данных.

SELECT STR_TO_DATE('25,12,2023', '%d,%m,%Y');
2023-12-25

Типичные ошибки и проблемы

Несоответствие количества переменных и спецификаторов

Если передано меньше переменных, чем спецификаторов в формате, требующих присваивания, возникает предупреждение.

$str = "10 20 30";
// Передаётся только две переменные для трёх спецификаторов.
$result = @sscanf($str, "%d %d %d", $a, $b); // Используем @, чтобы скрыть предупреждение для примера.
echo "a=$a, b=$b\n";
print_r($result);
a=10, b=20
Array
(
    [0] => 10
    [1] => 20
    [2] => 30
)

Функция всё равно возвращает все считанные значения в массиве.

Неполное совпадение строки с форматом

Когда начало строки соответствует формату, но далее идёт несоответствие, sscanf() обрабатывает только совпавшую часть.

$str = "Число: 123текст";
$count = sscanf($str, "Число: %d", $number);
echo "Считано элементов: $count\n";
echo "Число: $number\n";
Считано элементов: 1
Число: 123
Использование с пользовательским вводом без валидации

Функция доверяет формату. Неправильно построенный формат или неожиданный ввод могут привести к неверному разбору или неполучению данных.

$userInput = "100 apples";
// Ожидается два числа, но второе - строка.
sscanf($userInput, "%d %d", $count, $price);
echo "Count: $count, Price: $price (некорректно)";
Count: 100, Price: (некорректно)

Значение $price останется null или предыдущим, если переменная была определена.

Ожидание обязательного пробельного символа

Спецификатор %s останавливается на любом пробельном символе. Для чтения строк, содержащих пробелы, нужно использовать другой спецификатор, например, %[^\n].

$str = "Hello World 123";
sscanf($str, "%s %s %d", $word1, $word2, $num);
echo "$word1, $word2, $num"; // World будет в отдельной переменной.
Hello, World, 123

Изменения в новых версиях PHP

Функция sscanf() остаётся стабильной на протяжении многих версий PHP. Ключевые изменения касаются в основном обработки ошибок и производительности.

  • В PHP 8.0 была изменена сигнатура функции с точки зрения внутреннего описания аргументов, но это не повлияло на пользовательский код.
  • В более ранних версиях (до PHP 7.0) различия были минимальны. Важно отметить, что функция всегда возвращала null в случае, если строка не могла быть обработана согласно формату, или массив/количество присвоенных значений в случае успеха. Это поведение сохраняется.
  • В PHP 8 все функции работают в более строгом типизированном контексте, но для sscanf() это не привело к заметным изменениям в использовании.

Расширенные и специальные примеры

Обработка строк с переменным числом полей
Пример php
function parseLogLine($line) {
    // Пытаемся разобрать как минимум уровень и код, сообщение может быть составным.
    $parsed = sscanf($line, "%s %d: %[^"]");
    if ($parsed[0] !== null) {
        return [
            'level' => $parsed[0],
            'code' => $parsed[1] ?? null,
            'message' => $parsed[2] ?? ''
        ];
    }
    return null;
}
$lines = [
    "INFO 100: Запрос обработан",
    "ERROR 500: Internal Server Error",
    "WARNING: Неизвестный параметр"
];
foreach ($lines as $line) {
    print_r(parseLogLine($line));
    echo "\n---\n";
}
Array
(
    [level] => INFO
    [code] => 100
    [message] => Запрос обработан
)
---
Array
(
    [level] => ERROR
    [code] => 500
    [message] => Internal Server Error
)
---
Array
(
    [level] => WARNING
    [code] => 
    [message] => 
)
---
Использование для валидации и преобразования данных
Пример php
function isValidIpv4($ip) {
    // %d.%d.%d.%d - каждый октет должен быть числом.
    // %1d ограничивает ширину октета, предотвращая разбор "192.168.001.001" как одного числа 192168001001.
    $count = sscanf($ip, "%3d.%3d.%3d.%3d", $a, $b, $c, $d);
    return $count === 4 && $a >= 0 && $a <= 255 && $b >= 0 && $b <= 255 && $c >= 0 && $c <= 255 && $d >= 0 && $d <= 255;
}
$testIps = ["192.168.1.1", "256.0.0.1", "not.an.ip"];
foreach ($testIps as $ip) {
    echo "$ip: " . (isValidIpv4($ip) ? 'valid' : 'invalid') . "\n";
}
192.168.1.1: valid
256.0.0.1: invalid
not.an.ip: invalid
Разбор сложных составных строк
Пример php
$configLine = "memory_limit=256M;upload_max_filesize=20M;max_execution_time=30";
// Используем sscanf в цикле с strtok для имитации парсера.
$settings = [];
$token = strtok($configLine, ";");
while ($token !== false) {
    // Разбираем каждую пару ключ=значение.
    sscanf($token, "%[^=]=%s", $key, $value);
    $settings[$key] = $value;
    $token = strtok(";");
}
print_r($settings);
Array
(
    [memory_limit] => 256M
    [upload_max_filesize] => 20M
    [max_execution_time] => 30
)
Чтение данных фиксированной ширины
Пример php
// Предположим, данные имеют фиксированные колонки: ID (3 символа), Name (10 символов), Value (5 символов).
$fixedWidthData = "001ProductA 12.50\n002ItemB    99.99";
$lines = explode("\n", $fixedWidthData);
foreach ($lines as $line) {
    // Используем ширину поля: %3s, %10s, %5s
    sscanf($line, "%3s%10s%5s", $id, $name, $value);
    $name = rtrim($name); // Убираем пробелы фиксированной ширины.
    echo "ID: $id, Name: '$name', Value: $value\n";
}
ID: 001, Name: 'ProductA', Value: 12.50
ID: 002, Name: 'ItemB', Value: 99.99
Комбинация с list() для удобного присваивания
Пример php
$coords = "(45.123, -90.456)";
// Подавляем скобки и запятую с помощью %*c или просто считываем числа между ними.
list($lat, $lon) = sscanf($coords, "(%f, %f)");
echo "Широта: $lat, Долгота: $lon";
Широта: 45.123, Долгота: -90.456

PHP sscanf function comments

En
Sscanf Parses input from a string according to a format