文件IO与目录操作

文件IO

什么是文件IO

概念

又称系统IO,是系统调用,是操作系统提供的接口函数。

posix中定义的一组用于输入输出的函数。

POSIX接口 (英语:Portable Operating System Interface)可移植操作系统接口

 特点:

  1. 没有缓冲机制,每次都会引起系统调用
  2. 围绕文件描述符进行操作,非负整数(>=0),依次分配
  3. 文件IO默认打开三个文件描述符,分别是0(标准输入)、1(标准输出)和2(标准错误)
  4. 操作除了目录d以外类型的文件b c - l s p
  5. 可移植性相对较差

 问题:

打开三个文件描述符分别是:3 4 5 关闭4以后,重新打开这个文件,描述符是几?

        答: 还是4

一个进程的文件描述符最大到几?最多能打开多少个文件描述符?最多能打开多少个文件?

        一个进程的文件描述符最大到1023 (0-1023)

        最多能打开1024个文件描述符

        最多能打开1021(3-1024)的文件

 操作:

打开文件:open

关闭文件:close

读写操作:read和write

定位操作:lseek

 函数接口

打开文件open()

man 2 open

O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_TRUNC、O_APPEND

w: O_WRONLY | O_CREAT | O_TRUNC

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

int open(const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
     flags:打开文件的方式
            O_RDONLY:只读
            O_WRONLY:只写
            O_RDWR:可读可写
            O_CREAT:不存在创建
            O_TRUNC:存在清空
            O_APPEND:追加   
返回值:成功:文件描述符
       失败:-1

当第二个参数中有O_CREAT选项时,需要给open函数传递第三个参数,指定创建文件的权限 
int open(const char *pathname, int flags, mode_t mode);
最后权限 = mode &(~umask)  
例如:指定权限为0666(8进制)
最终权限= 0666 & (~umask) = 0666 &(~0002) = 664

 666  => 110 110 110
&775     111 111 101
 664 	  110 110 100

 文件IO和标准IO的打方式与对应关系:

标准IO

文件IO

r

O_RDONLY

只读

r+

O_RDWR

可读可写

w

O_RDONLY | O_CREAT | O_TRUNC ,0777

只写,不存在创建、存在则清空

w+

O_RDWR|O_CREAT | O_TRUNC ,0777

可读可写,不存在创建,存在清空

a

O_WRONLY | O_CREAT | O_APPEND, 0777

只写,不存在创建,存在追加

a+

O_RDWR | O_CREAT | O_APPEND, 0777

可读可写,不存在创建,存在追加

注意:O_CREAT需要指定第三个参数表示权限


关闭文件close()

#include <unistd.h>
int close(int fd);
功能:关闭文件
参数:fd:文件描述符
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int fd;
    //1.打开文件
    //fd = open("a.c", O_RDONLY); //r
    fd = open("a.c", O_WRONLY | O_CREAT | O_TRUNC, 0666); //w
    if (fd < 0)
    {
        perror("open err ");
        return -1;
    }
    printf("fd: %d\n", fd);

    //2.关闭文件
    close(fd);
    return 0;
}

读写文件

读文件read()

ssize_t read(int fd, void *buf, size_t count);
功能:从一个已打开的可读文件中读取数据
参数: fd  文件描述符
      buf  存放位置
      count  期望的个数
返回值:成功:实际读到的个数(小于期望值说明实际没这么多)
       返回0:表示读到文件结尾
       返回-1:表示出错,并设置errno号

fgetc -> EOF 末尾或失败

fgets -> NULL 末尾或失败

fread -> 0 末尾或失败

read -> 0末尾 -1失败

写文件write()

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向指定文件描述符中,写入 count个字节的数据。
参数:fd   文件描述符
      buf   要写的内容
      count  期望写入字节数
返回值:成功:实际写入数据的个数
              失败  : -1
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int fd;
    char buf[32] = "";
    //1.打开文件
    fd = open("a.c", O_RDWR); //r+
    if (fd < 0)
    {
        perror("open err ");
        return -1;
    }
    printf("fd: %d\n", fd);

    //2. 读写文件
    read(fd, buf, 10);  //文件中内容为:hello
    printf("%s\n", buf); //hello

    write(fd, "world", 5);

    //3.关闭文件
    close(fd);
    return 0;
}
练习:文件IO实现cp功能。cp 源文件 新文件名

./a.out src dest

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

