04-文件和目录操作-dup-fcntl

学习目标

  1. 掌握/open/read/write/lseek/close函数的使用
  1. 掌握stat/lstat函数的使用
  1. 掌握目录遍历相关函数的使用
  1. 掌握dup、dup2函数的使用
  1. 掌握fcntl函数的使用

文件IO

从本章开始学习各种Linux系统函数,这些函数的用法必须结合Linux内核的工作原理来理解, 因为系统函数正是内核提供给应用程序的接口, 而要理解内核的工作原理,必须熟练掌握C语言, 因为内核也是用C语言写的, 我们在描述内核工作原理时必然要用“指针”、“结构体”、“链表”这些名词来组织语言, 就像只有掌握了英语才能看懂英文书一样, 只有学好了C语言才能看懂我描述的内核工作原理。

C标准函数与系统函数的区别

什么是系统调用

由操作系统实现并提供给外部应用程序的编程接口。(Application Programming Interface,API)。是应用程序同系统之间数据交互的桥梁。

一个helloworld如何打印到屏幕。





每一个FILE文件流(标准C库函数)都有一个缓冲区buffer,默认大小8192Byte。Linux系统的IO函数默认是没有缓冲区.





open/close

文件描述符

一个进程启动之后,默认打开三个文件描述符:

#define STDIN_FILENO 0

#define STDOUT_FILENO 1

#define STDERR_FILENO 2

新打开文件返回文件描述符表中未使用的最小文件描述符, 调用open函数可以打开或创建一个文件, 得到一个文件描述符.

open函数

  • 函数描述: 打开或者新建一个文件
  • 函数原型:

int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode);

  • 函数参数:
  • pathname参数是要打开或创建的文件名,和fopen一样, pathname既可以是相对路径也可以是绝对路径。
  • flags参数有一系列常数值可供选择, 可以同时选择多个常数用按位或运算符连接起来, 所以这些常数的宏定义都以O_开头,表示or。
  • 必选项:以下三个常数中必须指定一个, 且仅允许指定一个。
  • O_RDONLY 只读打开
  • O_WRONLY 只写打开
  • O_RDWR 可读可写打开
  • 以下可选项可以同时指定0个或多个, 和必选项按位或起来作为flags参数。可选项有很多, 这里只介绍几个常用选项:
  • O_APPEND 表示追加。如果文件已有内容, 这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
  • O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode, 表示该文件的访问权限。
  • 文件最终权限:mode & ~umask //umask:文件掩码

111 111 111

^000 000 010

111 111 101 = 775

&111 111 111

111 111 101 = 775

  • O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
  • O_TRUNC 如果文件已存在, 将其长度截断为为0字节。
  • O_NONBLOCK 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O(NonblockI/O),非阻塞I/O。
  • 函数返回值:
  • 成功: 返回一个最小且未被占用的文件描述符
  • 失败: 返回-1, 并设置errno值.

  • close函数
  • 函数描述: 关闭文件
  • 函数原型: int close(int fd);
  • 函数参数: fd文件描述符
  • 函数返回值:
  • 成功返回0
  • 失败返回-1, 并设置errno值.

需要说明的是,当一个进程终止时, 内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close, 在终止时内核也会自动关闭它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器), 打开的文件描述符一定要记得关闭, 否则随着打开的文件越来越多, 会占用大量文件描述符和系统资源。

read/write/lseek

read函数

  • 函数描述: 从打开的设备或文件中读取数据
  • 函数原型: ssize_t read(int fd, void *buf, size_t count);
  • 函数参数:
  • fd: 文件描述符
  • buf: 读上来的数据保存在缓冲区buf中
  • count: buf缓冲区存放的最大字节数
  • 函数返回值:
  • >0:读取到的字节数
  • =0:文件读取完毕
  • -1: 出错,并设置errno

write

  • 函数描述: 向打开的设备或文件中写数据
  • 函数原型: ssize_t write(int fd, const void *buf, size_t count);
  • 函数参数:
  • fd:文件描述符
  • buf:缓冲区,要写入文件或设备的数据
  • count:buf中数据的长度
  • 函数返回值:
  • 成功:返回写入的字节数
  • 错误:返回-1并设置errno

lseek

所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为cfo. cfo通常是一个非负整数, 用于表明文件开始处到文件当前位置的字节数. 读写操作通常开始于 cfo, 并且使 cfo 增大, 增量为读写的字节数. 文件被打开时, cfo 会被初始化为 0, 除非使用了 O_APPEND.

使用 lseek 函数可以改变文件的 cfo.

#include

#include

