PHP fscanf()函数

fscanf()函数用于从文件中按照指定格式读取数据,类似于C语言的fscanf函数,可以解析格式化字符串。

语法

mixed fscanf ( resource $handle , string $format [, mixed &$... ] )

参数说明

参数 描述
handle 文件指针资源,通常由fopen()函数创建
format 格式化字符串,指定如何解析输入数据
... 可选的可变参数,用于存储解析后的值(按引用传递)

格式说明符

说明符 描述 示例
%s 字符串(直到遇到空白字符) "hello"
%d 有符号十进制整数 123, -456
%u 无符号十进制整数 123, 456
%f, %F 浮点数 3.14, -2.5
%c 单个字符 'a', 'B'
%x, %X 十六进制整数 1a, FF
%o 八进制整数 777, 123
%% 字面百分号 %
%[^] 匹配字符集 %[a-z] 匹配小写字母

返回值

  • 如果只传递了两个参数,返回包含解析值的数组
  • 如果传递了可变参数,返回成功匹配和赋值的参数数量
  • 失败或到达文件末尾时返回false

示例代码

示例1:基本用法

<?php
// 创建测试文件
$content = "John 25 75.5\nAlice 30 65.2\nBob 28 80.1";
file_put_contents('data.txt', $content);

// 打开文件
$handle = fopen('data.txt', 'r');
if (!$handle) {
    die('无法打开文件');
}

// 方法1:返回数组
rewind($handle);
while ($data = fscanf($handle, "%s %d %f")) {
    if ($data !== false) {
        list($name, $age, $score) = $data;
        echo "姓名: $name, 年龄: $age, 分数: $score<br>";
    }
}

echo "<hr>";

// 方法2:使用可变参数
rewind($handle);
while (fscanf($handle, "%s %d %f", $name, $age, $score) !== false) {
    if ($name !== null) {
        echo "姓名: $name, 年龄: $age, 分数: $score<br>";
    }
}

fclose($handle);

// 清理
unlink('data.txt');
?>

示例2:解析配置文件

<?php
// 创建配置文件
$config = "
# 数据库配置
db_host = localhost
db_port = 3306
db_name = mydatabase
db_user = admin
db_pass = secret123

# 应用配置
app_name = MyApp
app_debug = true
app_version = 2.5.1
";

file_put_contents('config.txt', $config);

// 解析配置文件
function parseConfigFile($filename) {
    $config = [];
    $handle = fopen($filename, 'r');

    if (!$handle) {
        return false;
    }

    while (!feof($handle)) {
        $line = fgets($handle);

        // 跳过注释和空行
        if (trim($line) === '' || $line[0] === '#') {
            continue;
        }

        // 解析键值对
        if (fscanf($handle, "%[^=] = %[^\n]", $key, $value) === 2) {
            $key = trim($key);
            $value = trim($value);

            // 处理布尔值
            if ($value === 'true') {
                $value = true;
            } elseif ($value === 'false') {
                $value = false;
            }
            // 处理数字
            elseif (is_numeric($value)) {
                if (strpos($value, '.') !== false) {
                    $value = (float)$value;
                } else {
                    $value = (int)$value;
                }
            }

            $config[$key] = $value;
        }

        // 由于fscanf移动了指针,需要调整
        fseek($handle, ftell($handle));
    }

    fclose($handle);
    return $config;
}

// 使用示例
$config = parseConfigFile('config.txt');
if ($config) {
    echo "<h5>解析的配置:</h5>";
    echo "<pre>" . print_r($config, true) . "</pre>";
}

// 清理
unlink('config.txt');
?>

示例3:解析CSV格式数据

<?php
// 创建CSV格式数据(包含不同类型)
$csvData = "Product,Price,Quantity,InStock
Laptop,999.99,50,true
Mouse,25.50,100,false
Keyboard,79.99,75,true
Monitor,299.99,30,true";

file_put_contents('inventory.csv', $csvData);

// 使用fscanf解析CSV
$handle = fopen('inventory.csv', 'r');
if (!$handle) {
    die('无法打开文件');
}

// 读取标题行
$headers = fgetcsv($handle);
echo "<table class='table table-bordered'>";
echo "<thead><tr>";
foreach ($headers as $header) {
    echo "<th>" . htmlspecialchars($header) . "</th>";
}
echo "</tr></thead><tbody>";

// 读取数据行
while (!feof($handle)) {
    // 使用fscanf解析每行
    $result = fscanf($handle, "%[^,],%f,%d,%s");

    if ($result !== false && count($result) === 4) {
        list($product, $price, $quantity, $inStock) = $result;

        // 处理布尔值
        $inStock = ($inStock === 'true') ? '是' : '否';

        echo "<tr>";
        echo "<td>" . htmlspecialchars($product) . "</td>";
        echo "<td>\${$price}</td>";
        echo "<td>{$quantity}</td>";
        echo "<td>{$inStock}</td>";
        echo "</tr>";
    }
}

