所有目录 1.文件及文件系统的定义 2.linux文件的类型 3.linux文件的权限 4.文件操作 4.1 文件的创建 4.2 文件的打开及关闭 4.3 文件的读写操作 4.4 文件上锁 4.5 文件的定位 4.6 特殊文件的操作 4.6.1 目录文件的操作 4.6.2 链接文件的操作 5.部分函数说明 ************************************************************* 正文 ************************************************************* 1.文件及文件系统的定义 文件是指有名字的一组相关信息的集合。文件系统是指按照一定规律组织起来的有序的文件组织结构,是构成系统中所有数据的基础。linux系统中,文件的准确定义是不包含有任何其他结构的字符流。换句话说,文件中的字符和字符之间除了同属于一个文件之外,不存在任何其他的关系。linux系统提供的文件系统,是树形层次结构系统。Linux中常用的文件系统主要有ext3、ext2及reiserfs 。 2.linux文件的类型 linux下最常见的文件类型有5种,它们是普通文件,目录文件,链接文件,字符设备文件和块设备文件,管道文件,套接口文件。
我们用 ls -lh 来查看某个文件的属性,可以看到有类似 -rw-r--r-- ,值得注意的是第一个符号是 - ,这样的文件在Linux中就是普通文件。这些文件一般是用一些相关的应用程序创建,比如图像工具、文档工具、归档工具... .... 或 cp工具等。这类文件的删除方式是用rm 命令。当我们在某个目录下执行,看到有类似 drwxr-xr-x ,这样的文件就是目录,目录在Linux是一个比较特殊的文件。注意它的第一个字符是d。创建目录的命令可以用 mkdir 命令,或cp命令,cp可以把一个目录复制为另一个目录。删除用rm 或rmdir命令。我们可以看到/dev/tty的属性是 crw-rw-rw- ,注意前面第一个字符是 c ,这表示字符设备文件。比如猫等串口设备我们看到 /dev/hda1 的属性是 brw-r----- ,注意前面的第一个字符是b,这表示块设备,比如硬盘,光驱等设备;这个种类的文件,是用mknode来创建,用rm来删除。目前在最新的Linux发行版本中,我们一般不用自己来创建设备文件。因为这些文件是和内核相关联的。当我们启动MySQL服务器时,会产生一个mysql.sock的文件。注意这个文件的属性的第一个字符是 s。我们了解一下就行了。当我们查看文件属性时,会看到有类似 lrwxrwxrwx,注意第一个字符是l,这类文件是链接文件。是通过ln -s 源文件名 新文件名 。上面是一个例子,表示setup.log是install.log的软链接文件。怎么理解呢?这和Windows操作系统中的快捷方式有点相似。
这儿有个程序,他可以列出当前目录下的文件信息,以及系统“/dev/sda1”和“/dev/lp0”的文件信息。 #include #include #include #include #include #include int main() { int newret; printf("列出当前目录下的文件信息:\n"); newret=system("ls -l"); printf("列出“/dev/sda1”的文件信息:\n"); newret=system("ls /dev/sda1 -l"); printf("列出“/dev/ lp0”的文件信息:\n"); newret=system("ls /dev/lp0 -l"); return 0; } 大家可以调试一下。其中system()函数说明如下:表头文件 #i nclude 定义函数 int system(const char * string); 函数说明 system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。 返回值 =-1:出现错误 =0:调用成功但是没有出现子进程 >0:成功退出的子进程的id 如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值>。 如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。 附加说明 在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题。 3.文件的权限 linux文件的权限可分为四种:可读取(Readable),可写入(writable),可执行(eXecute)和无权限,分别用r,w,x和-表示。在linux系统中,对于任意一个文件来说,他都有一个特定的所有者,也是创建此文件的用户,同时,由于linux中用户是按组分类的,一个用户属于一个或多个组。文件所有者以外的用户,又可以分为文件所有者的同组用户和其他用户。因此,linux系统按文件所有者,文件所有者同组用户和其他用户三类规定了了不同的文件访问权限。用ls -lh显示的作为权限的10个字符,可分为四部分:1).第一位:一般表示文件类型。2).第二到第四位:表示文件所有者的访问权限。3).第五位到第七位:表示文件所有者同组用户的访问权限。4).第八位到第十位:表示其他用户的访问权限。上面介绍了system()函数,可以用system函数编程来实现对文件权限的修改。当然还有个chmod()函数也可以用来设置文件的权限。如果希望每个新建的文件都能被赋予某种权限,则需要由系统的权限掩码设置函数umask来决定。下面关于umask函数的用法有个例子。 #include #include #include #include #include #include int main() { mode_t new_umask,old_umask; new_umask=0666; old_umask=umask(new_umask); printf("系统原来的权限掩码是:%o\n",old_umask); printf("系统新的权限掩码是:%o\n",new_umask); system("touch hu1"); printf("创建了文件hu1\n"); new_umask=0444; old_umask=umask(new_umask); printf("系统原来的权限掩码是:%o\n",old_umask); printf("系统新的权限掩码是:%o\n",new_umask); system("touch hu2"); printf("创建了文件hu2\n"); system("ls hu1 hu2 -l"); return 0; } 该程序的运行结果是:系统原来的权限掩码是:22 系统新的权限掩码是:666 创建了文件hu1 系统原来的权限掩码是:666 系统新的权限掩码是:444 创建了文件hu2 ---------- 1 hubin hubin 0 2009-08-02 11:07 hu1 --w--w--w- 1 hubin hubin 0 2009-08-02 11:07 hu2 程序结果说明:先将系统的权限掩码为0666,所以新建的文件hu1访问权限为0000,即"--------".再将系统的权限掩码为0444,所以新建的文件hu2访问权限为0444,即"--w--w--w-".语句system("touch hu1")的作用是调用system函数来运行shell命令"touch hu1",touch命令的作用是更改时间标记,如文件不在,则新建文件。 unmask函数说明: 所需头文件:#include #include 函数功能:设置建立新文件时的权限掩码 函数原型:mode_t umask(mode_t mask); 函数传入值:4位八进制数 函数返回值:返回值为原先系统的umask值 要想获得文件的其他属性,可以使用stat函数或者是fstat函数,其函数说明如下,例子见附件的st_mode.c.
函数原型
#include
int stat(const char *restrict pathname, struct stat *restrict buf);
提供文件名字,获取文件对应属性。
int fstat(int filedes, struct stat *buf);
通过文件描述符获取文件对应的属性。
int lstat(const char *restrict pathname, struct stat *restrict buf);
连接文件描述命,获取文件属性。
2 文件对应的属性
struct stat {
mode_t st_mode; //文件对应的模式,文件,目录等 ino_t st_ino; //inode节点号 dev_t st_dev; //设备号码 dev_t st_rdev; //特殊设备号码 nlink_t st_nlink; //文件的连接数 uid_t st_uid; //文件所有者 gid_t st_gid; //文件所有者对应的组 off_t st_size; //普通文件,对应的文件字节数 time_t st_atime; //文件最后被访问的时间 time_t st_mtime; //文件内容最后被修改的时间 time_t st_ctime; //文件状态改变时间 blksize_t st_blksize; //文件内容对应的块大小 blkcnt_t st_blocks; //伟建内容对应的块数量 };
可以通过上面提供的函数,返回一个结构体,保存着文件的信息。
4.文件操作Linux系统把目录,设备和连接等操作都等同于文件的操作。它通过一个文件描述符来进行区分和引用特定的文件。文件描述符是一个非负的整数,是一个索引值,指向内核中每个进程打开的文件的记录表。 Linux系统中,基于文件描述符的文件的操作有:不带缓存的文件I/O操作和带缓存的流文件I/O操作。不带缓存的文件I/O操作又称系统调用I/O操作,符合POSIX标准,设计的程序能在兼容POSIX标准的系统间方便的移植。主要的函数有:creat,open,close,read,write,lseek,flock,fcntl等。带缓存的流文件I/O操作是在内存开辟一个“缓存区”,为程序中的每一个文件使用。带缓存的文件I/O操作又称标准I/O操作,符合ANSI C标准,比不带缓存的文件I/O程序方便移植。主要函数有fopen,fclose,fgetc,fputc,fgets,fread,fwrite,fseek,rewind,ftell等。由于流文件的I/O操作在C语言经常用到,一般的C语言书上也有介绍,所以就不多说了,其函数原型见部分函数说明。 4.1 文件的创建。 在linuxC程序设计中,创建文件可以调用creat函数。 int creat(const char *filename, mode_t mode); 参数mode指定新建文件的存取权限,它同umask一起决定文件的最终权限(mode&umask),其中umask代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变:下面有个例子说明该函数的用法,程序是在“/home”目录下创建一个“file”的文件,并把此文件的权限为所有者具有只读权限。 #include #include #include #include #include #include int main() { int fd; fd=creat("/home/file",S_IRUSR);/*所有者具有只读权限*/ system("ls /home/file -l"); return 0; } 有兴趣的可以调一下。如果所创建的文件原本就存在,则就不能创建该文件了。 附creat函数的说明: creat函数:创建一个文件 相关头文件:#include 函数表达式:int creat(const char *pathname,mode_t mode); 参数说明:参数pathname表示需要创建的文件的路径。参数mode表示文件的文件权限。 返回值说明:如果成功创建一个文件则返回新的文件描述符,失败则返回-1. 函数功能详解:creat函数创建一个新文件,并以“只写”的方式打开,返回新文件的文描述符。 函数使用说明: crea函数用于创建一个文件,并且将其以“只写” 形式打开。因此程序是无法读该文件的。如果需要读这个新文件中的内容,则必须将其关闭后从新打开。 creat函数的第一个参数表示的文件不存在,则创建该文件并且以“只写”方式打开;如果该文件爱你存在则将其截短谓0,再以“只写”方式打开。因此使用creat函数要注意文件重名时可能带来的问题。以下是定义于 <sys/stat.h> 中的九种文件访问权限位(用于构成参数 mode): S_IRUSR // user-read(文件所有者读) S_IWUSR // user-write(文件所有者写) S_IXUSR // user-execute(文件所有者执行) S_IRGRP // group-read S_IWGRP // group-write S_IXGRP // group-execute S_IROTH // other-read S_IWOTH // other-write S_IXOTH // other-execute 其中 user 指文件所有者,group 指文件所有者所在的组,other 指其他用户。 creat 函数只能以只读方式创建新文件。如果我们要以读写方式创建新文件,可以用 open 函数:由于open函数比creat函数好用,所以open函数用得较多。 4.2 文件的打开和关闭 文件的打开可以用open,fopen函数。关闭用close和fclose函数。open和close属于不带缓存的文件I/O操作而fopen和fclose函数属于流文件操作。当用open函数时,即使原来的文件不存在,也可以用open函数创建文件。在打开或者创建文件时,可以指定文件的属性及用户的权限等参数。close函数则关闭一个打开的文件而fopen和fclose函数在C语言编程就用得很多了,这里就不举例子 例:要求在“./home”下以可读写的方式打开一个名为"file"的文件。如果该文件不存在,则创建此文件;如果存在,将文件清空后关闭。 #include #include #include int main() { int fd; if((fd=open("/home/file",O_CREAT|O_TRUNC|O_WRONLY,0600))<0) { printf("打开文件出错"); exit(1); } else { printf("打开(创建)文件“file”,文件描述符为:%d",fd); } if(close(fd)<0) { printf("关闭文件出错"); exit(1); } system("ls /home/file -l"); return 0; } open 函数的简单描述 #include <fcntl.h> int open(const char *pathname, int oflag, ... /* mode_t mode */); 返回值:成功则返回文件描述符,否则返回 -1 对于 open 函数来说,第三个参数(...)仅当创建新文件时才使用,用于指定文件的访问权限位(access permission bits)。pathname 是待打开/创建文件的路径名(如 C:/cpp/a.cpp);oflag 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。 O_RDONLY 只读模式 O_WRONLY 只写模式 O_RDWR 读写模式 打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的: O_APPEND 每次写操作都写入文件的末尾 O_CREAT 如果指定文件不存在,则创建这个文件 O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值 O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容 O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。 O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode) 以下三个常量同样是选用的,它们用于同步输入输出 O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。 O_RSYNC read 等待所有写入同一区域的写操作完成后再进行 O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O open 返回的文件描述符一定是最小的未被使用的描述符。 close函数说明: 头文件:#include <unistd.h> 函数原型:int close(int filedes); 返回值:0(成功)或者 -1(出错) 进程结束时,该进程打开的所有文件都会自动被内核(kernel)关闭。 4.3 文件的读写 当文件按指定的工作方式打开以后,就可以执行对文件的读和写。下面按文件的性质分类进行操作。针对文本文件和二进制文件的不同性质,对文本文件来说,可按字符读写或按字符串读写;对二进制文件来说,可进行成块的读写或格式化的读写。 下面是流文件的读写操作。 1. 读写字符 C提供fgetc和fputc函数对文本文件进行字符的读写,其函数的原型存于stdio.h头文件中,格式为: int fgetc(FILE *stream) fgetc( )函数从输入流的当前位置返回一个字符,并将文件指针指示器移到下一个字符处,如果已到文件尾,函数返回EOF,此时表示本次操作结束,若读写文件完成,则应关闭文件。 int fputc(int ch,FILE *stream) fputc()函数完成将字符c h的值写入所指定的流文件的当前位置处,并将文件指针后移一位。fputc()函数的返回值是所写入字符的值,出错时返回EOF 2. 读写字符串 C提供读写字符串的函数原型在stdio.h头文件中,其函数形式为: Char *fgets(char *str,int num,FILE *stream) fgets() 函数从流文件stream中读取至多num-1个字符,并把它们放入str指向的字符数组中。读取字符直到遇见回车符或E O F(文件结束符)为止,或读入了所限定的字符数。 int fputs(char *str,FILE *stream) fputs( )函数将str指向的字符串写入流文件。操作成功时,函数返回0值,失败返回非零值 3. 格式化的读写 前面的程序设计中,我们介绍过利用scanf( )和printf( )函数从键盘格式化输入及在显示器上进行格式化输出。对文件的格式化读写就是在上述函数的前面加一个字母f成为fscanf( )和fprintf( )。其函数调用方式: int fscanf(FILE *stream,char *format,arg_list) int fprintf(FILE *stream,char *format,arg_list) 其中,stream为流文件指针,其余两个参数与scanf( )和printf( )用法完全相同。 4. 成块读写 前面介绍的几种读写文件的方法,对其复杂的数据类型无法以整体形式向文件写入或从文件读出。C语言提供成块的读写方式来操作文件,使其数组或结构体等类型可以进行一次性读写。成块读写文件函数的调用形式为: int fread(void *buf,int size,int count,FILE *stream) int fwrite(void *buf,int size,int count,FILE *stream) fread()函数从stream 指向的流文件读取count (字段数)个字段,每个字段为size(字段长度)个字符长,并把它们放到b u f(缓冲区)指向的字符数组中。 fread()函数返回实际已读取的字段数。若函数调用时要求读取的字段数超过文件存放的字段数,则出错或已到文件尾,实际在操作时应注意检测。 fwrite( )函数从buf(缓冲区)指向的字符数组中,把count(字段数)个字段写到stream所指向的流中,每个字段为size个字符长,函数操作成功时返回所写字段数。 关于成块的文件读写,在创建文件时只能以二进制文件格式创建。 这些流文件的读写操作我们在C语言中经常用到,所以就不举例子了,下面的是不带缓存的文件读写,主要用到read函数,write函数和lseek函数。read函数用于将指定的文件描述符中读出数据。write函数用于向打开的文件写数据,写操作从文件当前位置开始。lseek函数用于在指定的文件描述符中将文件指针定位到相应的位置。 1. read 头文件:#include <unistd.h> 函数原型:ssize_t read(int filedes, void *buf, size_t nbytes); 返回值:读取到的字节数;0(读到 EOF);-1(出错) read 函数从 filedes 指定的已打开文件中读取 nbytes 字节到 buf 中。以下几种情况会导致读取到的字节数小于 nbytes : A. 读取普通文件时,读到文件末尾还不够 nbytes 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。 B. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行 C. 从网络读取时,网络缓存可能导致读取的字节数小于 nbytes 字节。 D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 nbytes 。 E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。 F. 在读取了部分数据时被信号中断。 读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。 2. write 头文件:#include <unistd.h> 函数原型:ssize_t write(int filedes, const void *buf, size_t nbytes) 返回值:写入文件的字节数(成功);-1(出错) write 函数向 filedes 中写入 nbytes 字节数据,数据来源为 buf 。返回值一般总是等于 nbytes,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。 例程:编写一个程序,在当前目录下创建用户可读写文件“hello.txt”,在其中写入“Hello, software weekly”,关闭该文件。再次打开该文件,读取其中的内容并输出在屏幕上 #include #include #include #include #include #include #define LENGTH 100 main() { int fd, len; char str[LENGTH]; fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /* 创建并打开文件 */ if (fd) { write(fd, "Hello, Software Weekly", strlen("Hello, software weekly")); /* 写入 Hello, software weekly字符串 */ close(fd); } fd = open("hello.txt", O_RDWR); len = read(fd, str, LENGTH); /* 读取文件内容 */ str[len] = '\0'; printf("%s\n", str); close(fd); } 4.4 文件的上锁 Linux是多用户操作系统,多个用户共同使用,操作同一个文件的事情很容易就发生。linux为了避免这种情况,就给这个文件上锁,以避免共享资源产生竞争,导致数据读写错误。Linux中给文件上锁主要有建议性锁和强制性锁。建议性锁是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。强制性锁是由内核执行的。当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。给文件加建议性锁的是flock函数,加强制性锁的是fcntl函数。,一般情况下,系统使用强制性锁,而很少使用建议性锁。 fcntl()用来操作文件描述词的一些特性。参数fd代表欲设置的文件描述词,参数cmd代表欲操作的指令。文件描述词,当一个文件打开后,系统会分配一部分资源来保存该文件的信息.以后对文件的操作就可以直接引用该部分资源了,文件描述词可以认为是该部分资源的一个索引,在打开文件时返回.fcntl是用来对文件的一些属性进行设置的,需要一个文件描述词参数.有以下几种情况: F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参 数fd的文件描述词。执行成功则返回新复制的文件描述词。请参考dup2()。F_GETFD取得close-on-exec旗标。若此旗标的 FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。 F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。 F_GETFL 取得文件描述词状态旗标,此旗标为open()的参数flags。 F_SETFL 设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。 F_GETLK 取得文件锁定的状态。 F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。 F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。参数lock指针为flock 结构指针,定义如下 struct flcok { short int l_type; /* 锁定的状态*/ short int l_whence;/*决定l_start位置*/ off_t l_start; /*锁定区域的开头位置*/ off_t l_len; /*锁定区域的大小*/ pid_t l_pid; /*锁定动作的进程*/ }; l_type 有三种状态: F_RDLCK 建立一个供读取用的锁定 F_WRLCK 建立一个供写入用的锁定 F_UNLCK 删除之前建立的锁定 l_whence 也有三种方式: SEEK_SET 以文件开头为锁定的起始位置。 SEEK_CUR 以目前文件读写位置为锁定的起始位置 SEEK_END 以文件结尾为锁定的起始位置。 返回值 成功则返回0,若有错误则返回-1,错误原因存于errno. 该函数可以改变已打开的文件的性质。 #include int fcntl(int fields, int cmd, .../* int arg */); //若成功则依赖于cmd,若出错则返回-1 第三个参数总是一个整数,与上面所示函数原型中的注释部分相对应。但是在作为记录锁用时,第三个参数则是指向一个结构的指针。 fcntl函数有5种功能: 1.复制一个现有的描述符(cmd=F_DUPFD). 2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL). 4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN). 5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW). 例子:打开“/home/hubin”下的一个“file”文件,打开后对其加上强制性的写入锁,然后释放写入锁。程序源代码见附件中的lock_set.c.编译成功后,打开两个终端,都执行lock_set,程序结果如下: 终端1: 加上写入锁的是:7800 释放强制性锁:7800 终端2: 加上写入锁的是:7800 加上写入锁的是:7803 释放强制性锁:7803 由程序运行结果可知,此程序在终端1中运行后的第一个进程,先打开或创建文件"file",接着给“file”加上写入锁,锁定文件,并打印输出加锁信息和给文件加锁进程的进程号,这儿加的是“写入锁”,这台机器上第一个给文件加锁进程 的进程号是“7800”,最后等待用户按任意键后解除锁定,并打印输出解锁信息和给文件解锁进程的进程号。如果在第一个进程解除锁定前,此程序在另一个终端中在运行,即运行第二个进程,此时文件已经被第一进程上锁,那么第二个进程无法上锁,只能先打印输出文件已经上锁和第一个进程的进程号的信息。直到前一个进程解锁后,后一个进程才能上锁,解锁。 4.5 文件的定位 文件中有一个位置指针,指向当前读写的位置。如果顺序读写一个文件,每次读写一个字符,则读写完一个字符后,该位置指针自动移动指向下一个字符位置。当需要改变文 件顺序读写的次序时,根据需要随时指定文件读写的位置,也就是能实现文件的随机读写操作,这种功能是由 C 语言提供的文件定位函数来实现的。 文件的定位函数有lseek,fseek,rewind,ftell等函数。其中lseek是不带缓存的文件操作,其他的是流文件操作。 4.5.1.lseek函数 所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。使用 lseek 函数可以改变文件的 cfo 。函数说明: 头文件: #include <unistd.h> 函数原型:off_t lseek(int filedes, off_t offset, int whence); 返回值:新的偏移量(成功),-1(失败) 参数 offset 的含义取决于参数 whence: 1. 如果 whence 是 SEEK_SET,文件偏移量将被设置为 offset。 2. 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。 3. 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。 SEEK_SET、SEEK_CUR 和 SEEK_END 是 System V 引入的,在这之前使用的是 0、1 和 2。 lseek 的以下用法返回当前的偏移量: off_t currpos; currpos = lseek(fd, 0, SEEK_CUR); 这个技巧也可用于判断我们是否可以改变某个文件的偏移量。如果参数 fd(文件描述符)指定的是 pipe(管道)、FIFO 或者 socket,lseek 返回 -1 并且置 errno 为 ESPIPE。对于普通文件(regular file),cfo 是一个非负整数。但对于特殊设备,cfo 有可能是负数。因此,我们不能简单地测试 lseek 的返回值是否小于 0 来判断 lseek 成功与否,而应该测试 lseek 的返回值是否等于 -1 来判断 lseek 成功与否。lseek 仅将 cfo 保存于内核中,不会导致任何 I/O 操作。这个 cfo 将被用于之后的读写操作。如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。例程: #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> char buf1[] = "abcdefghij"; char buf2[] = "ABCDEFGHIJ"; int main(void) { int fd, size; if ((fd = creat("file.hole", S_IRUSR|S_IWUSR)) < 0) { printf("creat error\n"); return -1; } size = sizeof(buf1); if (write(fd, buf1, size) != size) { printf("buf1 write error\n"); return -1; } /* offset now = 10 */ if (lseek(fd, 16384, SEEK_SET) == -1) { printf("lseek error\n"); return -1; } /* offset now = 16384 */ size = sizeof(buf2); if (write(fd, buf2, size) != size) { printf("buf2 write error\n"); return -1; } /* offset now = 16394 */ return 0; } 4.5.2 定位读写指针函数fseek ( ) 该函数的功能是将文件的读写指针从某个位置移到指定的位置,该函数将为C语言对文件的随机读写提供了方法。该函数调用格式如下: fseek(fp, f<偏移量),(起始位置)) 其中,fseek是该函数的函数名;fp是指向被操作文件的文件指针;<偏移量)是表示移动当前读写指针的距离量,该参数的类型为long int型;<起始位置)是偏移量的相对位置。例如,起始位置为文件头,偏移量为50,则表示将读写指针移到相对文件头距离为50个字节的位置。起始 位置的设置方法有三; 0表示相对于文件头 1表示相对于文件的当前位置 2表示祖对于文件尾 实际中,常用宏定义来替代起始位置,规定如下: SEEK_SE'C表示文件头 SEEK_CUR表示当前位置 SEEK_END表示文件尾 例如: fseek(fp,200L,0);将读写指针移到离文件头20.个字节处。 fseek(fp, 80L,1);将读写指针移到离当前位置80个字节处。 fseek (fp , -50L,0);将读写指针移到从文件尾向后退50个字节处。 该函数一般用于二进制文件。如果用于文本文件要发生字符转换,计算位置时会发生误差。 4.5.3 归位读写指针函数rewind () 该函数的功能是将某个文件的读写指针归位于文件头。该函数的调用格式如下: rewind(fp)其中,rewind是该函数的函数名,fp是被操作文件的文件指针。使用该函数后,会使被操作文件的读写指针指向文件头。该函数的功能与下列函数功能相同。 (seek (fp,OL.0); 4.5.4 返回读写指针函数ftell ( ) 该函数的功能是返回指定文件当前读写指针的位置,该位置是用相对于文件头所相隔的字节数来表示。例如,该函数返回某个文件的当前读写指针的位置是100字节,即表示当前读写指针在离文件头有100个字节处。该函数调用格式如下:ftell(fp)它返回一个表示字节数的long int型数值。 例程:建立一个数据文件,随机读取其中的某个数据。使用fprintf( )函数建立一个数据文件xy. dat ,然后,指定从某个数据起连续读出若干个数据,最后,再读出这组数据的起始数据。程序内容如下: #inciude main ( ) { int i,x,y; FLLE * fp; fp=fopen("xy. dat" ,"wb十rb"); for (i=O;i<20;i++) fprintf (fp,,%5d",i+1); printf("\nlnput x:"). scanf(^%d"&x), for(i=o;i<5:i十十) { fseek(fp, (long) (5*(x一l+i),0); fscanf (fp ,"%d",&y); printf("%d\t",y). } fseek(fp,(Iong)(5*x一5),0): fscan((fp,"%d",&c ), rewind(fp); printf("\n%d\n",&x); fclose(fp); 4.6 特殊文件的操作linux中,除了普通文件外,还有积累重要的特殊文件。特殊文件的操作和普通文件操作类似,最常用的主要有目录文件和链接文件。 4.6.1 目录文件的操作 对目录文件的操作可以使用mkdir函数,opendir函数,closedir函数,readdir函数和scandir函数。例程:读取系统目录文件"/etc/rc.d"中的所有目录结构。 #include #include #include #include #include #include int main() { DIR *dir; struct dirent *ptr; int i; dir=opendir("/etc/rc.d"); while((ptr=readdir(dir))!=NULL) printf("目录:%s\n",ptr->d_name); closedir(dir); } opendir函数说明: 所需头文件:#include #include 函数功能:打开目录句柄 函数原型:DIR *opendir(const char *name); 函数传入值:打开参数name指定的目录,并返回DIR *形态的目录流,对目录的读取和搜索都要使用此返回值 函数返回值:成功则返回DIR *形态的目录流,打开失败则返回NULL。 readdir函数说明: 所需头文件:#include #include #include 函数功能:读取目录文件 函数原型:struct dirent *readdir(DIR *dir); 函数传入值:返回参数dir目录流的下个目录进入点 dirent结构定义如下: struct dirent { ino_t d_ino; ff_t d_off; signed short int d_reclen; unsigned char d_type; har d_name[256]; } d_ino 此目录进入点的inode d_off 目录文件开头至此目录进入点的位移 d_reclen_name的长度,不包含NULL字符 d_type d_name所指的文件类型 s_name 文件名 函数返回值:成功则返回下个目录进入点,有错误发生或读取到目录文件尾则返回NULL closedir函数说明: 所需头文件:#include #include 函数功能:关闭目录文件 函数原型:int closedir(DIR *dir) 函数传入值:参数dir:目录流 函数返回值:关闭成功则返回0,失败返回-1。 4.6.2 链接文件的操作 Linux系统中的链接文件,有点类似于Windows系统中的“快捷方式”,但并不完全一样,Linux系统中的链接方式有两种:软链接和硬链接。 1.软链接文件 软链接又叫符号链接,这个文件包含了另一个文件的路径名。可以是任意文件或目录,可以链接不同文件系统的文件。链接文件甚至可以链接不存在的文件,这就产生了一般称之为“断链”的问题,链接文件甚至可以循环链接自己,类似于编程语言中的递归。例程:设计一个程序,要求为“/etc/passwd”文件建立软链接“link”,并查看此链接文件和“/etc/passwd”文件。 #include int main() { symlink("/etc/passwd","link"); system("ls link -l"); system("ls /etc/passwd -l"); return 0; } 程序运行结果如下: hubin@hubin-desktop:~/linux_c$ ./slink lrwxrwxrwx 1 hubin hubin 11 2009-08-03 08:29 link -> /etc/passwd -rw-r--r-- 1 root root 1487 2009-04-23 17:57 /etc/passwd 从程序运行结果看,在新创建的“link”文件代表权限的10个字符中,第一位是"l",而且最后显“->/etc/passwd”,表明链接目标是"/etc/passwd"文件。 symlink函数说明: 所需头文件:#include 函数功能:建立软链接 函数原型:int symlink(const char *oldpath,const char *newpath); 函数传入值:参数newpath:链接名称 参数oldpath:已存在文件路径或文件名 函数返回值:成功返回0,失败返回-1 2. 硬链接文件 对硬链接文件进行读写和删除操作时候,结果和软链接相同。但如果删除硬链接文件的源文件,硬链接文件依然存在,而且保留了原有的内容,只是,系统就“忘记了”它曾经是硬链接文件,而把它当成一个普通文件。硬链接文件有两个限制:1).不允许给目录创建硬链接;2).只有在同一文件系统中的文件之间才能创建链接。 例程:设计一个程序,为“/etc/passwd”文件建立硬链接文件“hpasswd”,并查看此链接文件和“/etc/passwd”文件。 #include int main() { link("/etc/passwd","hardlink"); system("ls hardlink -l"); system("ls /etc/passwd -l"); return 0; } 程序运行结果: -rw-r--r-- 1 hubin hubin 0 2009-08-03 08:49 hardlink -rw-r--r-- 1 root root 1487 2009-04-23 17:57 /etc/passwd 从程序结果看,两个文件表面上是一样的,除了文件名,新建立的文件跟原文件显示的属性一模一样,而且第二项是“1”,因此,确实建立了一个硬链接文件。 link函数说明: 所需头文件:#include 函数功能:建立硬链接 函数原型:int link(const char *oldpath,const char *newpath); 函数传入值:参数newpath:链接名称 参数oldpath:已存在文件路径或文件名 函数返回值:成功返回0,失败返回-1 备注:如果参数newpath指定的名称为一不存在的文件,则不会建立链接。 5.部分函数说明 chmod -- 改变文件模式 说明bool chmod ( string filename, int mode)尝试将 filename 所指定文件的模式改成 mode 所给定的。注意 mode 不会被自动当成八进制数值,而且也不能用字符串(例如 "g+w")。要确保正确操作,需要给 mode 前面加上 0: |