Skip to content

文件系统操作函数

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

c
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

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

perror和errno

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

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

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

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

int main() {
    //打开一个不存在的文件
    int fd = open("a.txt", O_RDWR);
    if (fd < 0) {
        //使用perror打印错误信息
        perror("open faild");
        return -1;
    }
    close(fd);

    return 0;
}

文件操作函数

open

打开或者新建一个文件

  • 函数原型:

    c
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    
    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,表示该文件的访问权限。可以使用 0xxx 来指定,比如 0755,不能不写 0 ,否则权限可能不如预期。
        • 文件最终权限:mode & ~umask
      • O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
      • O_TRUNC 如果文件已存在,将其长度截断为为0字节。
      • O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(NonblockI/O),非阻塞I/O。

函数返回值:

  • 成功:返回一个最小且未被占用的文件描述符
  • 失败:返回-1,并设置errno

查看在线 manual: Debian

close

关闭文件

函数原型

c
#include <unistd.h>
int close(int fd);

函数参数

  • fd文件描述符

函数返回值:

  • 成功返回0
  • 失败返回-1,并设置errno

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

read

从打开的设备或文件中读取数据

函数原型

c
#include <unistd.h>
ssize_t read(int fd, void* buf, size_t count);

函数参数:

  • fd:文件描述符
  • buf:读上来的数据保存在缓冲区buf中
  • count:buf缓冲区存放的最大字节数

函数返回值:

  • >0:读取到的字节数
  • =0:文件读取完毕
  • -1: 出错,并设置errno

write

向打开的设备或文件中写数据

函数原型

c
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);

函数参数

  • fd:文件描述符
  • buf:缓冲区,要写入文件或设备的数据
  • countbuf中数据的长度

函数返回值:

  • 成功:返回写入的字节数
  • 错误:返回-1并设置errno

lseek

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

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

  • 移动文件指针

函数原型

c
#include <sys/types.h>
#include <unistd.h>
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函数常用操作

  • 文件指针移动到头部
c
lseek(fd, 0, SEEK_SET);
  • 获取文件指针当前位置
c
int len = lseek(fd, 0, SEEK_CUR);
  • 获取文件长度
c
int len = lseek(fd, 0, SEEK_END);
  • lseek实现文件拓展
c
off_t currpos;
// 从文件尾部开始向后拓展1000个字节
currpos = lseek(fd, 1000, SEEK_END);
// 额外执行一次写操作,否则文件无法完成拓展
write(fd, “a”, 1);// 数据随便写

练习:

1、编写简单的IO函数读写文件的代码
c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(void) {
    //int open(const char *pathname, int flags, mode_t mode);
    int fd = open("log.txt", O_RDWR | O_CREAT, 0777);

    //ssize_t write(int fd, const void *buf, size_t count);
    write(fd, "hello world!", strlen("hello world!"));

    lseek(fd, 0, SEEK_SET);

    //ssize_t read(int fd, void *buf, size_t count);
    char buf[1024];
    memset(buf, 0x00, sizeof(buf));
    int ret = read(fd, buf, sizeof(buf));

    printf("ret=[%d],buf=[%s]\n", ret, buf);
    close(fd);

    return 0;
}
2、使用lseek函数获取文件大小
c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/002_lseek_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("Open error");
        return -1;
    }
    char content[] = "这是Linux系统编程部分IO-lseek函数的测试文档\n用来测试lseek函数读取文件大小";
    write(fd, content, strlen(content));

    lseek(fd, 0, SEEK_SET);
    int len = lseek(fd, 0, SEEK_END);
    printf("文件大小是:[%d]\n", len);
    close(fd);

    return 0;
}
3、使用lseek函数实现文件拓展
c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/003_lseek_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("Open error");
        return -1;
    }
    char content[] = "这是Linux系统编程部分IO-lseek函数的测试文档\n用来测试lseek函数实现文件扩展";
    write(fd, content, strlen(content));

    lseek(fd, 0, SEEK_SET);
    int len = lseek(fd, 0, SEEK_END);
    printf("文件大小是:[%d]\n", len);

    off_t curr;
    curr = lseek(fd, 999, SEEK_END);
    write(fd, "\n", 1);
    lseek(fd, 0, SEEK_SET);
    len = lseek(fd, 0, SEEK_END);
    printf("扩展后文件大小是:[%d]\n", len);
    close(fd);

    return 0;
}

