【文件I/O】(二)文件I/O

一、文件I/O基本概念

1.什么是文件I/O?

posix(可移植操作系统接口)定义的一组函数,不提供缓冲机制,每次读写操作都引起系统调用,核心概念是文件描述符,访问各类型文件,在Linux下,标准I/O基于文件I/O实现。

2.文件描述符

文件描述符(fd)是一个非负整数,Linux为程序中每个打开的文件分配一个文件描述符。文件描述符从0开始分配,依次递增,在ubuntu系统中一个正在执行的程序,文件描述符的范围是 0~1023(1024个)。每一个正在执行的程序都有自己的一套文件描述符,相互不干扰。

一个正在执行的程序中0,1,2文件描述符已经被占用了,分别对应的是标准输入,标准输出,标准出错。

#include <stdio.h>

int main(int argc, const char* argv[])
{
    printf("in  fd = %d\n", stdin->_fileno);  // 0
    printf("out fd = %d\n", stdout->_fileno); // 1
    printf("err fd = %d\n", stderr->_fileno); // 2
    return 0;
}

ulimit -a 显示当前所有的资源限制等。
ulimit -n 2048 修改文件描述符范围为2048。

二、文件I/O函数

head.h

编写head.h的头文件
将head.h放到/usr/include路径下
修改head.h文件所属用户和组,sudo chown linux:linux /usr/include/head.h
修改用户代码片段(c.json),将#include <stdio.h>改成#include <head.h>

#ifndef __HEAD_H__
#define __HEAD_H__

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

#define PRINT_ERR(msg) \
    do                 \
    {                  \
        perror(msg);   \
        return -1;     \
    } while (0)

#endif

1.open、close(打开、关闭文件)

1.1open、close函数API

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:打开文件
参数:
    @pathname:打开文件的路径及名字
 	@flags   :打开文件的方式
        O_RDONLY :只读
        O_WRONLY :只写
        O_RDWR   :读写
	  	O_APPEND :追加
	  	O_CREAT  :创建,如果flags中填写了O_CREAT,就必须填写第三个参数,第三个参数代表创建文件的权限
  		O_TRUNC  :清空
  		O_EXCL   :和O_CREAT结合使用,在创建文件的时候加上这个选项,如果文件存在就返回EEXIST,如果文件不存在就会不返回错误
  		O_NOCTTY :使用本参数时,如文件为终端,那么终端不可作为调用open()系统调用的那个进程的控制终端。
	 @mode:创建文件的权限,为8进制表示法
        实际创建出来的文件的权限 = (mode & ~umask)
        umask:文件的掩码:通过umask命令可以查看掩码的默认值是0002  
        ~umask:文件掩码的取反是在最大默认文件的权限的基础上取反的,最大默认文件的权限是0666
        实际创建出来的文件的权限 = (0666 & ~(0002)) =  (0666 & 0664) = 0664
返回值:成功返回文件描述符,失败返回-1置位错误码
#include <unistd.h>

int close(int fd);
功能:关闭文件
参数:
    @fd:文件描述符
返回值:成功返回0,失败返回-1置位错误码

1.2文件I/O和标准I/O文件打开方式对比

文件IO标准IO功能
O_RDONLY“r”以只读的方式打开文件,将光标定位到开头
O_RDWR“r+”以读写的方式打开文件,将光标定位到开头
O_WRONLY | O_CREAT | O_TRUNC,0666“w”以只写的方式打开文件,如果文件不存在就创建,如果文件存在清空,光标在开头
O_RDWR | O_CREAT | O_TRUNC,0666“w+”以读写的方式打开文件,如果文件不存在就创建,如果文件存在清空,光标在开头
O_WRONLY | O_CREAT | O_APPEND,0666“a”以只写和追加的方式打开文件,如果文件不存在就创建,如果存在光标在结尾
O_RDWR | O_CREAT | O_APPEND,0666“a+”以读写和追加的方式打开文件,如果文件不存在就创建,如果文件存在读光标在开头,写光标在结尾

1.3open函数实例1(只读、只写)

只读、只写方式打开文件。

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

