Linux文件操作

2016-03-01  本文已影响0人  JamesPeng

文件操作

(Linux文件操作)) [文件|目录]

Linux文件操作:为了对文件和目录进程处理,你需要用到系统调用(这是 Unix和Linux中与WindowsAPI对应的概念),但系统中同时还存在一套库函数--标准IO(stdio),可以更有效地进行文件处理。

3.1:Linux 文件结构

在Linux中,一切(或者几乎一切)都是文件,这就意味着,通常程序完全可以像使用文件那样使用磁盘文件、串行口、打印机、和其他设备。

3.1.1 :目录

文件,除了本身包含内容以外,他还会有一个名字和一些属性,即“管理信息”,它包括文件的创建/修改日期和它的访问权限。这些属性被保存在文件的inode(节点)中,他是文件系统中的一个特殊的数据块,他同时还包含文件的长度和文件在磁盘上的存放位置。系统使用的是文件的inode编号,目录结构为文件命名仅仅是为了便于人们使用。

目录是用于保存其他文件的节点号和名字的文件。目录文件中的每个数据项都指向某个文件节点链接,删除文件名就等于删除与之对应的连接(文件的节点号可以通过ln-i命令查看)。你可以通过使用ln命令在不同的目录中创建指向同一个文件的链接。

删除一个文件时,实质上是删除了该文件对应的目录项,同时指向该文件的链接数减1.

3.1.2 :文件和设备

UNIX和Linux中比较重要的设备文件有3个:/dev/console 、/dev/tty和/dev/null

3.2 系统调用和设备驱动程序

操作系统的核心部分,即内核,是一组设备驱动程序。它们是一组对系统硬件进行控制的底层接口。

下面是用于访问设备驱动程序的底层函数(系统调用)。

此外,每个驱动程序都定义了它自己的一组ioctl命令

3.3 库函数

用户空间与内核空间交互图

3.4 底层文件访问

每个运行中的程序称为进程(process),它有一些与之关联的文件描述符。

3.4.1 write系统调用

系统调用write的作用是吧缓冲区的buf的前nbytes个字节写入与文件描述符fileds关联的文件中。它返回实际写入的字节数。
write系统调用原型:


#include <unistd.h>
size_t write(int filds,const void *buf,size_t nbytes);

3.4.2 read系统调用

系统调用read的作用是:从与文件描述符fildes相关的文件里读入nbytes个字节的数据,并把它们放到数据区buf中。它返回实际读入的字节数,这可能会小于请求的字节数。


#include <unistd.h>
size_t read(int fildes,void *buf,size_t nbytes);
3.4.3 open系统调用

为了创建一个新的描述符,你需要使用系统调用open

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int open(const char *path,int oflags);
int open(const char *path,int oflags,mode_t mode);

严格来说,在遵循POSIX规范系统上,使用open系统调用并不需要包括头文件sys/types.h和sys/stat.h,但在某些unix系统上,它们可能是必不可少的。

open 建立一条文件和设备的访问路径。成功则返回一个唯一描述符,供read 和 write调用,文件描述符石不能进行进程共享。

oflags : 模式

模式 说明
O_RDONLY 以只读方式打开
O_WRONLY 以只写的方式打开
O_RDWR 以读写的方式打开

open调用还可以在oflags参数中包括下列可选模式的组合(按位或)

POSIX 规范还标准化了一个creat调用,但它并不常用。

3.4.4 访问权限的初始值

当你使用带有O_CREAT 标志的open 调用来创建文件时,你必须使用有3个参数格式的open调用。第三个参数mode 时几个标志按位或后得到的,这些标志在头文件sys/stat.h中定义。

请看下面的例子:


    open ("myfile",OCREAT,S_IRUSR|S_IXOTH);
    
1.umask

umask 是一个系统变量,他的作用是,当文件被创建时,为文件的访问权限设定一个掩码。执行umask命令可以修改这个变量的值。

2.close 系统调用

