第三章 文件操作

底层文件访问

文件描述符:

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\n",18))!=18)
      write(2,"A write error has occured on file desciptor 1 \n",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\n",26);

    if((write(1,buffer,nread)) != nread)
      write(2,"A write error has occurred\n",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.\n");  
   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 \n");
    if(vFile == S_IFREG)
                printf("It is a common file \n");
    if(vRight & S_IXUSR)
        printf("this file has excu right by user\n");
    if(vRight & S_IWUSR)
                printf("this file has write right by user\n");
    if(vRight & S_IRUSR)
                printf("this file has read right by user\n");
}

打印输出

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个字符,或者到达文件尾。它会把遇到的换行符也传递到接收参数字符串里,再加上一个表示结尾的空字节\0,当成功完成时,返回指向字符串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 \n",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/\n",depth,"",entry->d_name);
            printDir(entry->d_name,depth+4);
        }else{
            //如果是目录,直接打印,然后继续读该目录的下一个内容
            printf("%*s%s\n",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和错误信息以”:“号连接,并输出到标准输出中。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值