off_t lseek(int fd, off_t offset, int whence);

  • 函数描述: 移动文件指针
  • 函数原型: off_t lseek(int fd, off_t offset, int whence);
  • 函数参数:
  • fd:文件描述符
  • 参数 offset 的含义取决于参数 whence:
  • 如果 whence 是 SEEK_SET,文件偏移量将设置为 offset。
  • 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。
  • 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。
  • 函数返回值: 若lseek成功执行, 则返回新的偏移量。
  • lseek函数常用操作
  • 文件指针移动到头部

lseek(fd, 0, SEEK_SET);

  • 获取文件指针当前位置

int len = lseek(fd, 0, SEEK_CUR);

  • 获取文件长度

int len = lseek(fd, 0, SEEK_END);

  • lseek实现文件拓展

off_t currpos;

// 从文件尾部开始向后拓展1000个字节

currpos = lseek(fd, 1000, SEEK_END);

// 额外执行一次写操作,否则文件无法完成拓展

write(fd, “a”, 1); // 数据随便写

练习:

1 编写简单的IO函数读写文件的代码

函数应用举例

[holo@holocom 0328]$ vim open.c

[holo@holocom 0328]$ gcc -o open open.c

[holo@holocom 0328]$ ./open test.log

n == [11] , buf == [hello world]

//io函数测试->open close read write lseek

#include

#include

#include

#include

#include

#include

int main(int argc  , char ** argv)

{

        int fd = open(argv[1] , O_RDWR | O_CREAT , 0777);

        if(fd < 0)

        {

                perror("open error");

                exit(-1);

        }

        

        int size = write(fd , "hello" , strlen("hello"));

        

        //^

        lseek(fd , 0 , SEEK_SET);

        

        char str[1024];

        memset(str , 0x00 , sizeof(str));

         size = read(fd , str , sizeof(str));

  if (size > 0 && str[size-1] == '\n') {

        str[size-1] = '\0';

    }

    

         printf("[%d][%s]\n",size,str);

        close(fd);

        

}

holo@holo:~/test$ make read

cc     read.c   -o read

holo@holo:~/test$ ./read 1.txt

[14][hellodiaosd a]

holo@holo:~/test$

2 使用lseek函数获取文件大小

//lseek函数获取文件大小

#include

#include

#include

#include

#include

#include

int main(int argc , char * argv[]){

//打开文件

int fd = open(argv[1] , O_RDWR | O_CREAT); //可读可写打开,不存在则创建

//权限是8进制的

if(fd<0){

perror("open error"); //告诉为什么失败

return -1;

}

//调用lseek函数获取文件大小

int offset = lseek(fd , 0 , SEEK_END);

printf("%d",offset);

close(fd);

return 0;

}

[holo@holocom 0328]$ gcc lseek.c

[holo@holocom 0328]$ ls -ltr

总用量 52

-rw-rw-r--. 1 holo holo 1031 3月 28 15:26 open.c

-rwxrwxr-x. 1 holo holo 8800 3月 28 15:26 open

-rwxrwxr-x. 1 holo holo 11 3月 28 15:37 test.loh

-rwxrwxr-x. 1 holo holo 11 3月 28 15:37 test.log

-rw-rw-r--. 1 holo holo 566 3月 28 16:20 lseek.c

-rwxrwxr-x. 1 holo holo 8648 3月 28 16:20 lseek

-rwxrwxr-x. 1 holo holo 8648 4月 2 10:20 a.out

[holo@holocom 0328]$ ./a.out test.log

3 使用lseek函数实现文件拓展

#include

int main(int argc , char * argv[]){

//打开文件

int fd = open(argv[1] , O_RDWR | O_CREAT); //可读可写打开,不存在则创建

//权限是8进制的

if(fd<0){

perror("open error"); //告诉为什么失败

return -1;

}

//调用lseek函数获取文件代销

lseek(fd , 120 , SEEK_SET);

write(fd,"H",1);

close(fd);

return 0;

}

[holo@holocom 0328]$ vim lseek1.c

[holo@holocom 0328]$ vim lseek1.c

[holo@holocom 0328]$ gcc lseek1.c

[holo@holocom 0328]$ ./a.out test.log

[holo@holocom 0328]$ ls -ltr

总用量 56

-rw-rw-r--. 1 holo holo 1031 3月 28 15:26 open.c

-rwxrwxr-x. 1 holo holo 8800 3月 28 15:26 open

-rwxrwxr-x. 1 holo holo 11 3月 28 15:37 test.loh

-rw-rw-r--. 1 holo holo 566 3月 28 16:20 lseek.c

-rwxrwxr-x. 1 holo holo 8648 3月 28 16:20 lseek