stat/lstat

获取文件属性

Windows平台参考手册: _stat, _wstat功能,Linux平台参考手册: man/lstat.2

函数原型

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

int stat(const char* pathname, struct stat* statbuf);
int fstat(int fd, struct stat* statbuf);
int lstat(const char* pathname, struct stat* statbuf);

函数返回值

  • 成功返回 0
  • 失败返回 -1

struct stat结构体

c
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位整数,其中:

  • 0-2 bit -- 其他人权限
    c
    S_IROTH      00004  //读权限
    S_IWOTH      00002  //写权限
    S_IXOTH      00001  //执行权限
    S_IRWXO      00007  //掩码,过滤 st_mode中除其他人权限以外的信息
  • 3-5 bit -- 所属组权限
    c
    S_IRGRP     00040   //读权限
    S_IWGRP     00020   //写权限
    S_IXGRP     00010   //执行权限
    S_IRWXG     00070   //掩码,过滤 st_mode中除所属组权限以外的信息
  • 6-8 bit -- 文件所有者权限
    c
    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 -- 文件类型
    c
    S_IFSOCK    0140000    //套接字
    S_IFLNK     0120000    //符号链接(软链接)
    S_IFREG     0100000    //普通文件
    S_IFBLK     0060000    //块设备
    S_IFDIR     0040000    //目录
    S_IFCHR     0020000    //字符设备
    S_IFIFO     0010000    //管道
    S_IFMT      0170000    //掩码,过滤 st_mode中除文件类型以外的信息
    if ((st_mode & S_IFMT)==S_IFREG)    //为真普通文件
    if(S_ISREG(st_mode))    //为真表示普通文件
    if(S_ISDIR(st.st_mode))    //为真表示目录文件

stat和lstat的区别

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

文件权限计算方法

shell
mode & ~umask

练习

1、stat函数获取文件大小
c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/004_stat_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("Open faild");
        return -1;
    }
    char content[] = "这是Linux系统编程部分IO-stat函数的测试文档\n用来测试stat函数获取文件大小";
    write(fd, content, strlen(content));
    struct stat st;
    stat("./src/004_stat_test.txt", &st);
    printf("size=[%d]\n", (int) st.st_size);
    close(fd);

    return 0;
}
2、stat函数获取文件类型和文件权限
c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/005_stat_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("Open faild");
        return -1;
    }
    char content[] = "这是Linux系统编程部分IO-stat函数的测试文档\n用来测试stat函数获取文件类型和权限";
    write(fd, content, strlen(content));
    struct stat st;
    stat("./src/005_stat_test.txt", &st);
    if (S_ISDIR(st.st_mode)) printf("目录文件");
    else if (S_ISREG(st.st_mode))
        printf("普通文件");

    printf("\t其他人权限:[");
    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("]\n");

    close(fd);

    return 0;
}
3、lstat函数获取链接文件的属性(文件大小)
c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/006_lstat_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("Open faild");
        return -1;
    }
    char content[] = "这是Linux系统编程部分IO-stat函数的测试文档\n用来测试stat函数获取文件类型和权限";
    write(fd, content, strlen(content));
    struct stat st;
    // int ret = stat("./src/006_lstat_test.txt.s", &st);
    int ret = lstat("./src/006_lstat_test.txt.s", &st);
    if (ret < 0) {
        perror("Open faild(lstat)");
        return -1;
    }

    if (S_ISDIR(st.st_mode))
        printf("目录文件");
    else if (S_ISLNK(st.st_mode))
        printf("链接文件");
    else if (S_ISREG(st.st_mode))
        printf("普通文件");

    printf("\tsize:[%ld]\t其他人权限:[", st.st_size);
    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("]\n");

    close(fd);

    return 0;
}

dup

函数描述

  • 复制文件描述符

函数原型

c
#include <unistd.h>

int dup(int oldfd);

函数参数

  • oldfd -要复制的文件描述符

函数返回值

  • 成功:返回最小且没被占用的文件描述符
  • 失败:返回-1,设置errno值

练习:编写程序,测试dup函数。
c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/007_dup_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("fd open faild");
        return -1;
    }

    int newfd = dup(fd);
    if (newfd < 0) {
        perror("newfd open faild");
        return -1;
    }

    char content[] = "hello newfd~\nAre you ok?";
    // write(fd, content, sizeof(content));
    write(fd, content, strlen(content) + 1);
    close(fd);

    lseek(newfd, 0, SEEK_SET);
