PHP substr 函数怎么用:截取字符串的基本方法

生活资讯2小时前发布 jdkjadmin
0 00
PHP substr 函数怎么用:截取字符串的基本方法

PHP substr 函数怎么用:截取字符串的基本方法

在 PHP 中,substr() 用于从字符串中截取一段内容。它常见于提取文件名、生成摘要、读取固定格式文本,或从已有字符串中取出指定范围。函数本身并不复杂,但有两个容易被忽略的特点:它按字节计数,而不是按 UTF-8 字符计数;偏移量和长度还可以使用负数。不了解这两点,截取结果就可能和预期不同。

本文介绍 substr() 的参数、正负偏移与长度的计算方式,给出可以直接运行的示例,并说明它的边界行为、常见误用,以及截取字符串不能替代输入验证或安全转义的原因。

一、substr() 的基本语法

函数的基本形式如下:

<?php
substr(string $string, int $offset, ?int $length = null): string|false
?>
  • $string:要处理的字符串。
  • $offset:开始截取的位置,可以是正数或负数。
  • $length:可选参数,指定最多截取多少个字节;也可以是负数,表示从截取范围的末尾省去相应字节。省略或传入 null 时,截取到字符串末尾。

对于普通的 ASCII 字符串,可以先把每个字符都看作一个字节来理解。偏移量从 0 开始:字符串 "abcdef" 中,a 的位置是 0,b 的位置是 1。

二、从指定位置截取到字符串末尾

只传入字符串和偏移量时,substr() 会从该位置一直截取到字符串末尾:

<?php
$text = 'abcdef';

echo substr($text, 2), PHP_EOL; // cdef
echo substr($text, 0), PHP_EOL; // abcdef
?>

如果 $offset 是非负数,它表示从字符串开头计算的字节位置。substr($text, 2) 从位置 2 开始,因此结果是 "cdef"。

省略长度和显式传入 null,都表示截取到末尾:

<?php
$text = 'abcdef';

echo substr($text, 2), PHP_EOL;       // cdef
echo substr($text, 2, null), PHP_EOL; // cdef
?>

三、使用正数长度截取固定范围

当 $length 是正数时,它表示最多截取多少个字节。例如,从位置 1 开始取 3 个字节:

<?php
$text = 'abcdef';

echo substr($text, 1, 3), PHP_EOL; // bcd
echo substr($text, 0, 1), PHP_EOL; // a
?>

如果剩余内容少于指定长度,函数会返回实际存在的部分,不会因为长度不足而补齐字符:

<?php
$text = 'abcdef';

echo substr($text, 4, 10), PHP_EOL; // ef
?>

常见用途包括提取固定格式字符串中的一段内容。例如,处理一个约定格式为“前缀加数字”的标识符:

<?php
$code = 'ID-5832';

$prefix = substr($code, 0, 2);
$number = substr($code, 3);

echo $prefix, PHP_EOL; // ID
echo $number, PHP_EOL; // 5832
?>

这段代码只演示截取方法。实际业务中,不能仅凭截取结果就认定输入符合格式;还应根据需求检查分隔符、长度和字符组成。

四、负数偏移:从字符串末尾倒着数

$offset 为负数时,起点从字符串末尾开始计算。-1 表示最后一个字节,-2 表示倒数第二个字节。

<?php
$text = 'abcdef';

echo substr($text, -1), PHP_EOL;    // f
echo substr($text, -2), PHP_EOL;    // ef
echo substr($text, -3, 2), PHP_EOL; // de
?>

例如,substr($text, -3, 2) 从倒数第三个字节 d 开始,取两个字节,结果为 "de"。

负数偏移在提取扩展名、末尾标记等内容时很方便。例如,从简单的文件名中取得最后四个字节:

<?php
$filename = 'report.pdf';

echo substr($filename, -4), PHP_EOL; // .pdf
?>

不过,截取文件名末尾只是字符串操作,不等于可靠的文件类型判断。文件名可能没有扩展名,也可能包含多个点;在安全敏感的上传处理中,不能只依赖扩展名来决定文件是否可信。

五、负数长度:从末尾省去指定字节

当 $length 为负数时,它不是“向后取负数个字符”,而是表示在可截取范围的末尾省去相应数量的字节。

<?php
$text = 'abcdef';

echo substr($text, 0, -1), PHP_EOL; // abcde
echo substr($text, 1, -1), PHP_EOL; // bcde
?>

substr($text, 0, -1) 从开头开始,并省去末尾一个字节,因此得到 "abcde"。substr($text, 1, -1) 从位置 1 开始,省去原字符串末尾一个字节,得到 "bcde"。