-rw-rw-r--. 1 holo holo 556 4月 2 10:41 lseek1.c

-rwxrwxr-x. 1 holo holo 8648 4月 2 10:41 a.out

-rwxrwxr-x. 1 holo holo 121 4月 2 10:41 test.log

[holo@holocom 0328]$ vim test.log



U盘拷贝 : 先填充 后真拷贝 , 如果空间不够提前发现

perror和errno

errno、perror和stderr是用于处理错误信息的相关概念和工具。

  1. errno是一个全局变量,用于表示最近发生的错误代码。它定义在  头文件中,并由系统库函数在发生错误时设置。每个可能的错误代码都有一个唯一的整数值,可以通过errno变量来获取。在使用系统库函数之后,我们可以检查errno变量的值来确定函数是否成功执行,以及错误的具体原因。
  1. perror是一个函数,用于将当前errno的值和相应的错误消息打印到标准错误流(stderr)。它的原型定义在  头文件中。perror函数接受一个字符串作为参数,它会将这个字符串和errno的错误消息一起打印到标准错误流,以便我们可以更好地理解发生了什么错误。
  1. stderr是一个指向标准错误流的文件指针。它是标准C库定义的一个文件流,用于输出错误和诊断信息。与标准输出流(stdout)不同,标准错误流是一个独立的流,用于输出错误消息,而不会受到标准输出流的重定向影响。我们可以使用fprintf函数或直接使用stderr来将错误消息输出到标准错误流。

综上所述,errno变量用于表示最近发生的错误代码,perror函数用于将错误消息打印到标准错误流,而stderr则是标准错误流本身。我们可以使用它们配合使用,以更好地处理和输出错误信息。

当我们使用系统库函数时,可以通过errno、perror和stderr来处理和输出错误信息。以下是一个示例:

#include

#include

#include

int main() {

FILE *file = fopen("nonexistent.txt", "r");

if (file == NULL) {

perror("Error");

fprintf(stderr, "Failed to open file: %s\n", strerror(errno));

return EXIT_FAILURE;

}

// 其他文件操作...

fclose(file);

return 0;

}





在这个示例中,我们尝试打开一个不存在的文件("nonexistent.txt")。由于文件不存在,fopen函数将返回NULL指针,并设置errno为适当的错误代码。

接下来,我们使用perror函数将错误消息打印到标准错误流。它会将字符串"Error"和错误消息一起输出,以提供更详细的错误描述。

然后,我们使用fprintf函数将错误消息输出到标准错误流。通过调用strerror(errno),我们将errno的错误代码转换为相应的错误消息字符串,然后将其与自定义的错误消息一起输出。

最后,我们返回一个适当的退出状态码,以表示程序执行失败。

在这个示例中,perror和fprintf(stderr, ...)都将错误信息输出到标准错误流,而不是标准输出流。这样可以将错误消息与正常输出分开,并确保错误信息在错误流中可见。

每个进程都有独立的虚拟地址空间, 有独立的errno,每个进程的errno互不影响

errno是一个全局变量, 当系统调用后若出错会将errno进行设置, perror可以将errno对应的描述信息打印出来.

如:perror("open"); 如果报错的话打印: open:(空格)错误信息

练习:编写简单的例子, 测试perror和errno.

[holo@holocom 0328]$ gcc lseek.c

[holo@holocom 0328]$ ./a.out ***

open error: Text file busy

报错信息可以在man errno.h 里找到





POSIX.1 : 可移植的





阻塞和非阻塞:

阻塞:等待条件成立,成立了才能接着操作别的窗口

类似QT的模态对话框

思考: 阻塞和非阻塞是文件的属性还是read函数的属性?

  • 普通文件:hello.c
  • 默认是非阻塞的
  • 终端设备:如 /dev/tty
  • 默认阻塞
  • 管道和套接字
  • 默认阻塞

练习:

1 测试普通文件是阻塞还是非阻塞的?

#include

#include

#include

#include

#include

#include

int main(int argc , char * argv[]){

//打开文件

int fd = open(argv[1] , O_RDWR | O_CREAT , 0777); //可读可写打开,不存在则创建

//权限是8进制的

if(fd<0){

perror("open error"); //告诉为什么失败

return -1;

}

//读文件

// ssize_t read(int fd, void *buf, size_t count);

char buf[1024];

memset(buf , 0x00 , sizeof(buf));

int n = read(fd , buf , sizeof(buf)); // fd,缓冲区,缓冲区大小

printf("FIRSE READ:n == [%d] , buf == [%s]\n" , n , buf); //[]是为了可以看到缓冲区是空的情况

//再次读文件,验证read函数是否阻塞

memset(buf , 0x00 , sizeof(buf));

n = read(fd , buf , sizeof(buf)); // fd,缓冲区,缓冲区大小

printf("SECOND READ:n == [%d] , buf == [%s]\n" , n , buf); //[]是为了可以看到缓冲区是空的情况

//关闭文件

close(fd);

return 0;

}

