PHP ftp_nlist() 函数用于获取FTP服务器上指定目录中的文件和目录列表。
特点:此函数返回一个包含文件和目录名称的数组,仅包含名称而不包含其他信息(如文件大小、修改时间等)。
语法
ftp_nlist(resource $ftp, string $directory): array|false
参数说明
| 参数 |
描述 |
ftp |
必需。FTP连接的标识符,由ftp_connect()或ftp_ssl_connect()返回 |
directory |
必需。要列出内容的目录路径。如果未指定或为空字符串,则列出当前工作目录 |
返回值
- 成功时返回包含文件和目录名称的数组
- 失败时返回
false
- 返回的文件名可能是相对路径或绝对路径,具体取决于服务器配置
与ftp_rawlist()的区别
| 特性 |
ftp_nlist() |
ftp_rawlist() |
| 返回内容 |
仅文件名/目录名数组 |
原始服务器响应行数组,包含详细信息 |
| 信息量 |
简单,仅名称 |
详细,包括权限、所有者、大小、时间等 |
| 处理难度 |
简单,直接使用 |
复杂,需要解析 |
| 适用场景 |
只需文件名列表时 |
需要文件详细信息时 |
| 服务器兼容性 |
较好 |
可能因服务器而异 |
示例
示例1:基本用法 - 列出目录内容
<?php
// 连接FTP服务器
$ftp_server = "ftp.example.com";
$ftp_user = "username";
$ftp_pass = "password";
$ftp_conn = ftp_connect($ftp_server);
if (!$ftp_conn) {
die("无法连接到 $ftp_server");
}
// 登录
if (!ftp_login($ftp_conn, $ftp_user, $ftp_pass)) {
die("登录失败");
}
// 启用被动模式
ftp_pasv($ftp_conn, true);
// 列出根目录内容
echo "列出根目录内容:\n";
$file_list = ftp_nlist($ftp_conn, "/");
if ($file_list !== false) {
echo "找到 " . count($file_list) . " 个条目:\n";
foreach ($file_list as $file) {
echo " - $file\n";
}
} else {
echo "获取目录列表失败\n";
}
// 列出指定目录内容
$directory = "/public_html";
echo "\n列出目录 '$directory' 内容:\n";
$dir_list = ftp_nlist($ftp_conn, $directory);
if ($dir_list !== false) {
echo "找到 " . count($dir_list) . " 个条目:\n";
foreach ($dir_list as $item) {
echo " - $item\n";
}
} else {
echo "目录 '$directory' 不存在或无权限访问\n";
}
// 关闭FTP连接
ftp_close($ftp_conn);
?>
示例2:递归列出目录树
<?php
function listFTPDirectoryRecursive($ftp_conn, $directory, $depth = 0) {
// 获取目录列表
$items = ftp_nlist($ftp_conn, $directory);
if ($items === false) {
return [];
}
$result = [];
foreach ($items as $item) {
// 计算缩进
$indent = str_repeat(" ", $depth);
// 检查是否是目录
$is_dir = false;
// 方法1:尝试切换到该目录
$current_dir = ftp_pwd($ftp_conn);
if (@ftp_chdir($ftp_conn, $item)) {
$is_dir = true;
// 切换回原目录
ftp_chdir($ftp_conn, $current_dir);
}
// 方法2:检查是否以斜杠结尾(某些服务器可能这样表示目录)
if (!$is_dir && (substr($item, -1) == '/' || substr($item, -1) == '\\')) {
$is_dir = true;
}
$result[] = [
'name' => $item,
'is_dir' => $is_dir,
'path' => $directory . '/' . basename($item),
'depth' => $depth
];
// 如果是目录且不是当前目录或上级目录,递归处理
if ($is_dir) {
$dir_name = basename($item);
if ($dir_name != '.' && $dir_name != '..') {
$subdir_path = ($directory == '/' ? '' : $directory) . '/' . $dir_name;
$sub_items = listFTPDirectoryRecursive($ftp_conn, $subdir_path, $depth + 1);
$result = array_merge($result, $sub_items);
}
}
}
return $result;
}
// 使用示例
$ftp_conn = ftp_connect('localhost');
if ($ftp_conn && ftp_login($ftp_conn, 'user', 'pass')) {
ftp_pasv($ftp_conn, true);
echo "递归列出FTP目录结构:\n";
$tree = listFTPDirectoryRecursive($ftp_conn, '/');
foreach ($tree as $item) {
$prefix = str_repeat("│ ", $item['depth']) . ($item['is_dir'] ? "├── 📁 " : "├── 📄 ");
echo $prefix . basename($item['name']) . "\n";
}
ftp_close($ftp_conn);
} else {
echo "无法连接FTP服务器\n";
}
?>
示例3:带过滤功能的文件列表
<?php
class FTPFileLister {
private $ftp_conn;
public function __construct($server, $username, $password) {
$this->ftp_conn = ftp_connect($server);
if (!$this->ftp_conn) {
throw new Exception("无法连接到FTP服务器: $server");
}
if (!ftp_login($this->ftp_conn, $username, $password)) {
throw new Exception("FTP登录失败");
}
ftp_pasv($this->ftp_conn, true);
}
/**
* 列出目录中的文件和目录
* @param string $directory 目录路径
* @param array $filters 过滤器配置
* @return array 过滤后的列表
*/
public function listFiles($directory = '', $filters = []) {
// 默认过滤器
$default_filters = [
'extensions' => [], // 文件扩展名过滤,如 ['jpg', 'png', 'pdf']
'exclude_dirs' => [], // 排除的目录名
'exclude_files' => [], // 排除的文件名
'search_pattern' => '', // 搜索模式,支持通配符
'max_depth' => 1, // 最大深度,1表示不递归
'show_hidden' => false, // 是否显示隐藏文件(以.开头的文件)
];
$filters = array_merge($default_filters, $filters);
// 获取目录列表
$items = ftp_nlist($this->ftp_conn, $directory);
if ($items === false) {
return [];
}
$result = [
'directories' => [],
'files' => []
];
foreach ($items as $item) {
$basename = basename($item);
// 跳过隐藏文件(如果不显示)
if (!$filters['show_hidden'] && substr($basename, 0, 1) == '.') {
continue;
}
// 检查是否是目录
$is_dir = $this->isDirectory($item);
// 排除目录过滤
if ($is_dir && in_array($basename, $filters['exclude_dirs'])) {
continue;
}
// 排除文件过滤
if (!$is_dir && in_array($basename, $filters['exclude_files'])) {
continue;
}
// 搜索模式过滤
if ($filters['search_pattern'] && !fnmatch($filters['search_pattern'], $basename)) {
continue;
}
if ($is_dir) {
// 目录处理
$result['directories'][] = [
'name' => $basename,
'path' => $item,
'full_path' => $directory . '/' . $basename
];
} else {
// 文件处理
$extension = pathinfo($basename, PATHINFO_EXTENSION);
// 扩展名过滤
if (!empty($filters['extensions']) && !in_array(strtolower($extension), $filters['extensions'])) {
continue;
}
$result['files'][] = [
'name' => $basename,
'path' => $item,
'full_path' => $directory . '/' . $basename,
'extension' => $extension,
'size' => ftp_size($this->ftp_conn, $item),
'modified' => ftp_mdtm($this->ftp_conn, $item)
];
}
}
// 排序
usort($result['directories'], function($a, $b) {
return strcasecmp($a['name'], $b['name']);
});
usort($result['files'], function($a, $b) {
return strcasecmp($a['name'], $b['name']);
});
return $result;
}
/**
* 检查是否是目录
*/
private function isDirectory($path) {
$current_dir = ftp_pwd($this->ftp_conn);
if (@ftp_chdir($this->ftp_conn, $path)) {
ftp_chdir($this->ftp_conn, $current_dir);
return true;
}
return false;
}
/**
* 获取文件统计信息
*/
public function getStats($directory = '') {
$items = $this->listFiles($directory);
$stats = [
'total_dirs' => count($items['directories']),
'total_files' => count($items['files']),
'total_size' => 0,
'by_extension' => [],
'largest_file' => null,
'oldest_file' => null,
'newest_file' => null
];
foreach ($items['files'] as $file) {
// 统计总大小
if ($file['size'] > 0) {
$stats['total_size'] += $file['size'];
}
// 按扩展名统计
$ext = $file['extension'] ?: '无扩展名';
if (!isset($stats['by_extension'][$ext])) {
$stats['by_extension'][$ext] = 0;
}
$stats['by_extension'][$ext]++;
// 最大文件
if (!$stats['largest_file'] || $file['size'] > $stats['largest_file']['size']) {
$stats['largest_file'] = $file;
}
// 最旧文件
if ($file['modified'] > 0) {
if (!$stats['oldest_file'] || $file['modified'] < $stats['oldest_file']['modified']) {
$stats['oldest_file'] = $file;
}
// 最新文件
if (!$stats['newest_file'] || $file['modified'] > $stats['newest_file']['modified']) {
$stats['newest_file'] = $file;
}
}
}
// 按文件数排序扩展名
arsort($stats['by_extension']);
return $stats;
}
public function __destruct() {
if ($this->ftp_conn) {
ftp_close($this->ftp_conn);
}
}
}
// 使用示例
try {
$lister = new FTPFileLister('localhost', 'user', 'pass');
// 列出当前目录
echo "当前目录内容:\n";
$current_dir = $lister->listFiles();
echo "目录 (" . count($current_dir['directories']) . "):\n";
foreach ($current_dir['directories'] as $dir) {
echo " 📁 {$dir['name']}\n";
}
echo "\n文件 (" . count($current_dir['files']) . "):\n";
foreach ($current_dir['files'] as $file) {
$size = $file['size'] > 0 ? formatBytes($file['size']) : '未知';
echo " 📄 {$file['name']} ($size)\n";
}
// 使用过滤器列出图片文件
echo "\n图片文件:\n";
$images = $lister->listFiles('/public_html/images', [
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'bmp'],
'exclude_dirs' => ['thumbnails', 'backup']
]);
foreach ($images['files'] as $image) {
echo " 🖼️ {$image['name']}\n";
}
// 获取统计信息
echo "\n目录统计信息:\n";
$stats = $lister->getStats('/public_html');
echo "总目录数: {$stats['total_dirs']}\n";
echo "总文件数: {$stats['total_files']}\n";
echo "总大小: " . formatBytes($stats['total_size']) . "\n";
if ($stats['largest_file']) {
echo "最大文件: {$stats['largest_file']['name']} (" .
formatBytes($stats['largest_file']['size']) . ")\n";
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
// 辅助函数:格式化字节大小
function formatBytes($bytes, $precision = 2) {
if ($bytes <= 0) return '0 Bytes';
$units = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
$base = log($bytes, 1024);
$pow = floor($base);
$pow = min($pow, count($units) - 1);
return round(pow(1024, $base - $pow), $precision) . ' ' . $units[$pow];
}
?>
注意事项
- 返回的文件名可能是相对路径或绝对路径,具体取决于FTP服务器
- 某些FTP服务器可能不返回
. 和 .. 目录项
- 如果目录不存在或无权限访问,函数将返回
false
- 对于空目录,函数可能返回空数组或包含
. 和 .. 的数组
- 返回的列表不区分文件和目录,需要额外判断
- 某些FTP服务器可能对目录路径大小写敏感
- 使用被动模式(
ftp_pasv())可以提高兼容性
最佳实践建议
- 使用
ftp_pasv($ftp_conn, true)启用被动模式
- 总是检查返回值是否为
false,以处理错误
- 使用
ftp_chdir()辅助判断条目是否为目录
- 对返回的列表进行排序,提供更好的用户体验
- 实现递归函数以遍历整个目录树
- 添加适当的错误处理和日志记录
- 不要假设返回的列表包含
.和..目录项
- 避免在不检查返回值的情况下直接使用结果数组
- 不要假设返回的文件名是相对路径还是绝对路径
- 避免在循环中多次调用
ftp_nlist(),可能造成性能问题
- 不要忘记处理特殊字符和编码问题
- 避免在没有错误处理的情况下使用
常见问题解答
ftp_nlist() 返回的列表不直接区分文件和目录。可以通过以下方法判断:
- 使用
ftp_chdir() 尝试切换到该条目,成功则为目录
- 检查条目是否以斜杠结尾(某些FTP服务器会这样表示目录)
- 使用
ftp_rawlist() 获取详细信息并解析文件类型
- 结合
ftp_size(),目录通常返回 -1
这取决于FTP服务器的配置:
- 有些服务器返回相对于当前目录的相对路径
- 有些服务器返回绝对路径
- 可以通过
ftp_pwd() 获取当前目录,然后拼接相对路径
- 建议使用
basename() 获取文件名,dirname() 获取目录名
- 为了兼容性,最好编写能处理两种情况的代码
当目录包含大量文件时:
- 使用分页或懒加载技术,避免一次性获取所有文件
- 考虑使用
ftp_rawlist() 并解析,可能更高效
- 实现缓存机制,避免重复查询
- 设置超时时间,避免长时间等待
- 对结果进行过滤,只获取需要的文件
- 考虑使用异步处理,避免阻塞主线程
相关函数
ftp_rawlist() - 返回指定目录的详细列表
ftp_chdir() - 改变FTP服务器上的当前目录
ftp_pwd() - 返回当前目录名称
ftp_size() - 返回指定文件的大小
ftp_mdtm() - 返回指定文件的最后修改时间
ftp_cdup() - 切换到上级目录
ftp_mkdir() - 在FTP服务器上创建目录
ftp_rmdir() - 删除FTP服务器上的目录