echo "</tbody></table>";
fclose($handle);

// 清理
unlink('inventory.csv');
?>

示例4:解析日志文件

<?php
// 创建日志文件
$logData = <<<LOG
[2023-10-01 14:30:25] INFO: User login successful (user_id=123)
[2023-10-01 14:35:10] ERROR: Database connection failed (error_code=1001)
[2023-10-01 14:40:15] WARNING: High memory usage detected (usage=85%)
[2023-10-01 14:45:30] INFO: File uploaded successfully (file_size=2048576)
LOG;

file_put_contents('app.log', $logData);

// 解析日志文件
class LogParser {
    private $handle;

    public function __construct($filename) {
        $this->handle = fopen($filename, 'r');
        if (!$this->handle) {
            throw new Exception("无法打开日志文件: $filename");
        }
    }

    public function parseLine() {
        if (feof($this->handle)) {
            return false;
        }

        // 解析日志格式: [日期 时间] 级别: 消息 (键=值)
        $result = fscanf(
            $this->handle,
            "[%10s %8s] %[^:]: %[^(] (%[^)]))"
        );

        if ($result === false || count($result) < 4) {
            return false;
        }

        list($date, $time, $level, $message, $extra) = array_pad($result, 5, '');

        // 解析额外参数
        $params = [];
        if (!empty($extra)) {
            $pairs = explode(' ', $extra);
            foreach ($pairs as $pair) {
                list($key, $value) = explode('=', $pair, 2);
                $params[$key] = $value;
            }
        }

        return [
            'timestamp' => "$date $time",
            'level' => trim($level),
            'message' => trim($message),
            'params' => $params
        ];
    }

    public function getAllLogs() {
        $logs = [];
        while ($log = $this->parseLine()) {
            $logs[] = $log;
        }
        return $logs;
    }

    public function __destruct() {
        if ($this->handle) {
            fclose($this->handle);
        }
    }
}

// 使用示例
try {
    $parser = new LogParser('app.log');
    $logs = $parser->getAllLogs();

    echo "<h5>解析的日志条目:</h5>";
    echo "<div class='table-responsive'>";
    echo "<table class='table table-bordered table-sm'>";
    echo "<thead class='table-light'>
            <tr>
                <th>时间</th>
                <th>级别</th>
                <th>消息</th>
                <th>参数</th>
            </tr>
          </thead><tbody>";

    foreach ($logs as $log) {
        $levelClass = '';
        switch ($log['level']) {
            case 'ERROR': $levelClass = 'danger'; break;
            case 'WARNING': $levelClass = 'warning'; break;
            case 'INFO': $levelClass = 'info'; break;
        }

        echo "<tr>";
        echo "<td>{$log['timestamp']}</td>";
        echo "<td><span class='badge bg-{$levelClass}'>{$log['level']}</span></td>";
        echo "<td>{$log['message']}</td>";
        echo "<td>" . htmlspecialchars(json_encode($log['params'])) . "</td>";
        echo "</tr>";
    }

    echo "</tbody></table></div>";

} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}

// 清理
unlink('app.log');
?>

注意事项

重要提示:
  • 空白字符:格式字符串中的空白字符会匹配输入中的任意数量空白字符(包括零个)
  • 指针移动:fscanf()会移动文件指针,读取后指针停留在已读取内容之后
  • 性能考虑:对于简单的文本解析,fgets()配合sscanf()可能更灵活
  • 格式复杂性:复杂的格式字符串可能难以调试,建议先测试简单的格式
  • 编码问题:fscanf()对多字节字符支持有限,对于非ASCII文本可能表现不佳
  • 错误处理:始终检查返回值,失败时返回false
  • 可变参数:使用可变参数时,参数是按引用传递的
  • 类型转换:fscanf()会自动进行类型转换,但可能不如显式转换可控

fscanf() vs fgets()/sscanf()

特性 fscanf() fgets() + sscanf()
使用方式 一步完成读取和解析 先读取行,再解析
灵活性 较低,必须在读取时指定格式 较高,可以读取后再决定如何解析
错误处理 失败时返回false 可以分别处理读取和解析错误
性能 对于简单格式较快 对于复杂处理更可控
推荐场景 结构化的固定格式文件 需要灵活处理或复杂解析的场景

相关函数

  • sscanf() - 根据指定格式解析字符串
  • fgets() - 从文件指针中读取一行
  • fread() - 读取文件(二进制安全)
  • fopen() - 打开文件或URL
  • fclose() - 关闭一个已打开的文件指针
  • file() - 将整个文件读入数组中
  • preg_match() - 执行正则表达式匹配

典型应用场景

配置文件解析

解析结构化的配置文件,如INI风格的键值对。

日志文件分析

解析固定格式的日志文件,提取时间戳、级别和消息。

数据文件导入

导入结构化的数据文件,如CSV或自定义格式的数据。

文本模板处理

从模板文件中提取变量和占位符。