[holo@holocom 0401]$ ./a.out test.log

FIRSE READ:n == [13] , buf == [testtesttest

]

SECOND READ:n == [0] , buf == []

通过读普通文件得知,read函数读完文件内容之后,若再次read,则read函数会立刻返回,表明read函数读普通文件是非阻塞的,.

2 测试终端设备文件/dev/tty是阻塞还是非阻塞的.

得出结论: 阻塞和非阻塞是文件本身的属性, 不是read函数的属性.

//验证read函数读设备文件/dev/tty是否阻塞

#include

#include

#include

#include

       #include

       #include

int main()

{

        int fd = open("/dev/tty", O_RDWR);

        if(fd < 0)

        {

                perror("open error");

                return -1;

        }

        char str[1024];

        memset(str , 0x00 , sizeof(str));

        int n = read(fd , str , sizeof(str));

        printf("FIRST: n == [%d] , str = [%s]\n",n,str);

        close(fd);

}

holo@holo:~/test$ ./tty 

1212e1das

FIRST: n == [10] , str = [1212e1das

]

经过测试, read读设备文件是阻塞的

#include

#include

#include

#include

#include

#include

#include

int main()

{

//      int fd = open("/dev/tty", O_RDWR);

//      if(fd < 0)

//      {

//              perror("open error");

//              return -1;

//      }

        char str[1024];

        memset(str , 0x00 , sizeof(str));

        int n = read(STDIN_FILENO , str , sizeof(str));

        printf("FIRST: n == [%d] , str = [%s]\n",n,str);

//      close(fd);

}

holo@holo:~/test$ ./tty

nihao

FIRST: n == [6] , str = [nihao

]

holo@holo:~/test$ 

经测试 , read读设备文件是阻塞的,

结论:阻塞/非阻塞是文件本身的属性, read本身不具备阻塞/非阻塞属性.

socket, pipe 文件 也是 阻塞的.

文件和目录

文件操作相关函数

stat/lstat函数

  • 函数描述: 获取文件属性
  • 函数原型: int stat(const char *pathname, struct stat *buf);

//被const修饰的参数 一定是输入,而不是输出 属性放到buf里.

int lstat(const char *pathname, struct stat *buf);

  • 函数返回值:
  • 成功返回 0
  • 失败返回 -1

struct stat {

dev_t st_dev; //文件的设备编号

ino_t st_ino; //节点

mode_t st_mode; //文件的类型和存取的权限

nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1

uid_t st_uid; //用户ID

gid_t st_gid; //组ID

dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号

off_t st_size; //文件字节数(文件大小)

blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)

blkcnt_t st_blocks; //块数

time_t st_atime; //最后一次访问时间

time_t st_mtime; //最后一次修改时间

time_t st_ctime; //最后一次改变时间(指属性)

};

- st_mode -- 16位整数

  • R(Read):表示可读权限,允许读取文件的内容。
  • W(Write):表示可写权限,允许修改文件的内容。
  • X(Execute):表示可执行权限,允许执行文件作为程序。

用户 所属组 其他人

○ 0-2 bit -- 其他人权限

S_IROTH 00004 读权限

S_IWOTH 00002 写权限

S_IXOTH 00001 执行权限

S_IRWXO 00007 掩码, 过滤 st_mode中除其他人权限以外的信息 001 | 010 | 100

○ 3-5 bit -- 所属组权限

S_IRGRP 00040 读权限

S_IWGRP 00020 写权限

S_IXGRP 00010 执行权限

S_IRWXG 00070 掩码, 过滤 st_mode中除所属组权限以外的信息

○ 6-8 bit -- 文件所有者权限

S_IRUSR 00400 读权限

S_IWUSR 00200 写权限

S_IXUSR 00100 执行权限

S_IRWXU 00700 掩码, 过滤 st_mode中除文件所有者权限以外的信息

问:怎么判断某个用户有没有读权限?

答:If (st_mode & S_IRUSR) -----为真 表明可读

If (st_mode & S_IWUSR) ------为真 表明可写

If (st_mode & S_IXUSR) ------为真 表明可执行

○ 12-15 bit -- 文件类型

S_IFSOCK 0140000 套接字 001 100

S_IFLNK 0120000 符号链接(软链接) 001 010

S_IFREG 0100000 普通文件 001 000

S_IFBLK 0060000 块设备 000 110