char buf[1024];
    memset(buf, 0x00, sizeof(buf));
    int ret = read(newfd, buf, sizeof(buf));
    printf("ret=[%d],buf=[%s]\n", ret, buf);

    return 0;
}

dup2

函数描述

  • 复制文件描述符

函数原型

c
#include <unistd.h>

int dup2(int oldfd, int newfd);

函数参数

  • oldfd-原来的文件描述符
  • newfd-复制成的新的文件描述符

函数返回值

  • 成功:将oldfd复制给newfd,两个文件描述符指向同一个文件
  • 失败:返回-1,设置errno值
  • 假设newfd已经指向了一个文件,首先close原来打开的文件,然后newfd指向oldfd指向的文件。

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

练习

1、编写程序,测试dup2函数实现文件描述符的复制
c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/008_dup2_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("fd open faild");
        return -1;
    }

    int newfd;
    dup2(fd, newfd);
    if (newfd < 0) {
        perror("newfd open faild");
        return -1;
    }

    char content[] = "hello newfd~\nAre you ok?";
    // write(fd, content, sizeof(content));
    write(fd, content, strlen(content) + 1);
    close(fd);

    lseek(newfd, 0, SEEK_SET);
char buf[1024];
    memset(buf, 0x00, sizeof(buf));
    int ret = read(newfd, buf, sizeof(buf));
    printf("ret=[%d],buf=[%s]\n", ret, buf);

    return 0;
}
2、编写程序,完成终端标准输出重定向到文件中
c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/009_dup2_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("fd open faild");
        return -1;
    }

    int ret = dup2(fd, STDOUT_FILENO);
    if (ret < 0) {
        perror("dup2 running faild");
        return -1;
    }
    printf("dup2 test\n");

    return 0;
}

fcntl

函数描述

  • 改变已经打开的文件的属性

函数原型

c
#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */);

函数参数

  • cmdF_DUPFD,复制文件描述符,与dup相同
  • cmdF_GETFL,获取文件描述符的flag属性值
  • cmdF_SETFL,设置文件描述符的flag属性

函数返回值

  • 返回值取决于cmd
  • 成功
    • cmdF_DUPFD,返回一个新的文件描述符
    • cmdF_GETFL,返回文件描述符的flags值
    • cmdF_SETFL,返回0
  • 失败返回-1,并设置errno值。

fcntl函数常用的操作

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

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

2、获取文件的属性标志

c
int flag = fcntl(fd, F_GETFL, 0);

3、设置文件状态标志

c
int flag = fcntl(fd, F_GETFL, 0);
flag = flag | O_APPEND;
fcntl(fd, F_SETFL, flag)

4、常用的属性标志

c
O_APPEND    //设置文件打开为末尾添加
O_NONBLOCK    //设置打开的文件描述符为非阻塞

练习

1、使用fcntl函数实现复制文件描述符
c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/010_fcntl_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("open faild");
        return -1;
    }

    int newfd = fcntl(fd, F_DUPFD, 0);
    if (newfd < 0) {
        perror("fcntl faild");
        return -1;
    }

    char content[] = "测试使用fcntl函数进行文件描述符的复制\n";
    write(fd, content, sizeof(content));
    close(fd);

    lseek(newfd, 0, SEEK_SET);
    char buf[1024];
    memset(buf, 0x00, sizeof(buf));
    int ret = read(newfd, buf, sizeof(buf));
    printf("ret=[%d],buf=[%s]\n", ret, buf);
    close(newfd);

    return 0;
}
2、使用fcntl函数设置在打开的文件末尾添加内容。
c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd = open("./src/011_fcntl_test.txt", O_RDWR | O_CREAT, 0755);
    if (fd < 0) {
        perror("open faild");
        return -1;
    }

    char content[] = "测试使用fcntl函数进行在文件末尾添加内容\n";
    write(fd, content, sizeof(content));

    lseek(fd, 0, SEEK_SET);

    int flags = fcntl(fd, F_GETFL, 0);
    flags |= O_APPEND;
    fcntl(fd, F_SETFL, flags);

    char content1[] = "这是追加的内容\n";
    write(fd, content1, sizeof(content1));

    close(fd);

    return 0;
}

目录操作函数

opendir

函数描述

