PHP basename() 函数

定义和用法

basename() 函数返回路径中的文件名部分。它接受一个文件路径字符串,并返回该路径的文件名部分(包括扩展名,除非指定了后缀参数)。

basename() 函数通常用于从完整路径中提取文件名,或者在文件上传、文件处理等场景中获取干净的文件名。

注意:basename() 函数在 Windows 和 Unix/Linux 系统上都能正确工作,它会自动识别系统路径分隔符。但是,在处理来自用户输入的路径时要小心,因为 basename() 只进行简单的字符串操作,不检查文件是否存在。

语法

basename(string $path, string $suffix = ""): string

参数

参数 描述
path

必需。一个文件路径字符串。

可以是绝对路径或相对路径。Windows 和 Unix/Linux 路径都支持。

suffix

可选。如果文件名以此后缀结尾,该后缀也会被去掉。

常用于去除文件扩展名。注意:如果后缀与文件名末尾不匹配,则不会去除任何内容。

返回值

返回值 描述
string 返回路径中的文件名部分。如果指定了 suffix 参数且文件名以此后缀结尾,则去除此外缀后返回。

示例

示例1:基本用法

<?php
// 基本用法
echo "1. " . basename("/etc/passwd") . "<br>";               // 输出: passwd
echo "2. " . basename("/etc/") . "<br>";                    // 输出: etc
echo "3. " . basename(".") . "<br>";                        // 输出: .
echo "4. " . basename("C:\\test\\test.txt") . "<br>";       // 输出: test.txt
echo "5. " . basename("/usr/local/lib/libc.so.6") . "<br>"; // 输出: libc.so.6
echo "6. " . basename("filename.txt") . "<br>";             // 输出: filename.txt
echo "7. " . basename("/var/www/html/index.php") . "<br>";  // 输出: index.php

// 使用 suffix 参数去除扩展名
echo "<br>使用 suffix 参数:<br>";
echo "8. " . basename("/var/www/html/index.php", ".php") . "<br>";      // 输出: index
echo "9. " . basename("/path/to/document.pdf", ".pdf") . "<br>";        // 输出: document
echo "10. " . basename("/path/to/archive.tar.gz", ".tar.gz") . "<br>";  // 输出: archive
echo "11. " . basename("file.txt", ".pdf") . "<br>";                     // 输出: file.txt (后缀不匹配)
?>

示例2:使用 suffix 参数的各种情况

<?php
// 演示 suffix 参数的各种用法
$testCases = [
    ['path' => '/var/www/html/index.php', 'suffix' => '.php'],
    ['path' => '/var/www/html/index.php', 'suffix' => '.html'],
    ['path' => '/path/to/document.pdf', 'suffix' => '.pdf'],
    ['path' => '/path/to/README', 'suffix' => '.txt'],
    ['path' => 'C:\\Windows\\explorer.exe', 'suffix' => '.exe'],
    ['path' => 'image.jpg', 'suffix' => '.jpg'],
    ['path' => 'archive.tar.gz', 'suffix' => '.gz'],
    ['path' => 'archive.tar.gz', 'suffix' => '.tar.gz'],
    ['path' => 'config.ini.bak', 'suffix' => '.bak'],
    ['path' => '/path/to/file.with.multiple.dots.txt', 'suffix' => '.txt'],
];

echo "<table class='table table-bordered'>";
echo "<thead><tr><th>路径</th><th>后缀</th><th>basename() 结果</th><th>说明</th></tr></thead>";
echo "<tbody>";

foreach ($testCases as $case) {
    $result = basename($case['path'], $case['suffix']);
    $fullName = basename($case['path']);

    // 判断后缀是否匹配
    $suffixMatches = substr($fullName, -strlen($case['suffix'])) === $case['suffix'];
    $explanation = $suffixMatches ? '后缀匹配,已去除' : '后缀不匹配,未去除';

    echo "<tr>";
    echo "<td>" . htmlspecialchars($case['path']) . "</td>";
    echo "<td>" . htmlspecialchars($case['suffix']) . "</td>";
    echo "<td>" . htmlspecialchars($result) . "</td>";
    echo "<td>$explanation</td>";
    echo "</tr>";
}

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