S_IFDIR 0040000 目录 000 100

S_IFCHR 0020000 字符设备 000 010

S_IFIFO 0010000 管道 000 001

------------------------------------------------------------

S_IFMT 0170000 掩码,过滤 st_mode中除文件类型以外的信息 001 111(上面有1的全放下来)

问:怎么判断文件属性是否正确?

答:

If ((st_mode & S_IFMT)==S_IFREG) ----为真普通文件

与下面的语句等价,LINUX提供了宏

if(S_ISREG(st_mode)) ------为真表示普通文件

if(S_ISDIR(st.st_mode)) ------为真表示目录文件

if(S_ISLNK(st.st_mode)) ------为真表示链接文件

stat函数和lstat函数的区别

  • 对于普通文件, 这两个函数没有区别, 是一样的.
  • 对于连接文件,调用lstat函数获取的是链接文件本身的属性信息;

而stat函数获取的是链接文件指向的文件的属性信息.

练习:

1 stat函数获取文件大小

//stat函数测试,获取文件大小,文件属组和组

#include

#include

#include

#include

#include

#include

//不需要打开文件

int main(int argc , char * argv[]){

struct stat st;

stat(argv[1],&st); //属性信息保存到st结构体里

printf("size:[%d]\tuid:[%d]\tgid:[%d]",st.st_size,st.st_uid,st.st_gid);

return 0;

}

[holo@holocom 0401]$ cp ../0328/open.c stat.c

[holo@holocom 0401]$ ls

a.out block_read.c stat.c stdin_read.c test.log unblock_read.c

[holo@holocom 0401]$ vim stat.c

[holo@holocom 0401]$ gcc stat.c

[holo@holocom 0401]$ ./a.out test.log

size:[13] uid:[1000] gid:[1000][holo@holocom 0401]$ ls -ltr

总用量 32

-rw-rw-r--. 1 holo holo 1088 4月 2 11:12 unblock_read.c

-rwxrwxr-x. 1 holo holo 13 4月 2 11:13 test.log

-rw-rw-r--. 1 holo holo 1075 4月 2 11:27 block_read.c

-rw-rw-r--. 1 holo holo 552 4月 2 11:42 stdin_read.c

-rw-rw-r--. 1 holo holo 389 4月 2 12:29 stat.c

-rwxrwxr-x. 1 holo holo 8552 4月 2 12:30 a.out

[holo@holocom 0401]$ id //一个用户只有一个用户id 一个组只有一个组id

uid=1000(holo) gid=1000(holo) 组=1000(holo) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

2 stat函数获取文件类型和文件权限

//stat函数测试,获取文件类型和权限

#include

#include

#include

#include

#include

#include

int main(int argc , char * argv[]){

struct stat sb;

stat(argv[1],&sb); //属性信息保存到st结构体里

if((sb.st_mode & S_IFMT) == S_IFREG){

printf("普通文件\n");

}

else if((sb.st_mode & S_IFMT) == S_IFDIR){

printf("目录文件\n");

}else if((sb.st_mode & S_IFMT) == S_IFLNK){

printf("链接文件\n");

}

if(sb.st_mode & S_IROTH){

printf("读R权限");

} if(sb.st_mode & S_IWOTH){

printf("写W权限");

}

if(sb.st_mode & S_IXOTH){

printf("执行X权限");

}

return 0;

}

[holo@holocom 0401]$ gcc stat1.c

[holo@holocom 0401]$ chmod o+w test.log

[holo@holocom 0401]$ ./a.out test.log

普通文件

读R权限写W权限执行X权限[holo@holocom 0401]$ ls -ltr

总用量 52

-rw-rw-r--. 1 holo holo 1088 4月 2 11:12 unblock_read.c

-rwxrwxrwx. 1 holo holo 13 4月 2 11:13 test.log

-rw-rw-r--. 1 holo holo 1075 4月 2 11:27 block_read.c

-rw-rw-r--. 1 holo holo 552 4月 2 11:42 stdin_read.c

-rw-rw-r--. 1 holo holo 389 4月 4 14:09 stat.c

-rw-rw-r--. 1 holo holo 1648 4月 4 14:09 stat.o

lrwxrwxrwx. 1 holo holo 8 4月 4 14:55 test.log.s -> test.log

-rw-rw-r--. 1 holo holo 732 4月 4 15:12 stat2.c

-rw-rw-r--. 1 holo holo 2248 4月 4 15:13 stat2.o

-rw-rw-r--. 1 holo holo 2248 4月 4 15:15 stat1.o

-rw-rw-r--. 1 holo holo 750 4月 4 15:23 stat1.c