你可以使用close调用终止文件描述符fildes与其对应文件之间的关联。文件描述符被释放并能够重新使用。close 调用成功时返回0,出错时返回-1

注意,检查close 调用的返回结果非常重要。有的文件系统,特别是网络文件系统可能不会再关闭文件之前报告文件写操作中的错误,这是因为在执行写操作时,数据可能未被确认写入。

3.ioctl系统调用

ioctl 调用有点像是个大杂烩。它提供了一个用于控制设备及其描述符行为和配置底层服务接口。

#include unistd.h
int ioctl(int fildes,int cmd,...)

--》程序块一


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>


define IN_FILE "in_file.txt"
define OUT_FILE "out_file.txt"

int main(void) {

    char buf[1024];
    char c;
    int in ,out;
    int nread;
    in = open(IN_FILE,O_RDONLY);
    out = open(OUT_FILE,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
    while(nread = read(in,buf,sizeof(buf)) > 0)
        write(out,buf,1);


    return EXIT_SUCCESS;
}


3.4.5 其他与文件管理有关的系统调用
1:lseek 系统调用

lseek 系统调用对文件描述符fildes的读写指针进行设置。也就是说,你可以用它来设置文件的下一个读写位置。读写指针既可被设置为文件中的某个绝对位置,也可以把它设置为相对于当前位置或文件尾的某个相对位置

#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fildes,off_t offset, int whence);

off_set参数用来指定位置,而whence参数定义该偏移植的用法。whence可以取值之一:

lseek 返回从文件头到文件指针被设置处的字节偏移值,失败时,返回1

2:fstat、stat和lstat系统调用

fstat 系统调用返回与打开文件描述符的相关的文件状态信息,该信息将会写到一个buf结构中,buf的地址一参数形式传递给fstat。

#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.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 结构成员图

0)

3:dup和dup2系统调用[可用于断点续传]

dup系统调用提供了一种复制文件描述符的方法,使我们能够通过两个或者更多个不同的描述符访问同一个文件。这可以用于在文件的不同位置对数据进行读写。dup系统调用复制文件描述符fileds,返回一个新的描述符。dup2系统调用则是通过明确指向目标描述符来吧一个文件描述符复制为另外一个。

#include <unistd.h>
int dup(int fildes);
int dup2(int fildes,int fildes2);

当你通过管道多个进程间进行通信时,这些调用也很有用。

3.5 标准I/O 库

标准I/O库(stdio)即其头文件stdio.h 为底层I/O系统调用提供了一个通用的接口。
在标准I/O库中,与底层文件描述符对应的是流(stream),它被实现为指向结构FILE的指针。

标准I/O库中的下列库函数:

3.5.1 fopen 函数

fopen 库函数类似于底层的open系统调用。它主要用于文件和终端的输入输出。
该函数原型如下:

#include <stdio.h>
FILE *fopen(const char *filename,const char *mode)

fopen 打开由filename参数指定的文件,并把它与一个文件流关联起来,mode参数指定文件的打开方式,它取下列字符串中的值。

字母b表示文件是一个二机制文件而不是文本文件。

fopen 在成功时返回一个非空的FILE *指针,失败时返回NULL值,NULL值在头文件stdio.h里定义。

3.5.2 fread 函数

fread 库函数用于从一个文件流里读取数据。数据从文件流stream读到ptr指向的数据缓冲区里。


#include <stdio.h>
size_t fread(
 void *ptr//
,size_t size//指定每个数据记录的长度
,size_t nitems// 给出要传输的记录个数
,FILE * stream
 );

对所有向缓冲区里写数据的标准I/O 函数来说,为数据分配空间和检查错误是程序员的责任。

3.5.3 fwrite 函数

fwrite库函数与fread相似接口。从指定的数据缓冲区里取出数据记录,并把它们写出输出流中。它的返回值是成功写入的记录个数。

#include <stdio.h>
size_t fwrite(const void *ptr,size_t size,size_t nitems,FILE *stream);

请注意,我们不推荐把fread 和fwrite用于结构化数据。部分原因在于用fwrite写的文件在不同的计算机体现结构之间可能不具备可移植性