// 实际应用:安全地获取不带扩展名的文件名
function getFileNameWithoutExtension($path) {
    $filename = basename($path);
    $extension = pathinfo($path, PATHINFO_EXTENSION);

    if ($extension) {
        // 使用 basename 去除扩展名
        return basename($path, '.' . $extension);
    }

    return $filename;
}

echo "<br>安全获取不带扩展名的文件名:<br>";
echo "getFileNameWithoutExtension('/var/www/html/index.php'): " .
     getFileNameWithoutExtension('/var/www/html/index.php') . "<br>";
echo "getFileNameWithoutExtension('/path/to/document'): " .
     getFileNameWithoutExtension('/path/to/document') . "<br>";
?>

示例3:与 dirname() 和 pathinfo() 配合使用

<?php
// 演示 basename() 与 dirname() 和 pathinfo() 的配合
$path = "/var/www/html/project/src/controller/UserController.php";

echo "完整路径: $path<br><br>";

// 使用 basename() 和 dirname()
echo "使用 basename(): " . basename($path) . "<br>";
echo "使用 dirname(): " . dirname($path) . "<br><br>";

// 使用 basename() 去除扩展名
echo "不带扩展名的文件名: " . basename($path, '.php') . "<br>";
echo "不带 'Controller.php' 的文件名: " . basename($path, 'Controller.php') . "<br><br>";

// 使用 pathinfo()
$info = pathinfo($path);
echo "使用 pathinfo():<br>";
echo "- 目录名: " . $info['dirname'] . "<br>";
echo "- 文件名: " . $info['basename'] . "<br>";
echo "- 扩展名: " . $info['extension'] . "<br>";
echo "- 文件名(无扩展名): " . $info['filename'] . "<br><br>";

// 比较 basename() 和 pathinfo()['basename']
echo "比较:<br>";
echo "basename(\$path): " . basename($path) . "<br>";
echo "pathinfo(\$path, PATHINFO_BASENAME): " . pathinfo($path, PATHINFO_BASENAME) . "<br>";
echo "结果相同: " . (basename($path) === pathinfo($path, PATHINFO_BASENAME) ? '是' : '否') . "<br><br>";

// 实际应用:处理上传的文件
function processUploadedFile($uploadedPath) {
    // 获取原始文件名
    $originalName = basename($uploadedPath);

    // 生成安全的文件名
    $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $originalName);

    // 获取不带扩展名的文件名
    $nameWithoutExt = pathinfo($safeName, PATHINFO_FILENAME);

    // 获取扩展名
    $extension = pathinfo($safeName, PATHINFO_EXTENSION);

    // 生成新文件名(例如添加时间戳)
    $newName = $nameWithoutExt . '_' . time() . ($extension ? '.' . $extension : '');

    return [
        'original' => $originalName,
        'safe' => $safeName,
        'without_extension' => $nameWithoutExt,
        'extension' => $extension,
        'new_name' => $newName
    ];
}

// 测试上传文件处理
$uploadedFile = "/tmp/uploads/用户文件-测试 (1).pdf";
echo "处理上传文件: $uploadedFile<br>";
$result = processUploadedFile($uploadedFile);
echo "原始文件名: " . $result['original'] . "<br>";
echo "安全文件名: " . $result['safe'] . "<br>";
echo "不带扩展名: " . $result['without_extension'] . "<br>";
echo "扩展名: " . $result['extension'] . "<br>";
echo "新文件名: " . $result['new_name'] . "<br>";
?>

示例4:处理跨平台路径

<?php
/**
 * 跨平台的 basename 处理
 */
function crossPlatformBasename($path, $suffix = '') {
    // 标准化路径分隔符为 Unix 风格(basename 能处理)
    $normalizedPath = str_replace('\\', '/', $path);

    // 使用 basename
    $result = basename($normalizedPath, $suffix);

    return $result;
}

// 测试不同操作系统的路径格式
$testPaths = [
    '/var/www/html/index.php',                    // Unix 风格
    'C:\\xampp\\htdocs\\project\\index.php',      // Windows 风格
    'relative/path/to/file.txt',                  // Unix 相对路径
    '..\\..\\documents\\report.pdf',              // Windows 相对路径
    'file.txt',                                   // 只有文件名
    '.',                                          // 当前目录
    '..',                                         // 父目录
    '/',                                          // Unix 根目录
    'C:\\',                                       // Windows 根目录
    'C:',                                         // Windows 驱动器(无分隔符)
    '//server/share/file.txt',                    // UNC 路径
];

