底层文件访问
文件描述符:
0:标准输入
1:标准输出
2:标准错误
write系统调用
把缓冲区buf的前nbytes个字节写入与文件描述符fildes关联的文件中。返回实际写入的字节数。返回0表示未写入,返回-1表示错误。
#include <unistd.h> size_t write(int fildes,const void *buf,size_t nbytes)
#include<unistd.h> #include<stdlib.h> int main(){ if((write(1,"Here is some data ",18))!=18) write(2,"A write error has occured on file desciptor 1 ",46); exit(0); }
输出:
wuchao@:~/linux_program/CH03$ ./write Here is some data
read系统调用
从与文件描述符fileds相关联的文件里读入nbytes个字节的数据,并放在buffer中,返回实际读入的字节数。返回0表示未读入,-1表示错误。
#include <unistd.h> size_t read(int fildes, void *buf,size_t nbytes)
#include<unistd.h> #include<stdlib.h> int main(){ char buffer[128]; int nread; nread = read(0,buffer,128); if(nread == -1) write(2,"A read error has occurred ",26); if((write(1,buffer,nread)) != nread) write(2,"A write error has occurred ",27); exit(0); }
输出:
wuchao@:~/linux_program/CH03$ echo hello world | ./read hello world
open系统的调用
为了创建一个新的文件描述符,需要使用open
#inlcude <fcntl.h> int open(const char *path,int oflags); int open(const char *path,int oflags,mode_t mode);
open建立了一条到文件或设备的访问路径。如果调用成功,则返回一个可以被read,write和其他系统调用的文件描述符。即使两个程序打开一个文件,也会得到两个不同的文件描述符。如果它们都对文件写操作,将会各写各的,不会交织,最后数据彼此覆盖,可以使用文件锁解决这种冲突。
path指定文件名或者设备名,oflags指定打开文件采取的动作。
oflags参数通过必须文件访问模式域其他可选访问模式结合来指定(多个模式之间用“按位或”结合)。
以下是oflags的必须访问模式:
模式 | 说明 |
O_RDONLY | 以只读方式打开 |
O_WRONLY | 以只写方式打开 |
O_RDWR | 以读写方式打开 |
以下是可选访问模式:
模式 | 说明 |
O_APPEND |
把写入数据追加在文件末尾 |
O_TRUNC | 把文件长度设为0,丢弃已有内容 |
O_CREAT | 如果需要,就按参数mode中给出的访问模式创建文件 |
O_EXCL | 与O_CREAT一起使用,确保调用者创建出文件。Open调用是原子操作,只执行一个函数调用。使用这个模式可以防止两个程序同时创建同一个文件。如果文件存在,open调用将失败 |
open调用成功时返回一个新的文件描述符,失败返回-1并设置全局变量errno。
当使用O_CREAT标志来使用open创建文件时,必须设置mode参数,该参数是以下几个标志按位或得到:
mode标志 | 说明 |
S_IRUSR | 读权限,文件属user |
S_IWUSR | 写权限,文件属user |
S_IXUSR | 执行权限,文件属user |
S_IRGRP | 读权限,文件属group |
S_IWGRP | 写权限,文件属group |
S_IXGRP | 执行权限,文件属group |
S_IROTH | 读权限,文件属other |
S_IWOTH | 写权限,文件属other |
S_IXOTH | 执行权限,文件属other |
如下例子:
open("myfile",O_CREAT,S_IRUSR | S_IXOTH);
创建一个名为myfile的文件,user拥有读权限,other拥有执行权限
wuchao@:~/linux_program/CH03$ cat open.c #include<unistd.h> #include<stdlib.h> #include<fcntl.h> int main(){ open("myfile",O_CREAT,S_IRUSR | S_IXOTH); exit(0); } wuchao@:~/linux_program/CH03$ ./open wuchao@:~/linux_program/CH03$ ls -l myfile -r-------x 1 wuchao wuchao 0 10月 16 10:19 myfile
注意:mode值将与umask的反值做AND操作,比如上面设置了S_IXOTH,umask的值为001,则文件不会有其他用户的执行权限。
close系统调用
终止文件描述符fildes与其对应文件的关联。文件描述符被释放。调用成功返回0,错误返回-1。
#include<unistd.h> close(int fildes);
ioctl系统调用
提供了用于控制设备及其描述符行为和配置底层服务的接口。
#include<unistd.h> int ioctl(int fildes ,int cmd,...)
实验:文件的复制
将已有的文件file.in复制到新的file.out中
一个字符一个字符的复制:
#include<unistd.h> #include<sys/stat.h> #include<fcntl.h> #include<stdlib.h> int main(){ char c; int in,out; int nread; in = open("file.in",O_RDONLY); out = open("file.out",O_CREAT|O_WRONLY,S_IRUSR|S_IWUSR); while((nread = read(in,&c,1))==1){ write(out,&c,1); } }
按数据块复制:
#include<unistd.h> #include<sys/stat.h> #include<fcntl.h> #include<stdlib.h> int main(){ char block[20]; int in,out; int nread; in = open("file.in",O_RDONLY); out = open("file.out",O_CREAT|O_WRONLY,S_IRUSR|S_IWUSR); while((nread = read(in,block,sizeof(block)))>0){ write(out,block,nread); } exit(0); }
其他与文件管理有关的系统调用
lseek
设置文件的写一个读写位置
#include<unistd.h> #include<sys/types.h> off_t lseek(int fildes , off_t offset , int whence)
offset用来指定位置,whence定义偏移值的用法。
whence值如下:
SEEK_SET:offset是一个绝对位置
SEEK_CUR:offset是相当于当前位置的相对位置
SEEK_END:相对于文件末尾的相对位置
lseek函数返回文件头到被设置处的字节偏移值,失败返回-1。
off_t定义在sys/types.h中
fstat,stat,lstat
#include<unistd.h> #include<sys/types.h> #include<sys/stat.h> int fstat(int fildes , struct stat *buf); int stat(const char *path , struct stat *buf); int lstat(const char *path , struct stat *buf);
stat和lstat通过文件名查询文件的状态信息。当文件是符号链接时,lstat返回符号链接本身的信息,stat返回该链接文件指向的信息。
取得的文件状态存放在buf指针指向的struct stat结构提中, struct stat的定义如下:
struct stat { dev_t st_dev; /* ID of device containing file -文件所在设备的ID*/ ino_t st_ino; /* inode number -inode节点号*/ mode_t st_mode; /* 文件的类型和存取的权限*/ nlink_t st_nlink; /* number of hard links -链向此文件的连接数(硬连接)*/ uid_t st_uid; /* user ID of owner -user id*/ gid_t st_gid; /* group ID of owner - group id*/ dev_t st_rdev; /* device ID (if special file) -设备号,针对设备文件*/ off_t st_size; /* total size, in bytes -文件大小,字节为单位*/ blksize_t st_blksize; /* blocksize for filesystem I/O -系统块的大小*/ blkcnt_t st_blocks; /* number of blocks allocated -文件所占块数*/ time_t st_atime; /* time of last access -最近存取时间*/ time_t st_mtime; /* time of last modification -最近修改时间*/ time_t st_ctime; /* time of last status change - */ };
st_mode这个变量用来判断文件类型。
st_mode是用特征位来表示文件类型的,特征位的定义如下:
S_IFMT 0170000 文件类型的位遮罩 S_IFSOCK 0140000 socket S_IFLNK 0120000 符号链接(symbolic link) S_IFREG 0100000 一般文件 S_IFBLK 0060000 区块装置(block device) S_IFDIR 0040000 目录 S_IFCHR 0020000 字符装置(character device) S_IFIFO 0010000 先进先出(fifo) S_ISUID 0004000 文件的(set user-id on execution)位 S_ISGID 0002000 文件的(set group-id on execution)位 S_ISVTX 0001000 文件的sticky位 S_IRWXU 00700 文件所有者的遮罩值(即所有权限值) S_IRUSR 00400 文件所有者具可读取权限 S_IWUSR 00200 文件所有者具可写入权限 S_IXUSR 00100 文件所有者具可执行权限 S_IRWXG 00070 用户组的遮罩值(即所有权限值) S_IRGRP 00040 用户组具可读取权限 S_IWGRP 00020 用户组具可写入权限 S_IXGRP 00010 用户组具可执行权限 S_IRWXO 00007 其他用户的遮罩值(即所有权限值) S_IROTH 00004 其他用户具可读取权限 S_IWOTH 00002 其他用户具可写入权限 S_IXOTH 00001 其他用户具可执行权限
判断文件类型时,用对文件的st_mode的值与上面给出的值相与,再比较。
注意:如果要查看文件的权限,可以使用st_mode&S_IRWXU再与S_IXUSR等标志位比较,比如“st_mode&S_IRWXU == S_IXUSR”表示文件只有user的执行权限。
比如:
#include <sys/stat.h> #include <unistd.h> #include <stdio.h> int main() { int abc; struct stat buf; stat("/home", &buf); abc = buf.st_mode & S_IFDIR;//与对应的标志位相与 if(abc == S_IFDIR) //结果与标志位比较 printf("It's a directory. "); return 0; }
其实还有一个简单的方法,文件类型在POSIX中定义了检查这些类型的宏定义:
S_ISLINGK(st_mode) 判断是否位符号链接
S_ISREG(st_mode) 是否为一般文件
S_ISDIR(st_mode) 是否为目录
S_ISCHR(st_mode) 是否位字符装置文件
S_ISBLK(st_mode) 是否先进先出
S_ISSOCK(st_mode) 是否为socket
接下来举例,判断文件属性和权限:
有如下文件
-rw-rw-r-- 1 wuchao wuchao 123 10月 16 10:27 open.c
编写代码判断文件类型和user权限
#include<sys/stat.h> #include<unistd.h> #include<stdio.h> int main(){ int abc; struct stat statf; stat("open.c",&statf); int vDir = statf.st_mode & S_IFDIR; int vFile = statf.st_mode & S_IFREG; int vRight = statf.st_mode & S_IRWXU; if(vDir == S_IFDIR) printf("It is a directory "); if(vFile == S_IFREG) printf("It is a common file "); if(vRight & S_IXUSR) printf("this file has excu right by user "); if(vRight & S_IWUSR) printf("this file has write right by user "); if(vRight & S_IRUSR) printf("this file has read right by user "); }
打印输出
wuchao@:~/linux_program/CH03$ ./stat It is a common file this file has write right by user this file has read right by user
标准I/O库
头文件stdio.h,与底层文件描述符使用方式一样。在标准I/O库中,与底层文件描述符对应的是流。
fopen函数
相当于底层的open。它主要用于文件和终端的输入输出。
#include<stdio.h> FILE *fopen(const char *filename , const char *mode);
mode参数指定文件的打开方式:
mode选项 | 说明 |
“r”或"rb" | 以只读方式打开 |
"w"或“wb” | 以写方式打开,并将文件长度截断为零 |
“a”或“ab” | 以写方式打开,新内容追加在文件尾部 |
“r+”或“rb+”或"r+b" | 以更新方式打开(读和写) |
"w+"或"wb+"或“w+b” | 以更新方式打开,并将文件长度截断为零 |
“a+”或“ab+”或“a+b” | 以更新方式打开,新内容追加在文件尾部 |
b表示是二进制文件而不是文本文件。
fopen成功时返回非空File指针,失败返回NULL值。
fread函数
#include<stdio.h> size_t fread(void *ptr , size_t size , size_t nitems , FILE *stream);
从一个文件流stream里读取数据到ptr指向的缓冲区,size指定每个数据记录的长度,nitems给出要传输的记录个数。返回成功读到缓冲区的记录个数。当到达文件尾时,返回值<=0。
fwrite函数
#include<stdio.h> size_t fwrite(void *ptr , size_t size , size_t nitems , FILE *stream);
fclose函数
int fclose(FILE *stream);
关闭指定的文件流。
fflush函数
将文件流里未写出的数据立刻写出。fclose隐含调用了fclose函数。
fseek函数
#include<stdio.h> int fseek(FILE *stream , long int offset , int whence);
在文件流里为下一次读写操作指定位置。
offset用来指定位置,whence定义偏移值的用法。
whence值如下:
SEEK_SET:offset是一个绝对位置
SEEK_CUR:offset是相当于当前位置的相对位置
SEEK_END:相对于文件末尾的相对位置
返回值0表示成功,-1表示失败。
fgetc,getc,getchar函数
#include<stdio.h> int fgetc(FILE *stream); int getc(FILE *stream); int getchar();
fgetc从文件流取出下一个字节并作为字符返回。达到文件末尾或者出现错误时返回EOF。
fgetc,getc两个都是用来从stream中取得一个字符的,区别在于调用getc函数时所用的参数stream不能是有副作用的表达式(如,不能影响变量),而fgetc函数则可以,也就是说,getc可以被当作宏来调用,而fgetc只能作为函数来调用。
一般来说,调用宏比调用函数耗费的时间少。
fputc,putc,putchar函数
#include<stdio.h> int fputc(int c,FILE *stream); int putc(int c,FILE *stream); int putchar(int c);
fputc把一个字节写到一个输出流文件中,返回写入的值,如果失败,返回EOF。
putchar相当于putc(c,stdout),把单个字符写到标准输出。
putchar和getchar都是把字符当作int类型而不是char来使用。
fgets,gets函数
#include<stdio.h> char *fgets(char *s,int n,FILE *stream); char *gets(char *s);
fgets从文件流读入字符串并写到s指向的字符串里,直到遇到以下情况:已经传输了n-1个字符,或者到达文件尾。它会把遇到的换行符也传递到接收参数字符串里,再加上一个表示结尾的空字节 ,当成功完成时,返回指向字符串s的指针。如果到达文件末尾,返回空指针并设置EOF标识。如果出现错误,fgets返回空指针并设置errno。
gets类似于fgets,但会丢弃换行符并在尾部加上null字节。
格式化输入输出
printf,fprintf,sprintf函数
#include<stdio.h> int printf(const char *format,...); int sprintf(char *s,const char *format,...); int fprintf(FILE *stream,const char *format,...);
printf将自己的输出送到标准输出。
fprintf将自己的输出送到指定的文件流。
sprintf将自己的输出和一个结尾空字符串写到作为参数传递过来的字符串s里,这个字符串必须能容纳所有输出的数据。
转换控制符:
%d:十进制整数
%o:八进制整数
%c:输出一个字符
%s:输出一个字符串
%f:输出一个浮点数
%e:以科学计数法输出双精度浮点数
%g:通用格式输出双精度浮点数
scanf,fscanf,sscanf函数
#include<stdio.h> int scanf(const char *format,...); int fscanf(FILE *stream , const char *format,...); int sscanf(const *s,const char *format,...);
从文件流读取数据,并把数据放到指针参数指向的地址中。
其他流函数
fgetpos:获取文件流当前读写位置
fsetpos:设置文件流当前读写位置
ftell:返回文件流当前读位置的偏移值
rewind:重置文件流的读写位置
freopen:重新使用一个文件流
setvbuf:设置文件流的缓冲机制
remove:相当于unlink,如果是目录,则相当于rmdir
实验:文件的复制
#include<stdio.h> #include<stdlib.h> int main(){ int c; FILE *in,*out; in = fopen("test.file","r"); out = fopen("test2.file","w"); while((c=fgetc(in)) != EOF) fputc(c,out); fclose(in); fclose(out); exit(0); }
文件流错误
错误由外部变量errno指出。
也可以通过检查文件流状态来确定错误:
#include<stdio.h> int ferror(FILE *stream); int feof(FILE *stream); void clearerr(FILE *stream);
ferror测试文件流的错误标识,若有错误,返回非零值,否则返回0。
feof测试文件流的文件尾标识,到文件尾则返回非0。
clearerr清除文件流的文件尾表示和错误标识。
文件和目录的维护
chmod
#include<sys/stat.h> int chmod(const char *path,mode_t mode);
参数mode与open系统调用的一样,对所有访问权限通过OR操作。
chown
#include<sys/types.h> #include<unistd.h> int chown(const char *path,uid_t owner,gid_t group);
unlink,link,symlink
#include<unistd.h> int unlink(const char *path); int link(const char *path1,const char *path2); int symlink(const char *path1,const char *path2);
link函数建立硬链接,unlink删除文件的硬链接数,当链接数为零,则文件被删除。即删除path路径的文件。如果文件是符号链接,unlink删除该符号链接(只是删除快捷方式),symlink是符号链接。
mkdir,rmdir
#include<sys/types.h> #include<sys/stat.h> int mkdir(const char *path,mode_t mode);
#inlcude<unistd.h> int rmdir(const char *path);
注:mode必须符号umask的设置,rmdir只能删除空目录
chdir,getcwd
#include<unistd.h> int chdir(const char *path);
切换目录,成功返回0,失败返回-1。
#include<unistd.h> char *getcwd(char *buf,size_t size);
将当前目录名字放在buf里,如果目录长度超过size,返回NULL。如果成功,返回指针buf。
扫描目录
DIR,dirent两种结构体
struct __dirstream { void *__fd; char *__data; int __entry_data; char *__ptr; int __entry_ptr; size_t __allocation; size_t __size; __libc_lock_define (, __lock) }; typedef struct __dirstream DIR;
struct dirent { long d_ino; /* inode number 索引节点号 */ off_t d_off; /* offset to this dirent 在目录文件中的偏移 */ unsigned short d_reclen; /* length of this d_name 文件名长 */ unsigned char d_type; /* the type of d_name 文件类型 */ char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */ }
opendir
include<sys/types.h> #include<dirent.h> DIR *opendir(const char *name);
readdir
#include<sys/types.h> #include<dirent.h> struct dirent *readdir(DIR *dirp);
返回一个指针,指向的结构里保存着dirp中下一个目录项的信息。后续的readdir调用将返回后续的目录项。如果发生错误或到目录尾,返回NULL。
telldir
#include<sys/types.h> #include<dirent.h> long int telldir(DIR *dirp);
返回目录流的当前位置
seekdir
#include<sys/types.h> #include<dirent.h> void seekdir(DIR *sirp,long int loc);
设置目录流dirp的位置。
closedir
#include<sys/types.h> #include<dirent.h> int close(DIR *dirp);
关闭目录流。
实验:扫描并打印目录
/* ============================================================================ Name : project_01.c Author : wuchao Version : Copyright : njupt Description : Hello World in C, Ansi-style ============================================================================ */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> void printDir(char *dir,int depth){ struct dirent *entry; struct stat statbuf; DIR *dp; //检查该目录能否打开 if((dp=opendir(dir))==NULL){ fprintf(stderr,"cannot open directory:%s ",dir); return; } //切换到该目录 chdir(dir); //循环读该目录的内容,读到目录尾部时返回NULL while((entry = readdir(dp))!=NULL){ lstat(entry->d_name,&statbuf);//读取文件或目录的详细信息 //判断是文件还是目录 if(S_ISDIR(statbuf.st_mode)){ //不打印“.”和“..” if(strcmp(".",entry->d_name)==0||strcmp("..",entry->d_name)==0){ continue; } printf("%*s%s/ ",depth,"",entry->d_name); printDir(entry->d_name,depth+4); }else{ //如果是目录,直接打印,然后继续读该目录的下一个内容 printf("%*s%s ",depth,"",entry->d_name); } } //读完该目录后,返回上一层目录 chdir(".."); //关闭目录流 closedir(dp); } int main(void) { char *path = "/home/wuchao/caffe/docs"; printf("Directory scan of %s:/n",path); printDir(path,0); return EXIT_SUCCESS; }
错误处理
系统调用和函数都会产生各种错误而失败,此时会设置外部变量errno的值。errno的取值定义在errno.h文件,如下:
EPERM:操作不允许
ENOENT:文件或目录不存在
EIO:I/O错误
EBUSY:设备或资源忙
EEXIST:文件存在
EINVAL:无效参数
EMFILE:打开的文件过多
EISDIR:是一个目录
ENODEV:设备不存在
ENOTDIR:不是一个目录
以下两个函数可以用来报告错误
streeror函数
#include<string.h> char *streeror(int errnum);
perror函数
#include<stdio.h> void perror(const char *s);
将字符串s和错误信息以”:“号连接,并输出到标准输出中。