3.5.4 fclose函数

fclose 库函数关闭指定的文件stream ,使所有尚未写出数据都写出。
因为stdio 库会对数据进行缓冲,所以使用fcolse是很重要的。

#include stdio.h
int fclose(FILE *stream);
3.5.5 fflush 函数

fflush 库函数的作用是把文件流离的所有未写出数据立刻写出。

注意,调用fclose函数隐含执行了i 次flush操作,所以你不必再调用fclose之前调用fflush

#include stdio.h
int fflush(FILE *stream);
3.5.6 fseek函数

fseek 函数是与lseek 系统调用对应的文件流函数。它在文件流里为下一次读写操作指定位置。
fseek函数 返回一个整数:0表示成功,-1表示失败并设置errno 指出错误。

#include stdio.h
int fseek(FILE *stream,long int offset,int whence)
3.5.7 fgetc 、getc 和 getchar函数

fgetc 函数从文件流里取出下个字节并把他作为一个字符返回,当他到达文件尾或出现错误时,它返回EOF。你必须通过ferror或 feof 来区分者两种情况。

#include stdio.h
int fgetc(FILE *stream)
int getc(FILE *stream)
int getchar();
3.5.8 fputc、putc 和 putchar函数

fputc 函数把一个字符写到一个输出文件流中。它返回写入的值,如果失败,则返回EOF。

#include stdio.h
int fputc(int c,FILE *stream)
int fputc(int c,FILE *stream)
int putchar(int c);
3.5.9 fgets 和 gets 函数

fgets函数从输入文件流stream里读取一个字符串。

#include stdio.h
char *fgets(char *s ,int n ,FILE *stream)
char *gets(char *s);

fget把读到字符写到s执行的字符串里,知道出现下面某种情况:遇到换行符,已经传输了n-1个字符或者是达到文件尾。
当成功完成时,fgets返回一个指向字符串s的指针。如果文件流已经到到达文件尾,fgets会设置这个文件EOF标识并返回一个空指针。如果出现读错误,fgets返回一个空指针并设置errno以指出错误类型。

3.6 格式化输入输出

3.6.1:printf、fprintf和sprintf函数

printf 系列函数能够对各种不同类型的参数进程格式编排和输出

#include  <stdio.h>
int printf(const char *format,...);
int sprintf(char *s ,const char *format,...);
int fprintf(FILE *stream,const char *format,...);

说明:

下面一些常用的转换控制符:

3.6.2:scanf 、fscanf 和 sscanf 函数
#include  stdio.h
int scanf(const char *format,...);
int fscanf(FILE *stream,const char *format);
int sscanf(const char *s,const char *format);

说明:

一般来说,对scanf 系列函数的评价并不高,这主要有下面3方面的原因。

3.6.3:其他流函数
3.6.4:文件流错误

为了表明错误,许多stdio苦函数会返回一个超出范围的值,比如空指针或EOF常熟。此时,错误由外部变量errno指出:

#include <errno.h>

extern int errno;

注意许多函数都可能改变errno的值,他的值只有在函数调用失败时才有意义,你必须在函数表明失败之后立即对其进行检查,你应该总是在使用它之前将他先复制到另一个变量中,因为像fprintf这样的输出函数本身就可能改变errno的值。

你也可以通过检查文件流的状态来确定是否发生了错误,或者是否达到了文件尾。

#include stdio.h

int ferror(FILE *stream)
int feof(FILE *stream)
void clearerr(FILE *stream)

ferror函数测试一个文件流的错误标识,如果改标识被设置就返回一个非零值,否则返回零

feof 函数测试一个文件流的文件尾标识被设置就返回非零值,否则返回零

clearerr 函数的作用时清除由stream指向的文件尾标识和错误标识。

3.6.5 文件流和文件描述符

每个文件流都和一个底层文件描述符相关联。

#include stdio.h
int fileno(FILE *stream)
FILE *fdopen(int fildes,const char *mode);