echo "<h4>跨平台 basename 测试</h4>";
echo "<table class='table table-bordered'>";
echo "<thead><tr><th>原始路径</th><th>basename() 结果</th><th>去除 .php 后缀</th></tr></thead>";
echo "<tbody>";

foreach ($testPaths as $path) {
    $basename = crossPlatformBasename($path);
    $basenameWithoutExt = crossPlatformBasename($path, '.php');

    echo "<tr>";
    echo "<td>" . htmlspecialchars($path) . "</td>";
    echo "<td>" . htmlspecialchars($basename) . "</td>";
    echo "<td>" . htmlspecialchars($basenameWithoutExt) . "</td>";
    echo "</tr>";
}

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

// 实际应用:获取文件的 MIME 类型
function getFileInfo($path) {
    $info = [];

    // 获取文件名和扩展名
    $filename = basename($path);
    $extension = pathinfo($path, PATHINFO_EXTENSION);

    // 常见 MIME 类型映射
    $mimeTypes = [
        'jpg'  => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'png'  => 'image/png',
        'gif'  => 'image/gif',
        'pdf'  => 'application/pdf',
        'doc'  => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'xls'  => 'application/vnd.ms-excel',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'txt'  => 'text/plain',
        'html' => 'text/html',
        'php'  => 'application/x-httpd-php',
        'css'  => 'text/css',
        'js'   => 'application/javascript',
    ];

    $info['filename'] = $filename;
    $info['extension'] = $extension;
    $info['mime_type'] = isset($mimeTypes[strtolower($extension)]) ?
                         $mimeTypes[strtolower($extension)] : 'application/octet-stream';
    $info['name_without_ext'] = basename($path, '.' . $extension);

    return $info;
}

echo "<br><h4>文件信息提取示例</h4>";
$files = ['/var/www/html/index.php', 'C:\\Users\\Document\\report.pdf', 'image.jpg'];
foreach ($files as $file) {
    $fileInfo = getFileInfo($file);
    echo "文件: $file<br>";
    echo "文件名: " . $fileInfo['filename'] . "<br>";
    echo "扩展名: " . $fileInfo['extension'] . "<br>";
    echo "MIME 类型: " . $fileInfo['mime_type'] . "<br>";
    echo "不带扩展名的文件名: " . $fileInfo['name_without_ext'] . "<br><br>";
}
?>

示例5:安全处理用户输入的文件名

<?php
/**
 * 安全处理用户提供的文件路径
 */
class SafeFilenameHandler {

    /**
     * 从路径中安全地提取文件名
     */
    public static function getSafeBasename($path, $options = []) {
        $defaults = [
            'remove_extension' => false,
            'allowed_extensions' => [], // 空数组表示允许所有
            'max_length' => 255,
            'sanitize' => true,
        ];

        $options = array_merge($defaults, $options);

        // 获取原始文件名
        $filename = basename($path);

        // 安全过滤
        if ($options['sanitize']) {
            $filename = self::sanitizeFilename($filename);
        }

        // 检查扩展名
        $extension = pathinfo($filename, PATHINFO_EXTENSION);
        if (!empty($options['allowed_extensions']) && $extension) {
            if (!in_array(strtolower($extension), array_map('strtolower', $options['allowed_extensions']))) {
                throw new InvalidArgumentException("文件扩展名 '$extension' 不被允许");
            }
        }

        // 去除扩展名(如果需要)
        if ($options['remove_extension'] && $extension) {
            $filename = basename($filename, '.' . $extension);
        }

        // 限制长度
        if (strlen($filename) > $options['max_length']) {
            $filename = substr($filename, 0, $options['max_length']);
            if ($extension && !$options['remove_extension']) {
                // 确保保留扩展名
                $filename = rtrim($filename, '.') . '.' . $extension;
            }
        }

        return $filename;
    }