如果偏移量也是负数,起点先从末尾计算,再根据长度规则确定截取范围:

<?php
$text = 'abcdef';

echo substr($text, -4, -1), PHP_EOL; // cde
?>

在这个例子中,-4 对应 c,从这里开始截取,同时省去原字符串末尾一个字节 f,所以结果是 "cde"。负数长度不够直观,使用前最好用短字符串手动核对预期结果,并为边界写测试。

六、边界行为与返回值

substr() 的返回值类型是 string|false。在常见的有效参数用法中,通常会得到字符串;无法截取出内容时,也可能得到空字符串 ""。空字符串与 false 不是同一个值,不应不加判断地混为一谈。

偏移量超出字符串末尾时,当前 PHP 版本通常返回空字符串。负偏移量的绝对值大于字符串长度时,起点会落到字符串开头之前;对于这类情况,建议不要依赖复杂的边界推算,而是先检查字符串长度并明确处理。

<?php
$text = 'abc';

var_dump(substr($text, 10)); // string(0) ""
var_dump(substr($text, 0, 0)); // string(0) ""
?>

空字符串可能是有效的业务结果,例如用户确实没有填写内容。因此,判断截取是否成功时,不要只用 if ($result):空字符串会被 PHP 当作假值。若确实需要区分布尔型 false,可以使用严格比较:

<?php
$result = substr('abc', 0, 0);

if ($result === false) {
    echo '截取失败';
} else {
    echo '截取得到字符串,长度为:', strlen($result);
}
?>

substr() 的参数要求为字符串、整数和整数或 null。如果从外部输入得到偏移量或长度,应先验证并转换为预期类型,避免不受控的数据被隐式转换。PHP 版本也会影响部分异常边界的表现;例如负长度与超出范围的负偏移量组合,在 PHP 8.0 之前可能返回 false,PHP 8.0 起相应情形返回空字符串。依赖这些边界行为的代码应在目标 PHP 版本上测试。

七、最重要的限制:substr() 按字节截取

substr() 处理的是字节序列。对于纯 ASCII 文本,一个字符通常占一个字节,所以按字节截取看起来就像按字符截取;但 UTF-8 中文字符通常占多个字节,一个汉字并不等于一个偏移单位。

<?php
$text = '你好,PHP';

echo strlen($text), PHP_EOL;
echo substr($text, 0, 3), PHP_EOL;
?>

strlen() 与 substr() 都按字节工作。上例中的中文字符在 UTF-8 下占多个字节,截取前三个字节可能只拿到一个汉字的部分编码,输出可能出现乱码或替代字符。具体显示效果还会受到终端、编辑器和运行环境的影响。

若需求是按 UTF-8 字符截取,应使用多字节字符串扩展中的 mb_substr(),并明确字符编码:

<?php
$text = '你好,PHP';

if (!function_exists('mb_substr')) {
    throw new RuntimeException('需要安装并启用 mbstring 扩展');
}

echo mb_substr($text, 0, 2, 'UTF-8'), PHP_EOL; // 你好
?>

mb_substr() 的长度和偏移按字符处理,更适合 UTF-8 文本摘要、昵称截断等场景。使用前要确认服务器已安装并启用 mbstring 扩展。不要把二者的参数语义混为一谈:虽然参数形式相近,但一个按字节,一个按指定字符编码中的字符处理。

如果字符串是文件数据、协议片段或其他明确按字节定义的内容,按字节截取可能正是需要的行为。关键是先确认数据的表示方式和业务单位:是“字节数”,还是用户看到的“字符数”。

八、常见使用场景与更稳妥的写法

1. 生成固定长度的 ASCII 摘要

当输入确定为 ASCII 内容时,可以直接使用 substr():

<?php
$summary = 'A short description for a page.';
$preview = substr($summary, 0, 12);

echo $preview; // A short descr
?>

如果摘要面向中文用户,应该改用 mb_substr(),否则字节长度不等于字符数量。若还要追加省略号,也应先判断原文本是否超过目标字符数,避免对未截断的文本也追加省略号。

2. 根据已知格式拆分字符串

当格式固定、位置稳定时,偏移和长度可以清楚地表达提取范围。但如果字段由分隔符划分,通常用 explode() 或针对结构化数据的解析工具更易读,也更不容易因字段长度变化而出错。

<?php
$value = 'name:Alice';
$parts = explode(':', $value, 2);

if (count($parts) === 2) {
    echo $parts[0], PHP_EOL; // name
    echo $parts[1], PHP_EOL; // Alice
} else {
    echo '格式不正确';
}
?>

3. 提取末尾内容

负数偏移适合从末尾按固定字节数提取内容,但前提是格式长度确实固定。若要取得文件扩展名,优先考虑 pathinfo() 等针对路径语义的函数,而不是假设扩展名永远占固定字节数。

