函数用法讲解`)
前言fprintf()是printf()家族的「写到流」版本它按照指定的格式串生成字符串然后写进一个已经打开的文件流file stream并返回写入的字节数。它和sprintf()的关系是「同一套格式化引擎不同的输出去处」——一个写进流一个返回字符串一个直接输出到标准输出。三个常见的记错点先摆出来它不返回字符串。想拿到格式化后的字符串用sprintf()fprintf()返回的是整数写入的字节数。它不是「只能写文件」。第一个参数是「流资源stream resource」fopen()打开的文件是流php://memory、php://temp是流命令行下的STDOUT、STDERR也是流。它的错误处理在 PHP 8.0 变严了。格式串写错、参数给少、宽度或精度给了非法值现在都会直接抛出ValueError或ArgumentCountError而不是像 PHP 7 那样只是发个警告然后返回false。本文把签名、格式串语法、PHP 8.0 的行为变化和几个实用场景一次讲完。一、签名、返回值与家族成员官方手册给出的签名是fprintf(resource $stream, string $format, mixed ...$values): int三个参数参数说明$stream目标流资源通常由fopen()创建$format格式串由零个或多个「指令」组成...$values变长参数按顺序对应格式串里的转换说明符返回值是写入的字符串长度字节数。这里有个容易被忽略的细节返回值反映的是「格式化后的字符串有多少字节」而不是「有多少字节已经落盘」——文件流的写入通常带缓冲真正的系统调用发生在缓冲刷新时fflush()或fclose()时。同一套格式化引擎有六个对外函数按「输出到哪儿」和「参数怎么传」两个维度排列函数输出目标返回值参数传递printf(string $format, mixed ...$values): int标准输出字节数变长参数sprintf(string $format, mixed ...$values): string无返回字符串字符串变长参数fprintf(resource $stream, string $format, mixed ...$values): int指定流字节数变长参数vfprintf(resource $stream, string $format, array $values): int指定流字节数数组vsprintf(string $format, array $values): string无返回字符串字符串数组fwrite(resource $stream, string $data, ?int $length null): int\false指定流字节数或falsevfprintf()和fprintf()是同一件事的两种参数形式当你要格式化的参数本来就在一个数组里时用vfprintf()可以省掉一次展开PHP 7.4 起也可以用fprintf($fp, $fmt, ...$values)的展开语法。最基础的一段?php // 适用于 PHP 8.0$fp fopen(__DIR__ . /app.log, ab);if ($fp false) {exit(无法打开日志文件);}$written fprintf($fp, [%s] level%s msg%s\n, date(Y-m-d H:i:s), INFO, 服务启动);fclose($fp);var_dump($written); // int写入的字节数示例里的三个要点打开模式用ab追加模式b表示二进制安全避免在 Windows 上对换行符做转换fopen()失败返回false必须判断fclose()不能省它既释放句柄也会把缓冲区里的数据真正刷到磁盘。二、格式串语法一个转换说明符的完整原型是%[argnum$][flags][width][.precision]specifier参数序号%n$显式指定用第几个参数序号从 1 开始。它的价值在于「同一个参数可以复用」和「参数顺序与显示顺序解耦」。?php // 适用于 PHP 8.0$fp fopen(php://temp, r);fprintf($fp, %2$s 是 %1$d 号再说一次%2$s, 42, 答案);rewind($fp);echo stream_get_contents($fp), PHP_EOL; // 答案 是 42 号再说一次答案fclose($fp);注意这里必须用单引号写格式串。如果图省事写成双引号$s会被 PHP 当成变量插值解析得写成%2\$s才能得到字面量$非常容易写错。标志位 flags标志作用-在给定宽度内左对齐默认右对齐正数也显示正号默认只有负数才带符号空格用空格填充默认行为0用零左填充配合s时也可以右填充加一个字符用指定字符填充例如*表示用星号填充宽度 width一个整数表示这次转换的结果至少占多少个字符也可以写*此时宽度由「被格式化值之前的那一个额外整数参数」提供。精度 precision一个点号后面跟整数或*。含义随说明符变化——对e、E、f、F是小数点后的位数默认 6对g、G、h、H是有效数字上限对s是字符串的最大截断长度。手册注明只写点号而不写数字时精度按 0 处理。说明符 specifierPHP 8 支持的完整清单说明符含义%字面量百分号不消耗参数b整数按二进制输出c整数按 ASCII 值对应的字符输出d整数按有符号十进制输出e/E科学计数法E用大写字母f浮点数受区域设置影响F浮点数不受区域设置影响g/G通用格式按指数大小在f与e之间自动选择h/H与g/G类似但使用FPHP 8.0.0 起可用o整数按八进制输出s按字符串输出u整数按无符号十进制输出x/X整数按十六进制输出小写 / 大写字母手册对c说明符有一条 Warning它忽略填充和宽度设置。另外还有一条 Warning对「一个字符需要多个字节」的字符集把字符串和宽度说明符组合使用可能得到非预期的结果——这与宽度是按字节还是按字符计数有关中文场景要特别留心。参数会按说明符需要被强制转换s用字符串d、u、c、o、x、X、b用整数e、E、f、F、g、G、h、H用浮点数。一个对齐输出的例子?php // 适用于 PHP 8.0$rows [[Alice, 92.5],[Bob, 7.125],[Carol, 100.0],];$fp fopen(php://temp, r);foreach ($rows as [$name, $score]) {fprintf($fp, %-10s|%8.2f|%s\n, $name, $score, $score 60 ? 通过 : 未通过);}rewind($fp);echo stream_get_contents($fp), PHP_EOL;fclose($fp);php://temp是一个可读可写的临时流数据量小的时候放在内存里超过阈值自动落盘非常适合「先拼好再决定怎么用」的场景。三、PHP 8.0 起的错误行为这是升级时最需要留意的一段。手册的「错误异常」一栏列得很清楚情况PHP 8.0 起PHP 8.0 之前参数个数为 0抛ValueError发E_WARNING[width]小于 0 或大于PHP_INT_MAX抛ValueError发E_WARNING[precision]小于 0 或大于PHP_INT_MAX抛ValueError发E_WARNING给的参数比格式串需要的少抛ArgumentCountError返回false并发E_WARNING函数失败不再返回false可能返回false除此之外PHP 8 还把很多历史上「静默出错」的写法改成了显式报错。从实现里可以看到的具体取值错误包括格式串以一个孤立的%结尾抛出ValueError提示「Missing format specifier at end of string」出现不认识的说明符例如%q抛出ValueError提示「Unknown format specifier」宽度、精度写了非整数的内容抛出ValueError提示「Width must be an integer」之类指定填充字符但没写抛出ValueError提示「Missing padding character」。同一个报错清单也适用于printf()、sprintf()、vfprintf()、vsprintf()因为它们共用同一个格式化实现。还有一个更细的版本变化值得记录PHP 8.5.0修正了一处精度处理——「一个没有指定精度的格式说明符现在被正确地当作精度 0 处理而不再错误地重置精度」。如果你的代码依赖过旧行为升级到 8.5 时需要核对输出。四、实战写结构化日志与导出文本把日志格式集中到一个函数里是fprintf()最正当的用法?php // 适用于 PHP 8.0declare(strict_types1);const LOG_FILE __DIR__ . /app.log;function log_line(string $level, string $message, array $context []): void{$fp fopen(LOG_FILE, ab);if ($fp false) {return; // 打不开日志不应该让主流程崩掉}// 注意格式串里的 % 全部是占位符需要字面量百分号时写 %%fprintf($fp,[%s] %-5s %s%s,date(Y-m-d H:i:s),$level,$message,$context [] ? PHP_EOL : . json_encode($context, JSON_UNESCAPED_UNICODE) . PHP_EOL);fclose($fp);}log_line(INFO, 服务启动);log_line(WARN, 磁盘使用率 85%); // 这是参数内容% 不需要转义log_line(ERR, 请求失败, [code 500, path /api]);这段代码里有三个刻意的设计ab追加模式多个进程同时写时不会互相覆盖要更强的并发保证还得加锁见flock()格式化后的整行一次性写入避免半行日志被另一个进程插进来json_encode()用JSON_UNESCAPED_UNICODE让上下文里的中文保持可读而不是变成\uXXXX转义。顺便把%%的适用范围说清楚只有格式串本身里的%才需要写成%%。函数参数比如日志内容是数据里面的%会原样输出不需要也不能转义——如果在那里写85%%日志里就会真的多出一个百分号。如果要保证多进程写入的原子性可以在写之前加锁?php // 适用于 PHP 8.0$fp fopen(LOG_FILE, ab);if ($fp ! false flock($fp, LOCK_EX)) {fprintf($fp, [%s] %s%s, date(c), 带锁写入的一行, PHP_EOL);fflush($fp); // 立即刷入磁盘flock($fp, LOCK_UN); // 释放锁}if (is_resource($fp)) {fclose($fp);}flock()的锁在fclose()时会自动释放但显式解锁更清晰fflush()用来把缓冲区立刻推到磁盘代价是更频繁的系统调用。另一个常见场景是导出定长/对齐的文本报表比如给运维做一个纯文本的巡检报告?php // 适用于 PHP 8.0$items [[名称 web-01, cpu 12.3, mem 68.9],[名称 db-01, cpu 88.8, mem 91.2],];$fp fopen(__DIR__ . /report.txt, wb);if ($fp false) {exit(无法写入报表);}fprintf($fp, %-12s%-10s%-10s\n, HOST, CPU%, MEM%);foreach ($items as $item) {fprintf($fp, %-12s%-10.1f%-10.1f\n, $item[名称], $item[cpu], $item[mem]);}fclose($fp);需要提醒的是这里的宽度是按字节补齐的名称这样的中文键或中文值会让列对不齐一个汉字在 UTF-8 里占 3 个字节但显示宽度通常是 2。纯 ASCII 的报表用这套写法没问题含中文的报表应当改用固定分隔符如制表符或直接输出 CSV用fputcsv()。常见坑点❌ 用fprintf()拿格式化后的字符串$s fprintf($fp, %s, $x);✅ 它返回的是写入的字节数整数。要字符串用sprintf()要输出到标准输出用printf()。❌ 格式串里出现需要显示的字面量%而不转义✅ 字面量百分号必须写成%%。只写一个孤立的%时PHP 8 会抛出ValueError提示「Missing format specifier at end of string」PHP 8 之前只是静默输出错乱的结果。❌ 参数给少了以为会得到false✅ PHP 8.0 起抛出ArgumentCountError以前是返回false并发E_WARNING。凡是「格式化字符串拼接」的地方都要核对参数个数。❌ 在双引号格式串里写位置参数%1$s✅ 双引号下$s会被当作变量插值。格式串统一用单引号%1$s这样$是字面量。❌ 认为%f和%F只是大小写差别✅%f受区域设置影响%F不受。调用过setlocale(LC_NUMERIC, ...)之后某些区域下%f会把小数点输出成逗号而%F始终是点号。要可预测的输出就用%F或改用number_format()。❌ 用%c并指望宽度和填充生效✅ 手册明确说明c说明符忽略填充和宽度设置。要对齐字符列得自己用%s处理。❌ 写完不fclose()或者以为返回值代表「已经落盘」✅ 文件流是带缓冲的不关闭文件可能丢失缓冲区里的内容fclose()会刷新缓冲并释放句柄。需要立刻落盘就调fflush()。❌ 把大量数据逐行fprintf()到文件并且每行都重新fopen()/fclose()✅ 打开与关闭是相对昂贵的操作。同一个文件的多行输出应当复用一个句柄写完统一关闭。总结项目说明签名fprintf(resource $stream, string $format, mixed ...$values): int返回值写入的字节数不是字符串格式原型%[argnum$][flags][width][.precision]specifier常用标志-左对齐、显示正号、0零填充、x自定义填充字符说明符%bcdeEfFgGhHosuxX精度默认值e/E/f/F默认 6 位小数s用它做截断长度错误行为PHP 8.0 起抛ValueError/ArgumentCountError不再返回false数组参数版本vfprintf(resource $stream, string $format, array $values): int需要字符串时用sprintf()fprintf()的定位可以用一句话概括把sprintf()的结果直接送进流。它的价值不在格式化本身那是sprintf()的能力而在于「一边格式化一边写」——日志、报表、协议报文这类输出用它比「先sprintf()拼好再fwrite()」更直接也少一次中间字符串。使用时把三件事记牢返回值是字节数不是字符串、格式串里的字面量%要写成%%、PHP 8 起格式串写错会直接抛异常而不是静默失败。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。