一、文件系统概述
1、文件类型
php使用UNIX的文件系统为模型,所以在UNIX(Linux)中,可以获得7中类型,而在Windows系统中只能获取到file、dir和unknow 3中基本文件类型。
文件类型 | 描述 |
file | Windows/Linux/Mac/… |
dir | 普通类型,如记事本文件、图片文件等 |
link | 目录类型 |
char | 符号链接类型,类似Windows快捷方式图标,可理解为指向文件的指针 |
block | 套接字类型,以字符进行传输的设备 |
fifo | 设备类型,可以理解为某个磁盘分区 |
unknow | 未知类型 |
php程序和通过内置的函数filetype()即可获得目标文件的上述类型,对于已知的文件还可以使用is_file()函数来检查其是否为一个正常的文件。类似地,可使用is_dir()判断给定的文件名参数是否是一个目录,使用is_link()判断该文件是否是一个符号链接(指针)。
(UNIX平台上规定路径时,使用斜杠(/)用作目录分隔符;而在Windows上斜杠(/)和反斜杠(\)均可使用,一个反斜杠等同于两个斜杠,功能和UNIX下面的斜杠相同)
2、文件属性
文件的相关属性、大小、创建时间、修改时间等是文件的基本属性信,通常在计算机中可以用右键->属性查看到。在php中如果需要将这些基本信息显示到桌面上,就可以使用stat()和lstat()函数返回关于文件的信息(返回类型为数组类型,其数组下表对应的中文名称解释如下表所示:数组下标和数组键名对应,既可以使用数组下表访问也可以使用数组键名进行访问)这两个函数功能图相似,只是lstat()函数将返回符号连接的状态。
下 标 | 键 名 | 描 述 | 对应函数 |
0 | dev | 设备编号 | link() |
1 | ino | inode编号 | fileinode() |
2 | mode | inode保护模式 | - |
3 | nlink | 被连接数 | filegroup() |
4 | uid | 所有者用户ID | fileowner() |
5 | gid | 所有者用户组ID | filegroup() |
6 | rdev | 设备类型 | - |
7 | size | 文件大小(单位:字节) | filesize() |
下面是输出文件属性的实例:
打印出的结果如下所示:
Array( [0] => 3 [1] => 0 [2] => 33206 [3] => 1 [4] => 0 [5] => 0 [6] => 3 [7] => 18 [8] => 1321862509 [9] => 1317697122 [10] => 1321862509 [11] => -1 [12] => -1 [dev] => 3 [ino] => 0 [mode] => 33206 [nlink] => 1 [uid] => 0 [gid] => 0 [rdev] => 3 [size] => 18 [atime] => 1321862509 [mtime] => 1317697122 [ctime] => 1321862509 [blksize] => -1 [blocks] => -1)
在php中,大量的文件系统函数使用后会产生缓存,可使用clearstatcache()函数来清除缓存。
上面的函数是一次性打印出所有的属性信息,当然也可以使用php内置的函数打印出目标文件或目录的单独属性信息,下表给出了部分单独属性打印函数及其中文说明。
函 数 | 描 述 | 返回值 | PHP版本 |
file_exists() | 检查文件或目录是否存在 | TRUE/FALSE | 3 |
fileatime() | 文件的上次访问时间 | 返回NUIX时间戳格式 | 3 |
filemtime() | 文件的上次修改时间 | 返回NUIX时间戳格式 | 3 |
filesize() | 文件的大小 | 返回字节数/FALSE | 3 |
filetype() | 返回文件的类型 | TRUE/FALSE | 3 |
is_executable() | 判断文件是否可执行 | 存在且可执行返回TRUE | 3 |
is_readable() | 判断文件是否可读 | 存在且可读返回TRUE | 3 |
is_writable() | 判断文件是否可写 | 存在且可写返回TRUE | 4 |
3、文件的访问权限
用户、用户组
在php中,使用fileowner()和filegroup()可以返回目标文件的所有者ID和隶属组ID。在拥有root权限的前提下,可以使用chown()和chgrp()修改文件的的相关权限,而一般情况下,php都没有获得root权限,使用这两个函数有一定的限制。
访问权限
权 限 编 号 | 权 限 编 码 |
444 | r--r—r-- |
600 | rw------- |
644 | rw-r--r-- |
666 | rw-rw-rw- |
700 | rwx------ |
744 | rwxr--r-- |
上表中,三位数字代表9位权限编码,分成三部分,第一部分代表所有者的权限,第二部分代表同组(用户组)用户权限,第三部分代表其他用户权限。每部分的三位分别是rwx的对应位置,如果有则值分别对应421,无则用-表示。可通过权限修改函数chmod()修改权限。
4、路径处理
有时候需要获得一个文件路径信息或者部分信息,如一个路径中的绝对文件名、文件夹名、不带有文件扩展名的文件名等,此时可以使用php内置系统函数basename()、dirname()和pathinfo(),前两个函数返回字符串并可直接打印,pathinfo()则返回一个数组。
如下示例代码:
'; //获得不带有文件扩展名的文件名 echo basename($url,".php").''; //获得该路径的完整文件夹名 echo dirname($url).''; //使用pathinfo();返回定义路径的信息数组 $arr = pathinfo($url); print_r($arr);?>
程序打印输入结果如下:
index.phpindex/php100Array( [dirname] => /php100 [basename] => index.php [extension] => php [filename] => index)
UNIX和Windows路径的写法有所不同,在日常开发中,考虑到程序的移植性,推荐使用UNIX的路径写法,即使用(”/“)。
二、文件的基本操作
1、打开与关闭
用函数fopen()打开,函数fclose()关闭
fopen有两个参数,第一个是所要操作文件的文件名,第二个是文件打开的模式,具体打开模式可参见下表:
打 开 模 式 | 描 述 |
r | 以只读方式打开,将文件指针指向文件头 |
r+ | 以读写方式打开,将文件指针指向文件头 |
w | 以写入方式打开,将文件指针指向文件头,并将文件大小截为0,如果文件不存在,则尝试创建之 |
w+ | 以读写方式打开,将文件指针指向文件头,并将文件大小截为0,如果文件不存在,则尝试创建之 |
x | 创建并以写入方式打开,并将文件指针指向文件头。如果文件已存在,则fopen()调用失败并返回FALSE,并生成一条E_WARNING级别的错误信息。 |
x+ | 创建并以读写入方式打开,并将文件指针指向文件头。如果文件已存在,则fopen()调用失败并返回FALSE,并生成一条E_WARNING级别的错误信息。 |
a | 以写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之 |
a+ | 以读写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之 |
如果打开文件失败,fopen()返回FALSE;若成功,返回当前打开文件的指针,该指针是其他文件处理函数对目标文件进行读、写以及其他操作的资源
2、PHP读取内容
在使用fopen()打开文件之后,即可对目标函数进行读取,常使用的函数有fread()、fgets()、fgetc(),其基本语法格式如下:
string fread(int $handle, int $length);
string fgets(int $handle [, int $length]);string fgetc(resource $handle);除此之外还有file()、file_get_contents()和readfile()函数,它们也可完成读取文件的功能。
1、函数fread()
该函数用来打开目标文件中指定读取长度的字符串,可安全用于二进制文件。在遇到换行符(包括在返回值中)、EOF或者已经读取了length-1字节后停止(要看先遇到哪一种情况)。如果没有指定length,则默认为1kb,即1024字节。若失败,则返回FALSE。其中feof()是判断是否到达文件末尾的函数。
2、函数fgets()
该函数读取一次最多读取一行内容,在碰到换行符(包括在返回值中)、EOF或者已经读取了length-1字节后停止(要看先遇到哪一种情况)。如果没有指定length,则默认为1kb,即1024字节。若失败,则返回FALSE。
3、函数fgetc()
该函数只读取打开文件当前指针位置处的一个字符,若遇到文件结束标志EOF时,将返回FALSE。
4、函数file()
函数file()非常有用,它把整个文件读入一个数组中,各元素的分割是根据目标文件内容的换行符决定的。与file_get_contents()类似,不同的是file()函数将文件作为一个数组返回。数组中的每个单元都是文件中对应的一行,包括换行符在内。如果失败,返回FALSE。
3、PHP写入文件内容
php所提供的fwrite()函数可以将字符串内容写入到文件中
int fwrite(resource $handle, string $string [, int $length])
参数string 为要写入的字符串,length为可选参数表示要写入的最大字节数。fwrite()返回写入的字符数,出现错误时,返回FALSE。该函数的别名为fputs()。
4、PHP删除文件
unlink()可以指定删除目标文件,若成功返回TRUE,失败返回FALSE。若目标文件不存在、权限错误、文件被锁定,或目标文件是一个目录而非普通文件,则将无法删除。
5、文件截取、远程读取操作
1、文件截取 有时候需要把文件截取到指定的长度,此时便可以使用ftruncate()函数
bool ftruncate(resouce $handle, int $size)
函数说明:ftruncate()会将参数$handle指定的文件大小改为参数size指定的大小。参数$handle为文件指针句柄,如果原来的文件件大小比参数size大,则超过的部分会被删去,比参数size小,相当于用size参数来扩充容量。
此外,文件只会在append模式下改变(即a或者a+)。在write模式下,必须加上fseek()操作。在php4.3.3之前,ftruncate()函数成功时返回的是一个整数1而不是布尔值TRUE。
2、远程文件
在php中不仅可以访问本地文件,还可以通过HTTP、FTP等协议访问远程文件,也正因为有远程访问文件,php的文件操作才更加有意义。用户可利用该特性开发各种采集程序(如抓取网页内容),而这需要在php的配置文件中php.ini中激活allow_url_fopen选项。
resource fopen(string $filename, string $mode [, bool $use_include_path [, resource $context]]);
要访问远程文件,只需要在fopen()中填写正确的URL,fopen()函数将目标文件名指定的名字绑定到一个流上。如果文件名是”schema://...“格式,则被当成一个URL,php将搜索协议处理器(也称封装协议)来处理次模式。如果该协议尚未注册封装协议,PHP将会发出一条消息来帮助检查脚本中潜在的问题并将目标文件当成一个普通的文件名继续执行下去。
若使用HTTP协议,可以利用fopen()函数进行一些采集工作,例如新闻、股票、天气等宿主站点实时更新的数据。下面是使用fopen()函数采集搜狐IT新闻的示例:
·(.*)<\/a>/sU";//修正符s说明,将.的匹配更改为所有,包括换行符在内,修正符U,表示以非重复模式工作,默认是重复的。 preg_match_all($code,$content,$urlrr); foreach($urlrr[1] as $k=>$v){ echo "".$urlrr[2][$k]."".""; echo " ".$urlrr[1][$k].""; echo "
"; }?>
上述程序很好地诠释了PHP在远程文件的访问获取,并剔除了多余代码,通过正则表达式获取出需要的代码进行页面的重新打印。使用这种方法的好处是:当远程宿主的内容更新时,采集方无须更改任何内容,系统将自动实时更改。但是使用PHP(或其他任何语言)进行远程访问时,运行效率是一大硬伤,其解决方案有很多,可以设置脚本运行时间、使用缓存、采集存储进数据库等。
提示:可以使用set_time_limit()函数来对程序的运行最长时间加以限制(缩短),这样在服务器效率上会有所提高。该函数接收一个事件参数,单位为秒。若设置为“0”,则对脚本的运行时间没有任何限制。
3、文件指针
通过ftell()、fseek()、rewind()三个函数对指针位置进行操作,从而达到指定指针位置的功能。
格式:
int ftell(resource $handle);int fseek(resource $handle, int $offset [, int $whence]);bool rewind(resource $handle);
使用上述参数前,必须提供一个有效的fopen()函数打开文件得到的指针
ftell()函数返回文件指针的当前位置。若失败,返回FALSE。 fseek()函数把文件指针从当前位置向前或者向后移动到新的位置,新位置从文件头开始以字节度量。成功返回0,否则返回-1。注意移动到EOF的位置不会产生错误。 rewind()函数将文件指针的位置倒回文件的开头。若成功,返回TRUE,若失败,返回FALSE。4、临时文件
在开发过程中,若需要生成一个临时文件来临时存储数据,可以使用tmpfile()和tempnam()函数。格式:resource tmpfile(void);string tempnam(string $dir, string $prefix);
tmpfile()函数的使用是没有参数的,只需要将它赋值给一个变量即可获得一个文件指针句柄,即可写入。tempnam()函数接收两个参数,用于创建临时文件目录和文件名的前缀。
5、锁定文件在远程文件读写的时候,不同的访问进程会在同一时间读写同一个文件,发生这种情况时就有可能造成数据的紊乱或文件的破坏,解决方案就是使用flock函数,该函数可以避免多个进程同时对文件进行读写,从而避免文件数据被破坏。格式:bool flock(int $handle, int $operation [,int &$wouldblock]);
operation为锁定类类型,wouldlock 为可选参数,若设置为1或者TRUE,则当进行锁定时阻塞其他进程。锁定类型有以下几种:
取得共享锁定(读取的程序):LOCK_SH 取得独占锁定(写入的程序):LOCK_EX 释放锁定(无论共享或者独占):LOCK_UN 如果不希望flock()函数在锁定时堵塞:LOCK_NB锁定文件的实例:在非Windows的系统中,以上代码会产生阻塞,如要避免这种阻塞,可将锁定代码处改为“flock($fp, LOCK_UN+LOCK_NB)”,或者通过设置wouldlock的值来解决。
锁定操作也可被fclose释放。
6、复制文件
格式:bool copy(string $source, string $dest);
提示:如果目标文件已存在,将会被覆盖。从PHP4.3.0开始,如果启用了“fopen wrapers”,被复制目标文件和复制目的地都可以是URL。若复制的目的地是一个URL,如果封装协议不支持覆盖已有的文件时,复制操作就会失败。
三、目录的基本操作
1、新建目录
格式:bool mkdir(string $pathname [, int $mode [, bool $recursive [, resource $context]]]);
2、删除目录和递归删除目录
当目录是空目录时,可以使用rmdir(),直接删除。若非空,需要使用递归删除目录。rmdir()接受的参数是一个将要删除的目录名。若执行成功,则返回TRUE,否则返回FALSE。与unlink()函数共同使用时用于删除一个不明确的文件。下面为递归删除目录的实例:
$file删除成功!\n"; } else { echo "文件$file删除失败!\n"; } } } //现在,删除当前目录 if (rmdir ($dir)) { echo "目录$dir删除成功!\n"; } else { echo "目录$dir删除失败!\n"; } } //测试程序 $dir = "test"; deleteDir ($dir); unlink("test");?>
3、移动和删除目录
移动或者复制目标目录是文件操作的基本功能之一,在PHP中没有给出特定的函数,开发者自己可以通过文件的复制、删除、重命名等方法简单实现。重命名:bool rename(string $oldname,string $newname [, resource $context]);
文件移动:利用函数的思想实现的文件移动的实例:
复制目录:
与删除一个非空目录一样,要复制一个包含子目录的文件,将涉及到文件复制、目录简历等操作。这也是一个目标遍历和递归综合处理方案。
首先,先对目标目录进行遍历,如果遇到的是普通文件,可直接使用copy()函数进行复制,如果遇到一个目录,则必须简历该目录,然后对该目录中的文件或子目录进行复制操作。如此递归执行下去,最终将整个目录复制完毕。
下面为使用便利和递归实现目录复制的实例:
4、遍历的目录
要取得一个目录下的文件和子目录,可以使用两种方法:便利目录和检索目录。其中,遍历目录结构可使用opendir()、readdir()、closedir()和rewinddir()函数。相关介绍见下表:
函数名 | 描述 |
opendir() | opendir()打开一个目录句柄,可由closedir()、readdir()和rewinddir()函数使用。若成功,则该函数返回一个目录流,否则返回FALSE以及一个error。可以通过在函数名前加“@”来隐藏error的输出 |
readdir() | readdir()函数返回由opendir()打开目录中的条目。若成功则函数返回一个文件名,否则返回false |
closedir() | closedir()函数关系由opendir()函数打开的目录句柄 |
rewinddir() | rewinddir()函数重置由opendir()函数打开的目录句柄,该函数无返回值 |
下面为遍历目录的实例:
';echo '';echo '文件名文件大小文件类型创建时间修改时间是否可读是否可写可执行';while($file=readdir($handle)) { //使用readdir循环读取目录里的内容$dirFile=$url."/".$file; //将目录下的文件和当前目录连接起来,才能在程序中使用echo '';echo ''.$file.'';echo ''.filesize($dirFile).'';echo ''.filetype($dirFile).'';echo ''.date("Y/n/t",filectime($dirFile)).'';echo ''.date("Y/n/t",filemtime($dirFile)).'';$read = is_readable($dirFile)? '支持' : '不支持'; //是否可读$writable = is_writable($dirFile)? '支持' : '不支持'; //是否可写$executable = is_executable($dirFile)? '支持' : '不支持'; //是否可执行echo ''.$read.''; echo ''.$writable.''; echo ''.$executable.''; echo '';}echo '';closedir($handle); //关闭文件操作句柄?>
四、文件的上传与安全1、相关设置1、客户端
enctype="multipart/form-data"用来指定表单编码方式,通知服务器端传递一个文件,并带有常规表单数据,其中隐藏表单MAX_FILE_SIZE中的value的值为允许上传文件的最大值(单位字节)。这仅仅是本地客户端的限制,实际上还需要在客户端进行配置。打开php.ini文件,找到upload_max_filesize进行最大值的设置,这样才能有效地对上传文件大小进行设置。
2、服务器
在服务器端,不仅可以在PHP配置文件php.ini中设置上传文件的最大值,还可以对内存分配、是否接受上传、临时路径等进行设置。
相关介绍可参见下表
配置名 | 默认值 | 描述 |
file_uploads | ON | 服务器是否支持上传 |
memory_limit | 8MB | 分配最大内存 |
upload_max_filesize | 2MB | 限制上传文件最大值,必须小于post_max_size |
post_max_size | 8MB | 限制通过POST模式可接受数据的最大值,必须大于upload_max_size |
up_tmp_dir | NULL | 上传文件临时存放路径 |
3、$_FILES 全局数组
文件上穿后首先存放在服务器的临时目录中,这时PHP将获得一个$_FILES的临时数组,成功上传后的文件信息被保存在这个数组中。可通过对$_FILES进行相关的信息打印和各种操作。
$_FILES的相关元素第一个统一为upfiles,第二个可以为name、type、size、tmp_name或error等文件基本信息元素。
$_FILES['upfiles']['name'] :上传文件在客户端的原名称。
$_FILES['upfiles']['type'] :文件类型。
$_FILES['upfiles']['size'] :已上传的大小。
$_FILES['upfiles']['tmp_name'] :服务端临时文件名。
$_FILES['upfiles']['error'] :伴随上传时产生的错误信息代码。
错误信息代码详见下表:
错误值 | 描述 |
0 | 上传成功 |
1 | 文件超出了php.ini的upload_max_filesize限制值 |
2 | 文件超出了HTML表单中的MAX_FILE_SIZE限制值 |
3 | 只有部分被上传 |
2、但文件上传
实例代码如下所示:
中的name属性一致。 if (move_uploaded_file($_FILES["files"]["tmp_name"], $url)) { echo '文件上传成功!'; print_r($_FILES); } else { echo '文件上传失败!'; print_r($_FILES); }?>
上传后的文件通过了判断,则将其从临时目录复制到指定的存放位置,这时可以使用copy函数。PHP也为我们提供了用于上传文件移动的函数move_upload_file(),该函数仅能在上传文件时使用。
3、多文件的上传与安全
1、多文件上传
多文件上传和单文件上传的方式相同,只需要在HTML中增加多个file类型的表单,并制定不同的name值即可,实例代码如下:
在以上代码中,将三个文件的表单文件基本信息以数字形式组织在了一起,当表单提交到PHP进行处理时,在服务器端使用全局数组$_FILES存储所有上传文件信息数据。$_FILES将成为一个三维数组在PHP文件中可以使用print_r($_FILES)函数对该数组进行查看,如下图所示:
Array( [upload] => Array ( [name] =>Array ( [0] => tt.png [1] => tt2.png [3] => 大胖.bmp ) [type] =>Array ( [0] => image/x-png [1] => image/x-png [3] => image/bmp ) [tmp_name] =>Array ( [0] => D:\wamp\tmp\php49F1.tmp [1] => D:\wamp\tmp\php4A11.tmp [3] => D:\wamp\tmp\php4A21.tmp ) [error] =>Array ( [0] => 0 [1] => 0 [3] => 0 ) [size] =>Array ( [0] => 89481 [1] => 60080 [3] => 921654 ) ) )
单文件上传和多文件上传的原理一样,只是$_FILES的数组结构有所改变。
2、安全相关
在Web开发中,存在众多安全隐患,其中上传模块是个高危区,也是入侵者最喜欢尝试的一个选择,因为一旦在上传模块找到漏洞,就有可能被上传到WebShell,从而控制整个服务器。
PHP上传及相关安全注意事项详见下表
错误值 | 描述 |
PHP | 最简单也是最基本的上传模块必须要有的功能,可以通过“$_FILES[‘files’][‘type’]”语句来获取文件的类型 |
PHP | 用is_upload_file()和move_upload_file()函数判断目标文件是不是根据HTTP协议上传和移动的 |
PHP | 禁止一些对系统有潜在威胁的函数和设置 |
PHP | 只要是通过WEB提交的数据,都要经过严格过滤 |
PHP | eval()是一个危险的函数 |
Apache | 对相应目录的权限进行设置,如存储上传文件目录只给予只读权限;禁止对外目录执行不必要的可写权限 |
MySQL | 避免弱口令 |
ALL | 适时关注相关高危漏洞是否爆发,即使更新套件版本 |