#define PRINT_ERR(msg) \
    do                 \
    {                  \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc, const char *argv[])
{
    int fd1, fd2;

    // 只读方式打开文件
    if (-1 == (fd1 = open("./1.txt", O_RDONLY)))
        PRINT_ERR("open error");

    // 只写方式打开文件
    if (-1 == (fd2 = open("./hello.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666)))
        PRINT_ERR("open error");

    close(fd1);
    close(fd2);
    return 0;
}

1.4open函数实例2(参数O_EXCL)

如果文件不存在,创建文件,以只写方式打开文件。
如果文件存在,以只读方式打开文件。

#include <head.h>

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

    // 如果文件不存在,创建文件,以只写方式打开文件
    // 如果文件存在,以只读方式打开文件
    if (-1 == (fd = open("./1.txt", O_WRONLY | O_CREAT | O_EXCL, 0666))) {
        if (errno = EEXIST) {
            if (-1 == (fd = open("./1.txt", O_RDONLY)))
                PRINT_ERR("open error");
                puts("文件存在,以只读方式打开文件");
        } else {
            PRINT_ERR("open error");
        }
    } else {
        puts("文件不存在,创建文件,以只写方式打开文件");
    }

    printf("fd = %d\n", fd);
    close(fd);
    return 0;
}

1.5close函数实例3(关闭标准输出)

关闭标准输出(fd = 1),终端不会输出。

#include <head.h>

int main(int argc, const char *argv[])
{
    puts("--------------start");
    close(1);
    puts("--------------end");  //关闭标准输出(fd = 1),终端不会输出这行

    return 0;
}

2.read、write(读取、写入文件)

2.1read、write函数API

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
功能:读取文件中的内容
参数:
    @fd:文件描述符
 	@buf:存储读取到数据的首地址
 	@count:想要读取的字节的个数
返回值:成功返回读取到字节的个数,如果返回0代表读取到文件的结尾了
        失败返回-1置位错误码
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
功能:向文件中写数据
参数:
    @fd:文件描述符
    @buf:想要写的数据的首地址
    @count:想要写的字节的个数
返回值:成功返回写入的字节的个数,0表示不会写任何的数据
     失败返回-1置位错误码

2.2read函数实例1(读取整数、字符串、结构体)

配合2.3wirte函数实例2使用

#include <head.h>

typedef struct stu {
    char name[20];
    int age;
    char sex;
}stu_t;

int main(int argc, const char *argv[])
{
    int fd;
    if (-1 == (fd = open("./1.txt", O_RDONLY)))
        PRINT_ERR("open error");
 /*    
    // 1.验证读取整数
    int num;
    read(fd, &num, sizeof(num));
    printf("num = %d\n", num);

    // 2.验证读取字符串
    char buf[20] = {0};
    read(fd, buf, sizeof(buf));
    printf("buf = %s\n", buf);
 */
    // 3.验证读取结构体
    stu_t stu;
    read(fd, &stu, sizeof(stu));
    printf("name = %s, age = %d, sex = %c\n", stu.name, stu.age, stu.sex);

    close(fd);
    return 0;
}

2.3write函数实例2(写入整数、字符串、结构体)

配合2.2read函数实例1使用

#include <head.h>

typedef struct stu {
    char name[20];
    int age;
    char sex;
}stu_t;

int main(int argc, const char *argv[])
{
    int fd;
    if (-1 == (fd = (open("./1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666))))
        PRINT_ERR("open error");
/* 
    // 1.验证写入整数
    int num = 12345;
    write(fd, &num, sizeof(num));

    // 2.验证写入字符串
    char buf[100] = "hello world!";
    write(fd, buf, strlen(buf));
 */
    // 3.验证写入结构体
   stu_t stu = {
        .name = "xiaoming",
        .age = 21,
        .sex = 'm'
    };
    write(fd, &stu, sizeof(stu_t));

    close(fd);
    return 0;
}

2.4write函数实例3(计算文件大小)

#include <head.h>

int main(int argc, const char *argv[])
{
    if (2 != argc) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        return -1;
    }

    int fd;
    if (-1 == (fd = open(argv[1], O_RDONLY)))
        PRINT_ERR("open error");

    int total = 0;
    int ret;
    char buf[10] = {0};
    while (0 < (ret = read(fd, buf, sizeof(buf)))) {
        total += ret;
    }

    printf("%s size total is %d\n", argv[1], total);

    close(fd);
    return 0;
}

2.5read、write函数实例4(文件拷贝)

#include <head.h>

int main(int argc, const char *argv[])
{
    // 1.校验用户输入的参数是否正确
    if (3 != argc) {
        fprintf(stderr, "Usage: %s <src_file> <dest_file>\n", argv[0]);
        return -1;
    }

    // 2.只读方式打开源文件,写的方式打开目标文件(创建)
    int sfd, dfd;
    if (-1 == (sfd = open(argv[1], O_RDONLY)))
        PRINT_ERR("open src_file error");

    if (-1 == (dfd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666)))
        PRINT_ERR("open dest_file error");

    // 3.拷贝
    int ret;
    char buf[10] = {0};
    while (0 < (ret = read(sfd, buf, sizeof(buf)))) {
        write(dfd, buf, ret);
    }

    // 4.关闭文件
    close(sfd);
    close(dfd);
    return 0;
}

2.6fgets、write函数实例5(键盘输入内容到文件)

#include <head.h>

int main(int argc, const char *argv[])
{
    if (2 != argc) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        return -1;
    }

    int fd;
    if (-1 == (fd = open(argv[1], O_RDWR | O_APPEND | O_CREAT, 0666)))
        PRINT_ERR("open error");

    char buf[10] = {0};
    while (NULL != fgets(buf, 10, stdin)) {
        if (0 == strcmp(buf, "quit\n")) break;  //终端输入quit退出
        write(fd, buf, strlen(buf));
    }

    close(fd);
    return 0;
}

3.lseek(定位文件指针)

3.1lseek函数API

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

off_t lseek(int fd, off_t offset, int whence);
功能:设置光标的位置
参数:
    @fd:文件描述符
 	@offset:偏移量
        >0:向后偏移
        =0:不偏移
  		<0:向前偏移
	@whence:从那个位置开始偏移
  		SEEK_SET:从文件的开头
        SEEK_CUR:从当前位置
  		SEEK_END:从文件的结尾
返回值:成功返回光标到开头的字节数,失败返回-1置位错误码

3.2lseek函数实例1

#include <head.h>

int main(int argc, const char *argv[])
{
    // abcdefg1234567
    // 1.读写方式打开文件
    int fd;
    if (-1 == (fd = open("./1.txt", O_RDWR)))
        PRINT_ERR("open error");

    // 2.文件开头向后偏移5个字节
    lseek(fd, 5, SEEK_SET);
    char ch;
    read(fd, &ch, 1);
    printf("ch = %c\n", ch);  //读取到第6个字符,f

    ch = 'Q';
    write(fd, &ch, 1);  //写入到第7个字符,abcdefQ1234567

    close(fd);
    return 0;
}

3.3lseek函数实例2

如果使用读写和追加的方式打开文件,即使lseek偏移了文件指针,写指针永远是在结尾。

#include <head.h>

int main(int argc, const char *argv[])
{
    // abcdefg1234567
    // 1.以读写和追加的方式打开文件
    int fd;
    if (-1 == (fd = open("./1.txt", O_RDWR | O_APPEND | O_CREAT, 0666)))
        PRINT_ERR("open error");

    // 2.文件开头向后偏移5个字节
    lseek(fd, 5, SEEK_SET);
    char ch;
    read(fd, &ch, 1);
    printf("ch = %c\n", ch);  //读取到第6个字符,f

    ch = 'T';
    write(fd, &ch, 1);  //T字符写到了文件的结尾,写永远是在结尾

    int ret;
    ch = 0;
    ret = read(fd, &ch, 1);  //光标已经在结尾了,这里读是读取不到内容的,read返回0(end of file)
    printf("ch = %c, ret = %d\n", ch, ret);  //ch = , ret = 0

    close(fd); 
    return 0;
}

4.stat(获取文件属性)

4.1stat函数API

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

int stat(const char *pathname, struct stat *statbuf);
//stat不能获取软连接文件,如果向测试软链接,使用lstat完成,用法和stat完全相同
功能:获取文件的属性信息
参数:
    @pathname:文件的路径及名字
 	@statbuf:获取的属性信息结构指针
         struct stat {
           dev_t     st_dev;         //磁盘设备号,底层驱动课程讲解
           ino_t     st_ino;         //文件的inode号,文件系统识别文件的唯一编号,通过ls -i查看
           mode_t    st_mode;        //文件的类型和权限
           
			//1.文件的类型 文件的类型占4bit位,12-15就是文件的类型
               S_IFMT     0170000   bit mask for the file type bit field
               S_IFSOCK   0140000   socket           //套接字文件
               S_IFLNK    0120000   symbolic link    //软连接文件
               S_IFREG    0100000   regular file     //普通文件
               S_IFBLK    0060000   block device     //块设备文件
               S_IFDIR    0040000   directory        //目录
               S_IFCHR    0020000   character device //字符设备文件
               S_IFIFO    0010000   FIFO             //管道
               eg:      
                   if((st_mode & S_IFMT) == S_IFREG){
                       printf("普通文件\n");
                   } 
			//2.文件的权限 文件的权限占9bit位,0-8就是文件的权限 
             文件的权限 = st_mode & 0777
            
           nlink_t   st_nlink;       //硬链接数
           uid_t     st_uid;         //用户的uid
           gid_t     st_gid;         //用户的gid
           dev_t     st_rdev;        //设备号,底层驱动课程讲解
           off_t     st_size;        //文件的大小,单位是字节
           blksize_t st_blksize;     //操作块设备的最小单位block,512字节
           blkcnt_t  st_blocks;      //文件的大小,单位是block
           struct timespec st_atim;  //最后一次访问这个文件的时间
           struct timespec st_mtim;  //最后一次修改这个文件的时间
           struct timespec st_ctim;  //最后一次状态改变的时间
       };
返回值:成功返回0,失败返回-1置位错误码

4.2根据uid获取用户名,getpwuid函数

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

struct passwd *getpwuid(uid_t uid);
功能:根据uid获取用户信息结构体
参数:
    @uid:用户的id
返回值:成功返回用户信息结构指针,失败返回NULL,置位错误码
       struct passwd {
           char   *pw_name;       //用户名
           char   *pw_passwd;     //用户的密码
           uid_t   pw_uid;        //uid
           gid_t   pw_gid;        //gid
           char   *pw_gecos;      //用户的信息
           char   *pw_dir;        //用户的主目录
           char   *pw_shell;      //shell程序
       };

4.3根据gid获取组名,getgrgid函数

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

struct group *getgrgid(gid_t gid);
功能:根据gid获取group结构体
参数:
    @gid:组号
返回值:成功返回结构体指针,失败返回NULL置位错误码
   struct group {
       char   *gr_name;        //组名
       char   *gr_passwd;      //组的密码
       gid_t   gr_gid;         //组号
   };

4.4stat函数实例1(实现 ls -l)

#include <head.h>

void print_type(mode_t mode);
void print_permission(mode_t mode);
int print_time(struct timespec time);

int main(int argc, const char *argv[])
{
    // 1.校验参数输入是否正确
    if (2 != argc) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        return -1;
    }

    // 2.获取文件的属性
    struct stat st;
    if (stat(argv[1], &st))
        PRINT_ERR("stat get msg failed");

    // 3.将属性打印出来
    print_type(st.st_mode);  //打印文件类型
    print_permission(st.st_mode);  //打印文件权限
    printf(" %ld", st.st_nlink);  //打印硬链接个数
    printf(" %s", getpwuid(st.st_uid)->pw_name);  //打印用户名
    printf(" %s", getgrgid(st.st_gid)->gr_name);  //打印组名
    printf(" %ld", st.st_size);  //打印文件大小(字节)
    print_time(st.st_atim);  //打印时间戳
    printf(" %s", argv[1]);  //打印文件名
	puts("");

    return 0;
}

// 打印文件类型
void print_type(mode_t mode) {
    switch (mode & S_IFMT) {
        case S_IFSOCK:
            putchar('s'); break;
        case S_IFLNK:
            putchar('l'); break;
        case S_IFREG:
            putchar('-'); break;
        case S_IFBLK:
            putchar('b'); break;
        case S_IFDIR:
            putchar('d'); break;
        case S_IFCHR:
            putchar('c'); break;
        case S_IFIFO:
            putchar('p'); break;
    }
}

// 打印文件权限
void print_permission(mode_t mode) {
    for (int n = 8; n >= 0; n--) {
        if ((mode & 0777) & (1 << n)) {
	        switch (n % 3) {
		        case 2:
			        putchar('r'); break;
			    case 1:
			        putchar('w'); break;
			    case 0:
			        putchar('x'); break;
		}
	} else {
        putchar('-');
      }
   }
}

// 打印文件时间戳
int print_time(struct timespec time) {
    struct tm* tm;

    if (NULL == (tm = localtime(&time.tv_sec)))
        PRINT_ERR("change time error");
    
    printf(" %02d月 %2d %02d:%02d", \
        tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值