-rwxrwxr-x. 1 holo holo 8600 4月 4 15:23 a.out

[holo@holocom 0401]$ chmod o-r test.log

[holo@holocom 0401]$ ./a.out test.log

普通文件

写W权限执行X权限[holo@holocom 0401]$ ls -ltr

总用量 52

-rw-rw-r--. 1 holo holo 1088 4月 2 11:12 unblock_read.c

-rwxrwx-wx. 1 holo holo 13 4月 2 11:13 test.log

-rw-rw-r--. 1 holo holo 1075 4月 2 11:27 block_read.c

-rw-rw-r--. 1 holo holo 552 4月 2 11:42 stdin_read.c

-rw-rw-r--. 1 holo holo 389 4月 4 14:09 stat.c

-rw-rw-r--. 1 holo holo 1648 4月 4 14:09 stat.o

lrwxrwxrwx. 1 holo holo 8 4月 4 14:55 test.log.s -> test.log

-rw-rw-r--. 1 holo holo 732 4月 4 15:12 stat2.c

-rw-rw-r--. 1 holo holo 2248 4月 4 15:13 stat2.o

-rw-rw-r--. 1 holo holo 2248 4月 4 15:15 stat1.o

-rw-rw-r--. 1 holo holo 750 4月 4 15:23 stat1.c

-rwxrwxr-x. 1 holo holo 8600 4月 4 15:23 a.out

3 lstat函数获取连接文件的属性(文件大小)

对于软连接文件, stat 有 穿透功能(透过链接指向文件),类似于 vim 软连接文件.txt,打开的是指向的文件

rm删除的时候删除的是软连接文件自己,而不是指向的文件

#include

#include

#include

#include

#include

#include

int main(int argc , char * argv[]){

struct stat st;

lstat(argv[1],&st); //属性信息保存到st结构体里

printf("size:[%d]\tuid:[%d]\tgid:[%d]",st.st_size,st.st_uid,st.st_gid);

return 0;

}

[holo@holocom 0401]$ ./lstat test.log

size:[13] uid:[1000] gid:[1000][holo@holocom 0401]$

[holo@holocom 0401]$ ./lstat test.log.s

size:[8] uid:[1000] gid:[1000][holo@holocom 0401]$

目录操作相关函数

opendir函数

  • 函数描述:打开一个目录
  • 函数原型: DIR *opendir(const char *name);
  • 函数返回值: 指向目录的指针
  • 函数参数: 要遍历的目录(相对路径或者绝对路径)

readdir函数

  • 函数描述: 读取目录内容--目录项,读一次指针自动后移一位
  • 函数原型: struct dirent *readdir(DIR *dirp);
  • 函数返回值: 读取的目录项指针
  • 函数参数: opendir函数的返回值(指针)

struct dirent

{

ino_t d_ino; // 此目录进入点的inode

off_t d_off; // 目录文件开头至此目录进入点的位移

signed short int d_reclen; // d_name 的长度, 不包含NULL 字符

unsigned char d_type; // d_name 所指的文件类型

char d_name[256]; // 文件名

};

d_type的取值:

  • DT_BLK - 块设备
  • DT_CHR - 字符设备
  • DT_DIR - 目录
  • DT_LNK - 软连接
  • DT_FIFO - 管道
  • DT_REG - 普通文件
  • DT_SOCK - 套接字
  • DT_UNKNOWN - 未知





closedir函数

  • 函数描述: 关闭目录
  • 函数原型: int closedir(DIR *dirp);
  • 函数返回值: 成功返回0, 失败返回-1
  • 函数参数: opendir函数的返回值

读取目录内容的一般步骤

1 DIR *pDir = opendir(“dir”); //打开目录

2 while((p=readdir(pDir))!=NULL){} //循环读取文件

3 closedir(pDir); //关闭目录

练习

1 遍历指定目录下的所有文件, 并判断文件类型.

//目录操作测试: opendir readdir closedir

#include

#include

#include

#include

#include

#include

int main(int argc,char * argv[]){

//打开目录

DIR *pDir = opendir(argv[1]);

if(pDir == NULL){

perror("opendir error");

return -1;

}

//循环读取目录项

struct dirent *pDent = NULL;

while((pDent = readdir(pDir)) != NULL){

if(strcmp(pDent->d_name , ".") == 0 || strcmp(pDent->d_name , "..") == 0){

continue;

}

printf("[%s]------>",pDent->d_name);

//判断文件类型

switch(pDent->d_type){

case DT_REG:

printf("普通文件");

break;

case DT_DIR:

printf("目录文件");

break;

case DT_LNK:

printf("链接文件");

break;

}

printf("\n");

}

//关闭目录

closedir(pDir);

}