int main(int argc, char const *argv[])
{
    int fd1, fd2;
    if (argc != 3)
    {
        printf("err: %s <srcfile> <destfile>\n", argv[0]);
        return -1;
    }

    //1.打开两个文件
    fd1 = open(argv[1], O_RDONLY);
    if (fd1 < 0)
    {
        perror("fd1 open err");
        return -1;
    }
    fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0777);
    if (fd2 < 0)
    {
        perror("fd2 open err");
        return -1;
    }

    //2.循环读源文件,只要读到就写入目标
    char buf[32] = "";
    ssize_t n;
    while ((n = read(fd1, buf, 32)) > 0)
        write(fd2, buf, n);
    
    //while (read(fd1, buf, 1) > 0)  //或者
    //    write(fd2, buf, 1);

    //3. 关闭两个文件
    close(fd1);
    close(fd2);

    return 0;
}

文件定位操作:

off_t lseek(int fd, off_t offset, int whence);
功能:设定文件的偏移位置
参数:fd:文件描述符
    offset: 偏移量  
        正数:向文件结尾位置移动
        负数:向文件开始位置
    whence: 相对位置
        SEEK_SET   开始位置
        SEEK_CUR   当前位置
        SEEK_END   结尾位置
补充:和fseek一样其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2.

返回值:成功:文件的当前位置
        失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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

    fd = open("a.c", O_RDWR);
    if (fd < 0)
    {
        perror("open err");
        return -1;
    }

    //离开头后10个
    lseek(fd, 10, SEEK_SET);
    write(fd, "k", 1);

    off_t off = lseek(fd, 0, SEEK_END);
    printf("%ld\n", off);

    return 0;
}
练习:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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

    fd = open("a.c", O_RDWR);
    if (fd < 0)
    {
        perror("open err");
        return -1;
    }

    lseek(fd, 10, SEEK_SET);
    write(fd, "d", 1);

    lseek(fd, 20, SEEK_CUR);
    write(fd, "hello", 5);

    off_t off = lseek(fd, 0, SEEK_END);
    printf("%ld\n", off);

    return 0;
}

文件IO和标准IO总结

标准IO

文件IO

概念

在C库中定义的一组用于输入输出的函数

在posix中定义的一组用于输入输出的函数

特点

  1. 有缓冲机制
  2. 围绕着文件流进行操作,FILE *
  3. 默认打开三个流 stdin|stdout|stderr
  4. 只能操作普通文件
  5. 可移植性较强
  1. 没有缓冲机制
  2. 围绕着文件描述符进行操作,非负整数
  3. 默认打开三个文件描述符:0 1 2
  4. 可以操作除了目录意外任何类型文件
  5. 可移植性相对较弱

函数

打开文件:fopen/freopen

关闭文件:fclose

读文件:fgetc/fgets/fread

写文件:fputc/fputs/fwrite

定位操作:rewind/fseek/ftell

打开文件:open

关闭文件:close

读文件:read

写文件:write

定位操作:lseek

 练习:

实现 head -n 文件名 命令的功能

思路:循环读,读到就累加行数,并且打印到终端,判断是否达到最后一行,达到就退出。

实现“head -n 文件名”命令的功能

例:head -3 test.c -> ./a.out -3 test.c

atoi:"123"->123(整型)

argv[1]:"-3"的首地址

argv[1]+1:"3"的首地址

atoi(argv[1]+1)==>3

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    if (argc != 3)
    {
        printf("err: %s -n <filename>\n", argv[0]); //./a.out -n <filename>
        return -1;
    }

    FILE *fp = fopen(argv[2], "r");
    if (fp == NULL)
    {
        perror("fopen err");
        return -1;
    }

    //获取行数
    int num = atoi(argv[1] + 1); // +1是为了向后偏移一个地址单位把-去掉获取到要打印的行数

    char buf[32] = "";
    int len = 0;

    //循环读,然后数换行,打印到终端,如果是最后一行就退出
    while (fgets(buf, 32, fp) != NULL)
    {
        if (buf[strlen(buf) - 1] == '\n')     //数换行
            len++;

        if (len > num)    //达到行数就退出
            break;

        fputs(buf, stdout); //打印内容到终端
    }
    fclose(fp);
    return 0;
}

 获取文件属性:

stat函数:

man 2 stat

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

int stat(const char *path, struct stat *buf);
功能:获取文件属性
参数:  path:文件路径名
       buf:保存文件属性信息的结构体
返回值:成功:0
      失败:-1