打开一个目录

函数原型

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

DIR* opendir(const char* name);
DIR* fdopendir(int fd);

函数返回值

  • 指向目录的指针

函数参数

  • 要遍历的目录(相对路径或者绝对路径)

readdir

函数描述

读取目录内容--目录项

函数原型

c
#include <dirent.h>
struct dirent* readdir(DIR* dirp);

函数返回值

  • 成功:读取的目录项指针
  • 失败或到结尾:NULL

函数参数

  • opendir函数的返回值
c
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

函数描述

关闭目录

函数原型

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

int closedir(DIR* dirp);

函数返回值

  • 成功返回0,失败返回-1

函数参数

  • opendir函数的返回值

读取目录内容的一般步骤

c
//1 打开目录
DIR * pDir = opendir(“dir”);
//2 循环读取文件
while ((p = readdir(pDir)) != NULL) {}
//3 关闭目录 
closedir(pDir);

练习

1、遍历指定目录下的所有文件,并判断文件类型。
c
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>

int main() {
    struct dirent *pdir;
    DIR *pDIR = opendir("./src");
    while ((pdir = readdir(pDIR)) != NULL) {
        if (strcmp(pdir->d_name, ".") == 0 || strcmp(pdir->d_name, "..") == 0) {
            continue;
        }
        printf("文件名:[%s]", pdir->d_name);
        switch (pdir->d_type) {
            case DT_BLK:
                printf("\t-块设备文件\n");
                break;
            case DT_CHR:
                printf("\t-字符设备文件\n");
                break;
            case DT_FIFO:
                printf("\t-管道文件\n");
                break;
            case DT_DIR:
                printf("\t-目录文件\n");
                break;
            case DT_LNK:
                printf("\t-软链接文件\n");
                break;
            case DT_REG:
                printf("\t-普通文件\n");
                break;
            case DT_SOCK:
                printf("\t-套接字文件\n");
                break;
            case DT_UNKNOWN:
                printf("\t-未知文件\n");
                break;
            default:
                break;
        }
    }

    closedir(pDIR);
    return 0;
}
2、递归遍历目录下所有的文件, 并判断文件类型

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

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>

void printDirType(const char *path, int level);

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("run as ./main path\n");
        return -1;
    }

    printDirType(argv[1], 0);

    return 0;
}

void printDirType(const char *path, int level)
{
    DIR *dir = opendir(path);
    char name[256];
    struct dirent *p = NULL;
    while ((p = readdir(dir)) != NULL)
    {
        memset(name, 0x0, sizeof(name));
        sprintf(name, "%s", p->d_name);

        if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
        {
            continue;
        }

        for (int i = 0; i < level; ++i)
        {
            printf("    ");
        }

        printf("%s\t", name);

        switch (p->d_type)
        {
        case DT_BLK:
            printf("块设备\n");
            break;
        case DT_CHR:
            printf("字符设备\n");
            break;
        case DT_DIR:
            printf("目录\n");

            // 拼接新路径并递归调用
            char subName[1024] = {0};
            strcpy(subName, path);
            subName[strlen(subName)] = '/';
            strcat(subName, name);
            printDirType(subName, level + 1);
            
            break;
        case DT_LNK:
            printf("软链接\n");
            break;
        case DT_FIFO:
            printf("管道\n");
            break;
        case DT_REG:
            printf("普通文件\n");
            break;
        case DT_SOCK:
            printf("套接字\n");
            break;
        case DT_UNKNOWN:
            printf("未知\n");
            break;

        default:
            break;
        }
    }

    return;
}
3、实现ls命令

studyC.h 存储在/usr/include/目录中

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

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

// 校验程序运行参数
#define ARGS_CHECK(argc, num)                                                  \
  {                                                                            \
    if (argc != num) {                                                         \
      fprintf(stderr, "args error\n");                                         \
      return -1;                                                               \
    }                                                                          \
  }

// 出错校验
#define ERROR_CHECK(ret, num, msg)                                             \
  {                                                                            \
    if (ret == num) {                                                          \
      perror(msg);                                                             \
      return -1;                                                               \
    }                                                                          \
  }