[holo@holocom 0401]$ vim opendir.c

[holo@holocom 0401]$ gcc opendir.c

[holo@holocom 0401]$ ./a.out .

[.]------>目录文件

[..]------>目录文件

[unblock_read.c]------>普通文件

[test.log]------>普通文件

[block_read.c]------>普通文件

[stdin_read.c]------>普通文件

[.stat.c.swp]------>普通文件

[stat.c]------>普通文件

[stat.o]------>普通文件

[stat2.c]------>普通文件

[stat2.o]------>普通文件

[stat1.o]------>普通文件

[stat1.c]------>普通文件

[lstat.c]------>普通文件

[lstat]------>普通文件

[.opendir.c.swp]------>普通文件

[opendir.c]------>普通文件

[a.out]------>普通文件

[holo@holocom 0401]$ ls

a.out lstat opendir.c stat1.o stat2.o stat.o test.log

block_read.c lstat.c stat1.c stat2.c stat.c stdin_read.c unblock_read.c

2 递归遍历目录下所有的文件, 并判断文件类型.

特别注意: 递归遍历指定目录下的所有文件的时候, 要过滤掉.和..文件, 否则会进入死循环

dup/dup2/fcntl









dup函数

  • 函数描述: 复制文件描述符
  • 函数原型: int dup(int oldfd);
  • 函数参数: oldfd -要复制的文件描述符
  • 函数返回值:
  • 成功: 返回最小且没被占用的文件描述符
  • 失败: 返回-1, 设置errno值
  • 

练习: 编写程序, 测试dup函数.

#include

#include

#include

#include

#include

#include

#include

int main(int argc,char* argv[]){

//打开文件

int fd = open(argv[1],O_RDWR);

if(fd < 0){

perror("open error");

return -1;

}

int newfd = dup(fd);

printf("newfd:[%d],fd:[%d]\n",newfd,fd);

char buf[64];

memset(buf,0x00,sizeof(buf));

write(fd,"helloworld",strlen("helloworld"));

//调用lseek函数移动文件指针到开始处

//两个文件指针指向同一个文件,但是随着写,文件指针都会发生移动

lseek(fd,0,SEEK_SET);

int n = read(newfd,buf,sizeof(buf));

printf("read over: n == [%d], buf == [%s]\n" , n , buf);

close(fd);

close(newfd);

return 0;

}

[holo@holocom 0405]$ gcc dup.c

[holo@holocom 0405]$ ./a.out test.log

newfd:[4],fd:[3]

read over: n == [10], buf == [helloworld]

dup2函数

  • 函数描述: 复制文件描述符
  • 函数原型: int dup2(int oldfd, int newfd);
  • 函数参数:
  • oldfd-原来的文件描述符
  • newfd-复制成的新的文件描述符
  • 函数返回值:
  • 成功: 将oldfd复制给newfd, 两个文件描述符指向同一个文件
  • 失败: 返回-1, 设置errno值
  • 假设newfd已经指向了一个文件,首先close原来打开的文件,然后newfd指向oldfd指向的文件.

若newfd没有被占用,newfd指向oldfd指向的文件.

练习:

1编写程序, 测试dup2函数实现文件描述符的复制.

#include

#include

#include

#include

#include

#include

#include

int main(int argc,char* argv[]){

//打开文件

int fd = open(argv[1],O_RDWR | O_CREAT ,0755);

if(fd < 0){

perror("open 1 error");

return -1;

}

int fd2 = open(argv[2],O_RDWR | O_CREAT ,0755);

if(fd2<0){

perror("open 2 error");

return -1;

}

char buf[64];

memset(buf,0x00,sizeof(buf));

dup2(fd,fd2);

write(fd,"helloworld",strlen("helloworld"));

lseek(fd,0,SEEK_SET); //操作哪个都一样

int n = read(fd2,buf,sizeof(buf));

printf("read over:n = [%d],buf = [%s]",n,buf);

close(fd);

close(fd2);

return 0;

}

[holo@holocom 0405]$ make dup2

make: “dup2”是最新的。

[holo@holocom 0405]$ ./dup2 temp1.log temp2.log

read over:n = [10],buf = [helloworld][holo@holocom 0405]$ ls -ltr

总用量 40

-rw-rw-r--. 1 holo holo 10 4月 5 08:45 test.log

-rw-rw-r--. 1 holo holo 742 4月 5 08:48 dup.c

-rwxrwxr-x. 1 holo holo 8848 4月 5 10:18 a.out

-rw-rw-r--. 1 holo holo 682 4月 5 10:29 dup2.c