struct stat {
        ino_t     st_ino;     /* inode号 ls -il */     
        mode_t    st_mode;    /* 文件类型和权限 */
        nlink_t   st_nlink;   /* 硬链接数 */
        uid_t     st_uid;     /* 用户ID */
        gid_t     st_gid;     /* 组ID */
        off_t     st_size;    /* 大小 */
        time_t    st_atime;   /* 最后访问时间 */
        time_t    st_mtime;   /* 最后修改时间 */
        time_t    st_ctime;   /* 最后状态改变时间 */
    };

 文件权限和类型需要通过位操作获取:

st_mode 主要包含了 3 部分信息:

a. 15bit ~ 12bit 保存文件类型

b. 11bit ~ 9bit 保存执行文件时设置的信息(不用管)

c. 8bit ~ 0bit 保存文件访问权限

 获取文件类型

S_IFMT是一个掩码,它的值是0170000(注意这里用的是八进制前缀为0,二进制0b001111000000000000), 可以用来把st_mode位与上掩码过滤提取出表示的文件类型的那四位(15bit~12bit位),也就是这四位原样获取其他位清零。

这四位可以表示0b0000~0b1111(八进制表示:001~014)七个值,每个值分别对应不同的文件类型:套接字文件、符号链接文件、普通文件、块设备、目录、字符设备、管道。

判断一个文件是不是普通文件,首先通过掩码S_IFMT把其他无关的部分置0,再与表示普通文件的数值比较,从而判断这是否是一个普通文件:

解释:

也可以用swich:

练习:

完善ls -l (判断文件类型) 有能力实现判断文件权限
 

#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>

int main(int argc, char const *argv[])
{
    struct stat st;
    if (stat(argv[1], &st) < 0)
    {
        perror("stat err!\n");
        return -1;
    }
    // printf("inode:%ld nilnk:%ld size:%ld\n", st.st_ino, st.st_nlink, st.st_size);

    // 判断文件类型
    switch (st.st_mode & __S_IFMT)
    {
    case __S_IFBLK:
        printf("b");
        break;
    case __S_IFCHR:
        printf("c");
        break;
    case __S_IFDIR:
        printf("d");
        break;
    case __S_IFIFO:
        printf("p");
        break;
    case __S_IFLNK:
        printf("l");
        break;
    case __S_IFREG:
        printf("-");
        break;
    case __S_IFSOCK:
        printf("s");
        break;
    default:
        printf("unknown?\n");
        break;
    }
    // 当前用户
    if (st.st_mode & S_IRUSR)
        printf("r");
    else
        printf("-");
    if (st.st_mode & S_IWUSR)
        printf("w");
    else
        printf("-");
    if (st.st_mode & S_IXUSR)
        printf("x");
    else
        printf("-");
    // 组内用户
    if (st.st_mode & S_IRGRP)
        printf("r");
    else
        printf("-");
    if (st.st_mode & S_IWGRP)
        printf("w");
    else
        printf("-");
    if (st.st_mode & S_IXGRP)
        printf("x");
    else
        printf("-");

    if (st.st_mode & S_IROTH)
        printf("r");
    else
        printf("-");
    // 其他用户
    if (st.st_mode & S_IWOTH)
        printf("w");
    else
        printf("-");
    if (st.st_mode & S_IXOTH)
        printf("x");
    else
        printf("-");

    // 链接数
    printf(" %ld", st.st_nlink);

    // 用户名 需要getpwuid()函数
    printf(" %s", getpwuid(st.st_uid)->pw_name);

    // 组名 需要getgrgid()函数
    printf(" %s", getgrgid(st.st_gid)->gr_name);

    // 文件大小
    printf(" %ld", st.st_size);

    // 最后一次修改的时间
    printf(" %.12s", ctime(&st.st_mtime) + 4);

    // 文件名
    printf(" %s\n", argv[1]);
    return 0;
}

用标准IO实现cp功能