说明:

fileno 函数来确定文件流使用的是那个底层文件描述符

fdopen函数操作方式与fopen函数是一样的,只是前者参数不是一个文件名,而是一个底层文件描述符。

3.7文件和目录的维护

3.7.1 chmod 系统调用

你可以通过chmod 系统调用来改变文件或目录的访问权限。

该函数原型如下:


#include <sys/stat.h>

int chmod(const char *path,mode_t mode);

path参数指定的文件被修改为具有mode参数给出的访问权限。

参数mode的定义与open系统调用中的一样

3.7.2 chown 系统调用

“超级用户“可以使用chown 系统调用来改变一个文件的属主。

#include <sys/types.h>
#include <unistd.h>

int chown (const char *path,uid_t owner,gid_t group);

这个调用使用的是用户ID和组ID的数字值(通过getuid和getgid调用获得)和一个限定谁可以修改文件属主de系统值。

3.7.3 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 *path);

unlink 系统调用删除一个文件的目录项并且减少它的连接数。它在成功时返回0,失败时返回-1

link 系统调用将创建一个指向已有文件path1的新链接。新目录项由path2给出。

symlink 系统调用类似的方式创建符号链接。

3.7.2 mkdir 和rmdir系统调用

使用mkdir 和 rmdir 系统函数来建立和删除目录。

#include sys/types.h
#include sys/stat.h

/**
 * mkdir系统调用用于创建目录,它相当于mkdir程序。mkdir调用将参数path 作为新建目录的名字
  * 目录的权限由参数mode设定, 
 */
int mkdir (const char *path,mode_t mode);

/**
 * rmdir 系统调用用于删除目录,但只有在目录为空时才行。
 */

int rmdir (const char *path)



3.7.5 chdir 系统调用和getcwd函数

程序可以通过chdir系统调用来切换目录

程序可以通过调用getcwd函数来确定自己的当前工作目录

#include unistd.h
int chdir(const char *path)
int getcwd (char *buf,size_t size)

getcwd 函数把当前目录的名字写到给定的缓冲区buf里。如果目录名的长度超出了参数size给出的缓冲区长度(一个ERANGE错误),他就返回NULL,如果成功,它返回指针buf

如果再程序运行过程中,目录被删除(EINVAL错误)或者有关权限发生变化(EACCESS)错误,gecwt也可能返回NULL

3.8 扫描目录

Linux 系统上一个常见的问题就是扫描目录,也就是确定一个特定目录下存放的文件。

与目录相关的函数dirent.h头文件声明。

函数列表:

3.8.1 opendir 函数

opendir函数作用是打开一个目录并建立一个目录流。如果成功,它返回一个指向dir结构的指针,该指针用于读取目录数据项

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);

opendir在失败时返回一个空指针。注意,目录流使用一个底层文件描述符来访问目录本身,所以如果打开的文件过多,opendir可能会失败

3.8.2 readdir 函数

readdir 函数返回一个指针,该指针指向的结构里保存着目录流dirp中下一个目录项的有关资料。

#include <sys/types.h>
#include <dirent>
struct dirent *readdir(DIR *dirp);

注意:如果在readdir函数扫描目录的同时还有其他进程在该目录里创建或删除文件,readdir将不保证能够列出该墓里的所有文件(和子目录)

dirent结构中包含的目录项内容包括以下部分。
ino_t d_ino:文件的inode节点号
char d_name[]文件名字。

3.8.3 telldir函数

telldir函数的返回值纪录着一个目录流里的当前位置。


#include <sys/types.h>
#include <dirent.h>
long int telldir(DIR *dirp)

3.8.4: seekdir 函数

seekdir函数作用就是设置目录流dirp的目录项指针。loc的值用来设置指针位置,他应该通过前个telldir调用获得。

#include <sys/types.h>
#include <dirent.h>
void seekdir(DIR *dirp,long int loc);
3.8.5: closedir 函数

closedir 函数关闭一个目录流并释放与之关联的资源,他的执行成功返回0,发生错误时返回-1