-rwxrwxr-x. 1 holo holo 8848 4月 5 10:29 dup2

-rwxr-xr-x. 1 holo holo 0 4月 5 10:30 temp2.log

-rwxr-xr-x. 1 holo holo 10 4月 5 10:31 temp1.log

New文件是空的,

Old文件里是有东西的

2 编写程序, 完成终端标准输出重定向到文件中





[holo@holocom 0405]$ vim dup2-1.c

//使用dup2函数实现文件重定向操作

#include

#include

#include

#include

#include

#include

#include

int main(int argc,char* argv[]){

//打开文件

int fd = open(argv[1],O_RDWR | O_CREAT ,0755);

if(fd < 0){

perror("open 1 error");

return -1;

}

dup2(fd,STDOUT_FILENO); //fd目的,STDOUT_FILENO源

printf("你好 helloworld");

close(fd);

//不能关闭STDOUT_FILENO

return 0;

}

[holo@holocom 0405]$ vim dup2-1.c

[holo@holocom 0405]$ gcc dup2-1.c

[holo@holocom 0405]$ ./a.out test.log

[holo@holocom 0405]$ cat test.log

你好 helloworld

fcntl函数

  • 函数描述: 改变已经打开的文件的属性
  • 函数原型: int fcntl(int fd, int cmd, ... /* arg */ ); //变参函数 文件描述符(open,dup,dup2获取),
  • 若cmd为F_DUPFD, 复制文件描述符, 与dup相同
  • 若cmd为F_GETFL, 获取文件描述符的flag属性值
  • 若cmd为 F_SETFL, 设置文件描述符的flag属性
  • 函数返回值:返回值取决于cmd
  • 成功
  • 若cmd为F_DUPFD, 返回一个新的文件描述符
  • 若cmd为F_GETFL, 返回文件描述符的flags值
  • 若cmd为 F_SETFL, 返回0
  • 失败返回-1, 并设置errno值.

  • fcntl函数常用的操作:

1 复制一个新的文件描述符:

int newfd = fcntl(fd, F_DUPFD, 0);

2 获取文件的属性标志





int flag = fcntl(fd, F_GETFL, 0)

3 设置文件状态标志

flag = flag | O_APPEND;

fcntl(fd, F_SETFL, flag)

4 常用的属性标志

O_APPEND-----设置文件打开为末尾添加

O_NONBLOCK-----设置打开的文件描述符为非阻塞

练习:

1 使用fcntl函数实现复制文件描述符

[holo@holocom 0405]$ cat fctnl.c

#include

#include

#include

#include

#include

#include

#include

int main(int argc,char* argv[]){

//打开文件

int fd = open(argv[1],O_RDWR);

if(fd < 0){

perror("open error");

return -1;

}

// int newfd = dup(fd);

int newfd = fcntl(fd, F_DUPFD, 0); //复制fd 知道有这个功能就行,不用这个复制,因为不如dup简单

printf("newfd:[%d],fd:[%d]\n",newfd,fd);

//使用newfd

char buf[64];

memset(buf,0x00,sizeof(buf));

write(fd,"helloworld",strlen("helloworld"));

//调用lseek函数移动文件指针到开始处

//两个文件指针指向同一个文件,但是随着写,文件指针都会发生移动

lseek(fd,0,SEEK_SET);

int n = read(newfd,buf,sizeof(buf));

printf("read over: n == [%d], buf == [%s]\n" , n , buf);

close(fd);

close(newfd);

return 0;

}

[holo@holocom 0405]$ vim fctnl.c

[holo@holocom 0405]$ make fctnl

cc fctnl.c -o fctnl

[holo@holocom 0405]$ ./fctnl test.log

newfd:[4],fd:[3]

read over: n == [17], buf == [helloworldloworld]

2 使用fcntl函数设置在打开的文件末尾添加内容.

[holo@holocom 0405]$ cat fctnl1.c

//修改文件描述符的属性

#include

#include

#include

#include

#include

#include

#include

int main(int argc,char* argv[]){

//打开文件

int fd = open(argv[1],O_RDWR);

if(fd < 0){

perror("open error");

return -1;

}

//获得fd的flags属性

int flags = fcntl(fd, F_GETFL, 0);

//设置fd的flags属性

flags = flags | O_APPEND;

fcntl(fd, F_SETFL, flags);

write(fd,"helloworld",strlen("helloworld"));

close(fd);

return 0;

}

[holo@holocom 0405]$ vim fctnl1.c

[holo@holocom 0405]$ make fctnl1

make: “fctnl1”是最新的。

[holo@holocom 0405]$ ./fctnl1 test.log

你可能感兴趣的:(linux,linux)