#include <stdio.h>
int main(int argc, char const *argv[])
{
    FILE *fp1, *fp2;
    char buf[32];
    if (argc != 3)
    {
        printf("err: %s <srcfile> <destfile>\n", argv[0]);
        return -1;
    }

    fp1 = fopen(argv[1], "r");
    if (NULL == fp1)
    {
        perror("fopen fp1 err");
        return -1;
    }

    fp2 = fopen(argv[2], "w");
    if (NULL == fp2)
    {
        perror("fopen fp2 err");
        return -1;
    }

    while (fgets(buf, 32, fp1) != NULL)
    {
        fputs(buf, fp2);
    }
    // char ch;
    // while ((ch = fgetc(fp1)) != EOF)
    // {
    //     fputc(ch, fp2);
    // }

    fclose(fp1);
    fclose(fp2);

获取文件权限

0-8bit位每一位表示一个权限,所以只需要把这一位位与出来就可以判断是否有这个权限,为1说明有,为0说明没有。

比如判断个人权限是否有可读: st.st_mode&0b000000100000000(八进制:00400)

也就是利用宏: st.st_mode&S_IRUSR

解释:

练习:编程实现ls -l 文件名 功能

getpwuid

getgrgid

localtime或ctime

ctime函数在C库中,头文件为<time.h>

函数原型:

char *ctime (const time_t *__timer)

作用:返回一个表示当地时间的字符串,当地时间是基于参数 timer

格式例如: Wed Aug 29 19:48:54 2018

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>

int main(int argc, char const *argv[])
{
    struct stat st;

    if (stat(argv[1], &st) < 0)
    {
        perror("stat err");
        return -1;
    }

    switch (st.st_mode & S_IFMT)
    {
    case S_IFBLK:
        printf("b");
        break;
    case S_IFCHR:
        printf("c");
        break;
    case S_IFDIR:
        printf("d");
        break;
    case S_IFIFO:
        printf("p");
        break;
    case S_IFLNK:
        printf("l");
        break;
    case S_IFREG:
        printf("-");
        break;
    case S_IFSOCK:
        printf("s");
        break;
    default:
        printf("unknown?\n");
        break;
    }

    //判断文件权限
    //个人权限
    if (st.st_mode & S_IRUSR) //r
        printf("r");
    else
        printf("-");

    if (st.st_mode & S_IWUSR) //w
        printf("w");
    else
        printf("-");

    if (st.st_mode & S_IXUSR) //x
        printf("x");
    else
        printf("-");

    //小组成员
    if (st.st_mode & S_IRGRP) //r
        printf("r");
    else
        printf("-");

    if (st.st_mode & S_IWGRP) //w
        printf("w");
    else
        printf("-");

    if (st.st_mode & S_IXGRP) //x
        printf("x");
    else
        printf("-");

    //其他人
    //个人权限
    if (st.st_mode & S_IROTH) //r
        printf("r");
    else
        printf("-");

    if (st.st_mode & S_IWOTH) //w
        printf("w");
    else
        printf("-");

    if (st.st_mode & S_IXOTH) //x
        printf("x");
    else
        printf("-");

    //链接数
    printf(" %ld", st.st_nlink);

    //用户名 需要getpwuid()
    printf(" %s", getpwuid(st.st_uid)->pw_name);

    //组名 需要getgrgid()
    printf(" %s", getgrgid(st.st_gid)->gr_name);

    //文件大小
    printf(" %ld", st.st_size);

    //最后修改的时间
    printf(" %.12s", ctime(&st.st_mtime) + 4);  //+4表示偏移4个地址跳过前4个字符, %.12s表示只打印前12个字符

    //文件名
    printf(" %s\n", argv[1]);

    return 0;
}

stat/fstat/lstat的区别

stat函数返回一个与此命名文件有关的信息结构

fstat函数获得已在描述符filedes上打开的文件的有关信息,也就是参数是文件描述符,其他与stat相同。

lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息。

目录操作

围绕目录流进行操作:DIR*

opendir

closedir

readdir

chdir

DIR *opendir(const char *name);
功能:获得目录流
参数:要打开的目录的路径
返回值:成功:目录流
       失败:NULL

struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功:读到的信息    
       失败:NULL
返回值为结构体,该结构体成员为描述该目录下的文件信息

struct dirent {
        ino_t   d_ino;               /* 索引节点号*/
        off_t   d_off;               /*在目录文件中的偏移*/
        unsigned short d_reclen;     /* 文件名长度*/
        unsigned char  d_type;       /* 文件类型 */
        char    d_name[256];         /* 文件名 */
};

int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录流
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
    DIR *dir;
    struct dirent *d;

    dir = opendir(".");
    if (NULL == dir)
    {
        perror("opendir err");
        return -1;
    }

    // d = readdir(dir);
    // printf("%s\n", d->d_name);

    // d = readdir(dir);
    // printf("%s\n", d->d_name);

    //实现ls -a 功能 (打印指定目录所有文件名就可以了)
    while ((d = readdir(dir)) != NULL)
    {
        printf("%s\n", d->d_name);
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值