#include <sys/types.h>
#include <dirent.h>

int closedir(DIR *dirp)

总结案例

/*
 ============================================================================
 Name        : printdir.c
 Author      : james
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
void printdir(char *dir ,int depth){

    DIR *dp;
    struct dirent *entry;
    struct stat statbuf;

    if((dp = opendir(dir)) == NULL){
        fprintf(stderr,"cannot open directory:%s\n",dir);
        return ;
    }


    chdir(dir);
    while((entry == readdir(dp)) != NULL){
        lstat (entry->d_name,&statbuf);
        if(S_ISDIR(statbuf.st_mode)){
            /* Found a directory ,but ignore. and..*/
            if(strcmp(".",entry->d_name) == 0 ||
               strcmp("..",entry->d_name) == 0)
                continue;
            printf("%*%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) {

    printf("Directory scan of /home/james/workspace:\n");
    printdir("/home/james/workspace",0);
    printf("done.\n");

    return EXIT_SUCCESS;
}

3.9 错误处理

错误代码的取值喝含义都列在头文件errno.h里:

有两个非常有用的函数可以用来报告出现的错误,它们是strerror 和perror

3.9.1 strerror 函数

strerror 函数把错误代码映射为一个字符串,该字符串对发生的错误类型进程说明。这在记录错误条件时十分有用

函数如下

#include <string.h>
char *strerror(int errnum)
3.9.2 perror 函数

perror 函数也罢errno 变量中报告的当前错误映射到了一个字符串,并把它输出到标准错误输出流。该字符串的前面加上字符串s(如果不为null)中给出的消息,再加上一个冒号和空格

该函数原型如下:


#include <stdio.h>
void perror(const char *s);

请看下面的栗子:

perror(“program”);

他可能在标准错误输出中给出如下输出结果:

program:Too many open files

3.10 /proc 文件系统

Linux 提供了一个特殊的文件系统procfs ,它通常以/proc 目录的形式呈现。该目录中包含了许多的特殊文件用来对缺懂程序和内河星系进程更高层的访问。只要应用程序有正确的访问权限,他们就可以通过读写这些文件来获得信息或设置参数。

proc目录下面文件一览

3.11 高级主题:fcntl 和 mmap

3.11.1: fcntl 系统调用

fcntl 系统调用对底层文件描述符提供了更多的操纵方法。

#include <fcntl.h>

int fcntl(int fildes,int cmd)
int fcntl(int fildes,int cmd ,long arg)

利用fcntl系统调用,你可以对伐开的文件描述符进行各种操作,报考对它们进程复制、获取和设置文件描述符的标志,以及管理建设性文件性文件锁等。

3.11.2 mmap 函数

Unix 提供了一个有用的功能以允许程序共享内存,Linux内核从2.0版本开始已经把这一功能包括进程。mmap(内存映射)函数作用是建立一段可被两个或多个程序读写内存。一个程序对她所作出的修改可以被其他程序看见。

这一组功能还可以用到文件处理上。你可以是某个磁盘文件的全部看起来就像在内存中的一个数组,如果文件由纪录组成,而这些记录又能够用C语言中的结构来描述的话,就可以通过访问结构数组来更新文件的内容了。

mmap函数创建了一个指向一段内存区域的指针,该内存区域可以通过一个打开的文件描述符访问文件的内容相关联。

#include <sys/mman.h>
void *mmap(void *addr ,size_t len, int port,int flags,int fildes,off_t off);

可以通过传递off参数来改变共享内存段访问文件中暑的启示偏移值。打开文件描述符由filds参数给出。可以访问的数据量(即内存的长度)由len参数设置。

port 参数用于设置内存段的访问权限。

flags参数控制程序对该内存段的改变所造成的影响。

msync 函数的作用是:把该内存段的某个部分或争端中的修改回到被映射的文件中(或者从被映射文件里读出)。

mumap 函数的作用是:释放内存段

上一篇 下一篇

猜你喜欢

热点阅读