九、常见错误

错误一:把字节位置当成中文字符位置

这是最常见的问题。substr($text, 0, 5) 对 UTF-8 中文不是“取前五个汉字”,而是最多取前五个字节。按字符处理应使用合适的多字节函数。

错误二:把长度为负理解为截取方向

substr($text, 2, -1) 不是从位置 2 倒着取一个字节,而是从位置 2 起截取,并省去末尾一个字节。需要倒数位置时,应把负数放在偏移量参数中。

错误三:忽略索引从零开始

第一个字节的位置是 0,不是 1。如果把人类习惯的“第一个字符”直接写成偏移量 1,结果就会从第二个位置开始。

错误四:没处理短字符串和空字符串

当输入长度不足、内容为空或偏移量超出范围时,结果可能是空字符串。对于必填字段或固定格式数据,应先验证长度和格式,再做截取。

错误五:假设 substr() 会修改原变量

substr() 返回截取后的新字符串,不会原地修改传入的变量。要保留结果,需要接收返回值:

<?php
$text = 'abcdef';
$part = substr($text, 1, 3);

echo $text, PHP_EOL; // abcdef
echo $part, PHP_EOL; // bcd
?>

十、安全用途:截取不是验证,也不是转义

字符串截取本身不会让不可信输入变安全。它既不会检查内容是否合法,也不会针对 HTML、SQL、Shell 命令或 URL 执行上下文编码。即使先截短输入,恶意字符仍可能留在结果中。因此,不能把 substr() 当作防止跨站脚本、SQL 注入或命令注入的安全措施。

例如,把一段内容截短后直接输出到网页,仍要按 HTML 文本上下文正确转义:

<?php
$userInput = '<script>alert(1)</script>';
$short = substr($userInput, 0, 100);

echo htmlspecialchars(
    $short,
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);
?>

网页输出应根据实际输出上下文选择合适的编码方式;这里的 htmlspecialchars() 用于 HTML 文本等相应场景,并不能替代 JavaScript、CSS 或 URL 等上下文所需的专门处理。数据库操作应使用预处理语句,而不是靠截取或过滤字符串拼接 SQL。

截取还可能把编码单位或语法结构切断:按字节截 UTF-8 文本可能破坏字符;截 HTML 片段可能留下未闭合标签;截 URL、JSON 或多字节协议数据也可能破坏格式。处理这类内容时,应使用理解其结构的解析或编码工具,而不是任意截断。

如果确实需要限制用户输入长度,应明确限制的是字节数还是字符数,在适当阶段完成长度检查,并对超过限制的输入作出清晰处理。长度限制是业务规则的一部分,不等于内容可信,也不等于完成安全校验。

十一、一个综合示例

下面的例子展示 ASCII 标识符的输入检查、固定前缀提取,以及严格处理空结果的方式。代码可以保存为 PHP 文件运行:

<?php
declare(strict_types=1);

$code = 'ID-5832';

if (!is_string($code)) {
    throw new InvalidArgumentException('标识符必须是字符串');
}

if (strlen($code) < 4) {
    throw new InvalidArgumentException('标识符长度不足');
}

if (substr($code, 0, 3) !== 'ID-') {
    throw new InvalidArgumentException('标识符前缀不正确');
}

$number = substr($code, 3);

if ($number === false || $number === '') {
    throw new InvalidArgumentException('编号不能为空');
}

echo '前缀:', substr($code, 0, 2), PHP_EOL;
echo '编号:', $number, PHP_EOL;
?>

这个示例适用于约定字符均为 ASCII 的简单格式。它没有把截取当作验证的全部内容,而是额外检查了长度和前缀。真实项目还应按业务要求验证编号只能包含哪些字符;如果数据来自外部系统,也要考虑编码和格式约定。

十二、快速记忆规则

  • substr($s, $offset):从偏移位置截取到末尾。
  • substr($s, $offset, $length):正长度表示最多取多少字节。
  • 负偏移表示从字符串末尾倒数定位。
  • 负长度表示从可截取范围末尾省去相应字节。
  • substr() 按字节工作;按 UTF-8 字符截取通常应使用 mb_substr()。
  • 截取不会验证输入,也不会进行安全转义;验证、解析和按输出上下文编码需要分别处理。

使用 substr() 时,先确认截取单位是字节还是字符,再明确起点和长度的正负含义,并测试空字符串、短字符串和越界情况。对固定格式、明确按字节定义的内容,它简单有效;对中文文本、结构化数据或安全敏感输入,则应选用更符合数据语义的函数和验证方式。

© 版权声明

相关文章