    /**
     * 清理文件名,移除潜在危险字符
     */
    private static function sanitizeFilename($filename) {
        // 移除空字符和目录遍历序列
        $filename = str_replace(["\0", '../', '..\\'], '', $filename);

        // 移除控制字符
        $filename = preg_replace('/[\x00-\x1F\x7F]/', '', $filename);

        // 替换空格和特殊字符
        $filename = preg_replace('/[\/\\\\:\*\?"<>\|]/', '_', $filename);

        // 限制连续的下划线
        $filename = preg_replace('/_+/', '_', $filename);

        // 移除开头和结尾的下划线
        $filename = trim($filename, '_');

        // 如果文件名变为空,使用默认名
        if (empty($filename)) {
            $filename = 'file_' . uniqid();
        }

        return $filename;
    }

    /**
     * 生成安全的下载文件名
     */
    public static function getDownloadFilename($path, $userFilename = null) {
        if ($userFilename) {
            // 从用户提供的文件名中提取基本名
            $filename = self::getSafeBasename($userFilename, [
                'sanitize' => true,
                'max_length' => 100
            ]);
        } else {
            // 从路径中提取
            $filename = self::getSafeBasename($path, [
                'sanitize' => true,
                'max_length' => 100
            ]);
        }

        return $filename;
    }
}

// 使用示例
echo "<h4>安全文件名处理示例</h4>";

$testFiles = [
    '/var/www/html/../../../etc/passwd',
    'C:\\Windows\\System32\\cmd.exe',
    '正常文件.pdf',
    '文件 名称 with 空格.txt',
    'file<script>alert("xss")</script>.php',
    'very_long_filename_that_exceeds_maximum_length_restrictions_and_needs_to_be_truncated.pdf',
];

foreach ($testFiles as $file) {
    try {
        $safeName = SafeFilenameHandler::getSafeBasename($file, [
            'allowed_extensions' => ['pdf', 'txt', 'jpg', 'png'],
            'max_length' => 50
        ]);

        echo "原始: " . htmlspecialchars($file) . "<br>";
        echo "安全: " . htmlspecialchars($safeName) . "<br><br>";
    } catch (Exception $e) {
        echo "错误处理 " . htmlspecialchars($file) . ": " . $e->getMessage() . "<br><br>";
    }
}

// 下载文件名示例
echo "<h4>下载文件名示例</h4>";
$serverFile = '/var/www/uploads/季度报告2023Q4.pdf';
$userProvidedName = '我的报告<script>.pdf';

            echo "服务器文件: $serverFile<br>";
            echo "用户提供的文件名: " . htmlspecialchars($userProvidedName) . "<br>";
            echo "安全的下载文件名: " .
            htmlspecialchars(SafeFilenameHandler::getDownloadFilename($serverFile, $userProvidedName)) . "<br>";
                ?>

示例6:批量处理文件路径

<?php
/**
 * 批量处理文件路径的工具类
 */
class BatchFileProcessor {

        /**
         * 批量提取文件名
         */
        public static function extractFilenames(array $paths, $removeExtensions = false) {
        $results = [];

        foreach ($paths as $index => $path) {
        if ($removeExtensions) {
        $extension = pathinfo($path, PATHINFO_EXTENSION);
        if ($extension) {
        $results[$index] = basename($path, '.' . $extension);
    } else {
        $results[$index] = basename($path);
    }
    } else {
        $results[$index] = basename($path);
    }
    }

        return $results;
    }

        /**
         * 按扩展名分组文件名
         */
        public static function groupByExtension(array $paths) {
        $groups = [];

        foreach ($paths as $path) {
        $filename = basename($path);
        $extension = pathinfo($path, PATHINFO_EXTENSION);

        if (empty($extension)) {
        $extension = '无扩展名';
    }

        if (!isset($groups[$extension])) {
        $groups[$extension] = [];
    }

        $groups[$extension][] = $filename;
    }

        // 按扩展名排序
        ksort($groups);

        return $groups;
    }

        /**
         * 重命名文件,保持目录结构
         */
        public static function renameFiles(array $fileMap, $callback) {
        $results = [];

        foreach ($fileMap as $oldPath => $newFilename) {
        $dir = dirname($oldPath);
        $extension = pathinfo($oldPath, PATHINFO_EXTENSION);

        // 应用回调函数生成新文件名
        $processedName = $callback(basename($oldPath, '.' . $extension));

        // 添加扩展名(如果存在)
        if ($extension) {
        $newFilename = $processedName . '.' . $extension;
    } else {
        $newFilename = $processedName;
    }

        $newPath = $dir . '/' . $newFilename;
        $results[$oldPath] = $newPath;
    }

        return $results;
    }
    }

    // 使用示例