myLs.c
#include <studyC.h>

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

    DIR *dir = opendir(argv[1]);
    ERROR_CHECK(dir, NULL, "opendir");

    struct dirent *dirNode;
    while ((dirNode = readdir(dir)) != NULL) {
        if (strcmp(dirNode->d_name, ".") == 0 || strcmp(dirNode->d_name, "..") == 0) {
            continue;
        }
        printf("%s ", dirNode->d_name);
    }
    puts("");

    closedir(dir);

    return 0;
}
4、实现ls -l命令
c
#include <studyC.h>

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

    DIR *dir = opendir(argv[1]);
    ERROR_CHECK(dir, NULL, "opendir");

    // 切换工作目录到输入的目录
    chdir(argv[1]);

    struct dirent *dirNode;
    struct stat fileNode;
    char fileInfo[1024] = {0};
    struct tm *fileTime;

    while ((dirNode = readdir(dir)) != NULL) {
        /*
        // 跳过.和..目录
        if (strcmp(dirNode->d_name, ".") == 0 || strcmp(dirNode->d_name, "..") == 0) {
            continue;
        }
        */

        int lstatRet = lstat(dirNode->d_name, &fileNode);
        ERROR_CHECK(lstatRet, -1, "lstat");

        /*
        // 使用if判断文件类型
        if ((fileNode.st_mode & S_IFMT) == S_IFSOCK) {
            printf("s ");
        } else if ((fileNode.st_mode & S_IFMT) == S_IFLNK) {
            printf("l ");
        } else if ((fileNode.st_mode & S_IFMT) == S_IFREG) {
            printf("- ");
        } else if ((fileNode.st_mode & S_IFMT) == S_IFBLK) {
            printf("b ");
        } else if ((fileNode.st_mode & S_IFMT) == S_IFDIR) {
            printf("d ");
        } else if ((fileNode.st_mode & S_IFMT) == S_IFCHR) {
            printf("c ");
        } else if ((fileNode.st_mode & S_IFMT) == S_IFIFO) {
            printf("f ");
        } else if ((fileNode.st_mode & S_IFMT) == S_IFMT) {
            printf("? ");
        }
        */

        // 使用switch判断文件类型
        switch (fileNode.st_mode & S_IFMT) {
            case S_IFSOCK:
                printf("s");
                break;
            case S_IFLNK:
                printf("l");
                break;
            case S_IFREG:
                printf("-");
                break;
            case S_IFBLK:
                printf("b");
                break;
            case S_IFDIR:
                printf("d");
                break;
            case S_IFCHR:
                printf("c");
                break;
            case S_IFIFO:
                printf("f");
                break;
            case S_IFMT:
                printf("?");
        }


        // 判断拥有者读权限
        if ((fileNode.st_mode & S_IRUSR) != 0) {
            printf("r");
        } else {
            printf("-");
        }
        // 判断拥有者写权限
        if ((fileNode.st_mode & S_IWUSR) != 0) {
            printf("w");
        } else {
            printf("-");
        }
        // 判断拥有者执行权限
        if ((fileNode.st_mode & S_IXUSR) != 0) {
            printf("x");
        } else {
            printf("-");
        }

        // 判断文件所属组用户对文件的操作权限
        if ((fileNode.st_mode & S_IRGRP) != 0) {
            printf("r");
        } else {
            printf("-");
        }
        if ((fileNode.st_mode & S_IWGRP) != 0) {
            printf("w");
        } else {
            printf("-");
        }
        if ((fileNode.st_mode & S_IXGRP) != 0) {
            printf("x");
        } else {
            printf("-");
        }

        // 判断其他人对文件的操作权限
        if ((fileNode.st_mode & S_IROTH) != 0) {
            printf("r");
        } else {
            printf("-");
        }
        if ((fileNode.st_mode & S_IWOTH) != 0) {
            printf("w");
        } else {
            printf("-");
        }
        if ((fileNode.st_mode & S_IXOTH) != 0) {
            printf("x");
        } else {
            printf("-");
        }

        fileTime = localtime(&fileNode.st_mtime);

        printf(" %ld %s %s %8ld %d%d %02d:%02d %s\n", fileNode.st_nlink, getpwuid(fileNode.st_uid)->pw_name, getgrgid(fileNode.st_gid)->gr_name, fileNode.st_size, fileTime->tm_mon + 1, fileTime->tm_mday, fileTime->tm_hour, fileTime->tm_min, dirNode->d_name);
    }

    closedir(dir);

    return 0;
}

补充说明

使用stat获取文件信息的时间无法准确获取链接文件的文件类型信息,需要使用lstat