echo "<h4>批量文件处理示例</h4>";

$files = [
    '/var/www/html/index.php',
    '/var/www/html/style.css',
    '/var/www/html/script.js',
    '/var/www/html/images/logo.png',
    '/var/www/html/images/banner.jpg',
    '/var/www/html/documents/report.pdf',
    '/var/www/html/README',
    'C:\\Users\\Documents\\presentation.ppt',
];

echo "<h5>1. 提取所有文件名</h5>";
$filenames = BatchFileProcessor::extractFilenames($files);
foreach ($filenames as $index => $name) {
        echo ($index + 1) . ". " . htmlspecialchars($name) . "<br>";
    }

    echo "<h5>2. 提取不带扩展名的文件名</h5>";
$namesWithoutExt = BatchFileProcessor::extractFilenames($files, true);
foreach ($namesWithoutExt as $index => $name) {
        echo ($index + 1) . ". " . htmlspecialchars($name) . "<br>";
    }

    echo "<h5>3. 按扩展名分组</h5>";
$grouped = BatchFileProcessor::groupByExtension($files);
foreach ($grouped as $extension => $fileList) {
        echo "扩展名: $extension (" . count($fileList) . " 个文件)<br>";
        foreach ($fileList as $filename) {
        echo "  - " . htmlspecialchars($filename) . "<br>";
    }
        echo "<br>";
    }

    echo "<h5>4. 批量重命名示例</h5>";
$fileMap = [
    '/var/www/html/document1.pdf' => 'doc1',
    '/var/www/html/document2.pdf' => 'doc2',
    '/var/www/html/image1.jpg' => 'img1',
];

$renameCallback = function($oldName) {
        // 示例:添加前缀和时间戳
        return 'renamed_' . $oldName . '_' . date('Ymd');
    };

$renamed = BatchFileProcessor::renameFiles($fileMap, $renameCallback);
foreach ($renamed as $oldPath => $newPath) {
        echo "原路径: " . htmlspecialchars($oldPath) . "<br>";
        echo "新路径: " . htmlspecialchars($newPath) . "<br><br>";
    }
    ?>

basename() 与 pathinfo() 的比较

特性 basename() pathinfo()
返回类型 字符串(文件名) 数组(包含多个路径信息)
功能范围 仅获取文件名 获取目录名、文件名、扩展名等
去除后缀 支持(通过第二个参数) 不支持(但可以获取无扩展名的文件名)
性能 较高(只做一件事) 较低(需要解析多个部分)
使用场景 仅需要文件名时 需要多个路径信息时
易用性 简单直接 需要处理数组

最佳实践

  1. 处理用户输入时要小心:basename() 可能无法防止目录遍历攻击,应结合其他安全措施
  2. 使用 pathinfo() 获取扩展名:获取文件扩展名时,pathinfo() 比字符串操作更可靠
  3. 跨平台考虑:虽然 basename() 支持跨平台,但在处理路径时最好使用 DIRECTORY_SEPARATOR
  4. 结合 dirname() 使用:需要同时获取目录和文件名时,结合使用 dirname() 和 basename()
  5. 检查文件存在:basename() 只进行字符串操作,不检查文件是否存在,必要时使用 file_exists()
  6. 处理特殊字符:文件名可能包含特殊字符,使用前应进行适当的过滤和转义
  7. 注意编码问题:处理多语言文件名时,注意字符编码的一致性

常见错误

错误 原因 解决方法
预期获取文件名却得到目录名 路径以分隔符结尾,如 "/path/to/dir/" 使用 rtrim($path, '/\\') 清理路径结尾
suffix 参数没有生效 后缀与文件名结尾不匹配(大小写敏感) 确保后缀完全匹配,或使用 pathinfo() 获取扩展名
处理 UNC 路径问题 Windows UNC 路径如 "\\server\share\file" 先标准化路径分隔符,或使用专门处理 UNC 路径的函数
多字节字符问题 文件名包含非 ASCII 字符 使用 mb_* 函数或多字节安全的字符串函数
安全问题 用户输入可能包含目录遍历序列 结合使用 realpath() 或自定义安全过滤函数

相关函数