C语言文件操作

底层文件访问

文件IO的4个函数:open、read、write、close。

当一个程序开始运行时,它一般会有3个已经打开的文件描述符:

  • 0:标准输入
  • 1:标准输出
  • 2:标准错误

open——打开或创建一个文件

#include <fcntl.h>
// 返回值:若成功,则返回文件描述符;若出错,则返回-1
int open(const char *path, int oflag, .../* mode_t mode*/);

功能描述:打开或创建一个文件。

参数描述:

  • path:要打开或创建的文件的名字。
  • oflag:打开文件的方式。oflag常见的取值如下:
oflag功能
O_RDONLY以只读方式打开
O_WRONLY以只写方式打开
O_RDWR以读写方式打开
O_EXEC以只执行的方式打开
O_SEARCH以只搜索的方式打开(适用于目录)

对于上面的5个常量,必须指定且只能指定一个,而下面的常量则是可选的。

oflag功能
O_APPEND每次写入时都追加到文件的末尾
O_CREAT若文件不存在,则创建文件
O_EXCL如果同时指定了O_CREAT且文件已经存在,则报错
O_TRUNC如果文件已存在,而且以只写或读写的方式打开,则将其长度截断为0。(会把已经存在的内容给删除)
  • mode:创建文件的权限

**返回值:**返回值:若成功,则返回文件描述符;若出错,则返回-1。返回的文件描述符是最小的未使用的描述符编号。

注意:

  • 最后一个参数被写成…表示余下的参数数量及类型是可变的。对于open函数来说,只有创建新文件时才会使用最后的这个参数。
  • open函数创建文件是的权限是: mode & (~umask)

举例

1、用open函数实现touch命令行的功能。

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

int main(int argc, char *argv[])
{
    int fd;
    fd = open(argv[1], O_CREAT | O_RDWR, 0777);
    if (fd < 0)
    {
        printf("create file %s failed\n", argv[1]);
        return -1;
    }
    printf("create file %s success,fd=%d\n", argv[1], fd);
    close(fd);
    return 0;
}

运行结果:

[zld@localhost codes]$ ./touch test1.txt
create file test1.txt success,fd=3
[zld@localhost codes]$ ls -l
-rwxrwxr-x. 1 zld zld    0 Jul 27 03:45 test1.txt
[zld@localhost codes]$ umask
0002

在上面创建文件时,mode=0777,而umask=0002,因此,最终test1.txt文件创建的权限为mode & (~umask)=

B111 111 111 & ~(B000 000 010) = B111 111 101,因此是rwxrwxr-x。

close——关闭一个文件

#include <unistd.h>

// 返回值:若成功,返回0;否则,返回-1
int close(int fd);

功能描述:关闭一个打开的文件。

注意:

  • 当一个进程终止时,它打开的所有文件将由内核自动关闭。

write——向打开的文件写入数据

#include <unistd.h>
// 返回值:若成功,则返回已写入的字节数;若出错,则返回-1
ssize_t write(int fd, const void *buf, size_t nbytes);

**功能描述:**向打开的文件写入数据。

参数描述:

  • fd:要写入的文件的文件描述符
  • buf:待写入的内容
  • nbytes:请求写入的字节数

返回值:

若成功,则返回已写入的字节数;若出错,则返回-1,错误码保存在全局变量errno里。

注意:

  • write函数出错的一个常见原因是磁盘空间已满,或者超过了给定进程的文件长度的限制。
  • 对于普通文件,写操作是从文件的当前偏移量开始的。如果在打开该文件时就指定了O_APPEND选项,那么在每次写操作之前,都将文件偏移设置于当前文件的末尾。在成功写入之后,该文件偏移量会增加,增加的值为实际写入的字节数。

举例:

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

int main(int argc, char *argv[])
{
    int fd;
    char buf[] = "Hello World!"; // 待写入的内容
    int write_ret;               // write函数的返回值

    // 打开已存在的a.c文件,打开的方式为以读写的方式打开,且覆盖已存在的内容
    fd = open("./a.c", O_RDWR | O_TRUNC, 0777);
    if (fd < 0)
    {
        printf("open file a.c failed\n");
        return -1;
    }
    printf("open file a.c success,fd=%d\n", fd);
    // 向文件中写入数据
    write_ret = write(fd, buf, sizeof(buf));
    printf("write_ret=%d\n", write_ret);
    close(fd);
    return 0;
}

运行结果:

[zld@localhost 0728]$ cat a.c       // 初始时,a.c中没有内容
[zld@localhost 0728]$ ./write       // 执行上面的程序
open file a.c success,fd=3
write_ret=13
[zld@localhost 0728]$ cat a.c       // 再次查看文件,写入内容成功
Hello World![zld@localhost 0728]$ 
[zld@localhost 0728]$ 

read——从打开的文件中读数据

#include <unistd.h>
// 返回值:若读取成功,则返回读到的字节数;若已到文件末尾,则返回0;若出错,则返回-1
ssize_t read(int fd, void *buf, size_t nbytes);

**功能描述:**从打开的文件中读取数据。

参数描述:

  • fd:要读取的文件的文件描述符
  • buf:读取出来的数据要保存的缓冲区地址
  • nbytes:请求读取出来的字节数

返回值:

如果read函数读取成功,则返回读到的字节数;如果已达到文件的末尾,则返回0。若出错,则返回-1。

举例:

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

int main(int argc, char *argv[])
{
    int fd;
    char buf[] = "Hello World!";
    char read_buf[128] = {0};
    int write_ret;
    int read_ret;
    fd = open("./a.c", O_RDWR | O_TRUNC, 0777);
    if (fd < 0)
    {
        printf("open file a.c failed\n");
        return -1;
    }
    printf("open file a.c success,fd=%d\n", fd);
    write_ret = write(fd, buf, sizeof(buf));
    printf("write_ret=%d\n", write_ret);
    read_ret = read(fd, read_buf, 128);
    printf("read_ret=%d, read_buf:%s\n", read_ret, read_buf);
    close(fd);
    return 0;
}

运行结果:

[zld@localhost 0728]$ gcc -o read read.c     
[zld@localhost 0728]$ ./read 
open file a.c success,fd=3
write_ret=13
read_ret=0,read_buf=               // read不符合预期
[zld@localhost 0728]$ 

从上面的运行结果上看,读取内容不符合预期。原因是在write写入之后,文件偏移在文件的末尾了,所以read读取的时候,是从文件的末尾开始往后读取的,因此读取的内容不符合预期。需要用到lseek函数,修改文件的偏移指针。

lseek——设置打开文件的偏移量

#include <unistd.h>
// 返回值:若成功,则返回新建文件的偏移量;否则,返回-1
off_t lseek(int fd, off_t offset, int whence);

**功能描述:**显示地设置打开文件的偏移量。

参数描述:

对参数offset的解释取决于参数whence的值。

  • 如果whence的值为SEEK_SET,则文件的偏移量被设置为距文件开头offset字节的位置。
  • 如果whence的值为SEEK_CUR,则文件的偏移量被设置为其当前值加上offset。offset可以是正数,也可以是负数。
  • 如果whence的值为SEEK_END,则文件的偏移量被设置为文件的长度加上offset。offset可以是正数,也可以是负数。

**返回值:**若成功,则返回新建文件的偏移量;否则,返回-1。

举例:

在上面的read中,读取内容不是预期的,现在加上lseek再次运行:

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

int main(int argc, char *argv[])
{
    int fd;
    char buf[] = "Hello World!";
    char read_buf[128] = {0};
    int write_ret;
    int read_ret;
    off_t pos;
    fd = open("./a.c", O_RDWR | O_TRUNC, 0777);
    if (fd < 0)
    {
        printf("open file a.c failed\n");
        return -1;
    }
    printf("open file a.c success,fd=%d\n", fd);
    write_ret = write(fd, buf, sizeof(buf));
    printf("write_ret=%d\n", write_ret);
    pos = lseek(fd, 0, SEEK_SET); // 将偏移量移到开头位置
    printf("pos=%d\n", pos);
    read_ret = read(fd, read_buf, 128);
    printf("read_ret=%d, read_buf:%s\n", read_ret, read_buf);
    close(fd);
    return 0;
}

运行结果如下,读取成功

[zld@localhost 0728]$ ./lseek 
open file a.c success,fd=3
write_ret=13
pos=0
read_ret=13, read_buf:Hello World!
[zld@localhost 0728]$ 

fstat、stat和lstat

#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);

fstat返回与打开的文件描述符相关的文件的状态信息,该信息会写到一个buf结构中。stat和lstat返回的是通过文件名查到的状态信息。它们产生相同的结果,但是当文件是一个符号链接时,lstat返回的是该符号链接本身的信息,而stat返回的是该链接指向的文件的信息。

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

int main(int argc, char *argv[])
{
    struct stat buf;
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <path>\n", argv[0]);
        return -1;
    }
    if (lstat(argv[1], &buf) == -1)
    {
        perror("lstat");
        return -2;
    }
    if (S_ISLNK(buf.st_mode))
    {
        printf("%s is a symbolic link.\n", argv[1]);
    }
    else if (S_ISDIR(buf.st_mode))
    {
        printf("%s is a directory.\n", argv[1]);
    }
    else
    {
        printf("%s is not a directory or symbolic link.\n", argv[1]);
    }
    return 0;
}

运行结果:

[zld@localhost 0729]$ ./lstats /usr/local/
/usr/local/ is a directory.

标准IO

与文件IO的区别:

文件IO:是直接调用内核提供的系统调用函数,头文件是unistd.h

标准IO:是间接调用系统调用函数,头文件是stdlib.h

三个缓存的概念(数组)

1、我们的程序中的缓存,就是你想从内核读写的缓存(数组)——用户空间的缓存

2、每打开一个文件,内核在内核空间中也会开辟一块缓存,这个叫内核空间的缓存。

​ 文件IO中的写即是将用户空间中的缓存写到内核空间的缓存中。

​ 文件IO中的读即是将内核空间的缓存写到用户空间中的缓存中。

3、标准IO的库函数中也有一个缓存,这个缓存称为库缓存。

对比以下两个代码:

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

int main(int argc, char *argv[])
{
    int fd;
    char buf[] = "Hello World!";
    write(1, buf, sizeof(buf));  // 第一次执行这个
    //printf("%s", buf);    
    while (1);
    return 0;
}


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

int main(int argc, char *argv[])
{
    int fd;
    char buf[] = "Hello World!";
    // write(1, buf, sizeof(buf));
    printf("%s", buf);  // 第二次执行这个  
    while (1);
    return 0;
}

输出结果:

// 第一次
[zld@localhost 0728]$ ./write2 
Hello World!

// 第二次
[zld@localhost 0728]$ ./write2 

从上面的结果上看,第一次有输出到屏幕上,而第二次没有输出到屏幕上,这是因为write函数是直接写入内核缓存的,而printf是先写入库缓存的,待满足一定的条件后,才会将库缓存中的内容写到内核中。

printf需要满足以下条件:

  • 遇到\n时,会将库缓存的内容写到内存中,即调用了系统调用函数。

    将上面的第一段代码中,修改一下,便能输出到屏幕上了:

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    int main(int argc, char *argv[])
    {
        int fd;
        char buf[] = "Hello World!\n"; // 这里加上一个\n
        // write(1, buf, sizeof(buf));
        printf("%s", buf);
        while (1);
        return 0;
    }
    

    运行结果:

    [zld@localhost 0728]$ ./write2               
    Hello World!
    
    
  • 库缓存写满时,会调用系统调用函数,将库缓存内容写到内核缓存中去。

标准IO中,提供了和底层文件访问的接口,如下:

底层文件访问标准IO
openfopen
closefclose
lseekfseek、rewind
read读写函数比较多(分三类,全缓存、行缓存和无缓存)
write读写函数比较多(分三类,全缓存、行缓存和无缓存)

fopen——打开流

#include <stdio.h>
// 返回值:若成功,则返回文件指针;若出错,则返回NULL
FILE *fopen(const char *restric pathname, const char *restrict type);

**功能描述:**类似于底层的open函数,用于打开指定的文件。

参数描述:

type参数指定了15种不同的值,如下表所示:

type说明open标识
r或rb打开只读文件,该文件必须存在O_RDONLY
w或wb打开只写文件。若文件存在则文件长度清为0,即会擦除文件以前的内容。若文件不存在则建立该文件O_WRONLY|O_CREAT|O_TRUNC
a或ab追加:为在文件末尾开始写入而打开,或者为写入而创建O_WRONLY|O_CREAT|O_APPEND
r+或r+b或rb+为读取和写入而打开,该文件必须存在O_RDWR
w+或w+b或wb+将文件截断为0的长度,或者为读取和写入而创建O_RDWR|O_CREAT|O_TRUNC
a+或a+b或ab+为在文件末尾开始读取或者写入打开或者创建O_RDWR|O_CREAT|O_APPEND
  • b:二进制文件
  • r:只读方式打开文件,文件必须存在
  • w或a:只写方式打开文件,文件不存在则创建。区别:w等价O_TRUNC;a等价O_APPEND
  • r+:读写方式打开文件,文件必须存在。

举例:

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;
    fp = fopen(argv[1], "w+");
    if (fp == NULL)
    {
        printf("create file %s failed\n", argv[1]);
        return -1;
    }
    printf("create file %s success\n", argv[1]);
    fclose(fp);
    return 0;
}

运行结果:

[zld@localhost 0728]$ ./fopen1 zld.c
create file zld.c success

fclose——关闭流

#include <stdio.h>

// 返回值:若成功,则返回0;若出错,则返回EOF
int fclose(FILE *fp);

在该文件被关闭之前,冲洗缓冲中的输出数据。缓冲区中的任何输入数据都将被丢弃。如果标准I/O库自动为流分配了一个缓冲区,则释放此缓冲区。

当进程正常终止时,无论是直接调用exit函数,还是从main函数返回,所有带未写缓冲数据的标准I/O流都会被冲洗,所有打开的标准I/O流都会被关闭。

读写函数

三类读写函数:

  • 第一类:行缓存。即遇到换行符(\n)或者写满缓存时,即调用系统调用函数。
    • 读:fgets,gets,printf,fprintf,sprintf
    • 写:fputs,puts,scanf
  • 第二类:无缓存。即只要用户调用这个函数,就会将其内容写到内核中
  • 第三类:全缓存。即只有写满缓存再调用系统调用函数。
    • 读:fread
    • 写:fwrite

行缓存的读写函数fgets和fputs

#include <stdio.h>
// 返回值:若成功,则返回buf;若到文件末尾或者出错,则返回NULL
char *fgets(char *restrict buf, int n, FILE *restrict fp);

// 返回值:若成功,则返回非负值;若出错,则返回EOF
int fputs(const char *restrict str, FILE *restrict stream);

功能描述:

  • fgets函数从fp指向的流中读取字符,直到遇到换行符(\n)、文件结束符(EOF)或已读取了n-1个字符为止。然后,它会在字符串的末尾添加一个空字符\0以表示字符串的结束。如果读取的字符串长度小于n-1个字符,并且遇到了换行符,换行符也会被读取并存储在字符串中。

  • fputs函数将字符串str写入到由stream指定的文件或流中,直到遇到字符串的结尾标志\0为止。但需要注意的是,fputs并不会自动在字符串末尾添加换行符\n,如果需要换行,必须在字符串中显示地包含它。成功写入字符串后,

参数说明:

  • buf:指向一个字符数组的指针,该数组用于存储从流中读取的字符串;

  • n:指定要读取的最大字符数(包括最后的\0)。因此,实际能存储的最大字符数是n-1。如果输入的字符串长度超过n-1个字符,fgets会截断字符串,只存储前n-1个字符,并在末尾添加\0。剩余的字符(包括换行符)将留在输入缓冲区中,可能会影响到后续的输入操作;

  • fp:指向FILE对象的指针。该FILE对象标识了要从中读取字符的流。这通常是一个文件流或标准输入流stdin。

  • str:指向呀写入文件的字符串的指针。该字符串以空字符\0结尾。

  • stream:指向FILE对象的指针,该FILE对象标识了要写入字符串的目标文件或流。它可以是文件流,也可以是标准输出流stdout。

  • 注意:还有两个函数gets、puts的功能类似于fgets和fputs。但还是建议不要使用这两个函数。

举例:

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;
    char buf[] = "Hello linux";
    char readBuf[128] = {0};
    fp = fopen("./a.c", "w+");
    if (fp == NULL)
    {
        printf("open file a.c failed\n");
        return -1;
    }
    printf("open file a.c succes\n");
    fputs(buf, fp);
    fgets(readBuf, 128, fp);
    printf("readBuf=%s\n", readBuf);
    fclose(fp);
    return 0;
}

输出:

[zld@localhost 0728]$ ./fgets 
open file a.c succes
readBuf=

从上面的输出可以看出来,fgets读取的结果不符合预期,readBuf中没有内容,原因和之前说的一样,在fputs之后,文件指针指向了末尾,因此再fgets函数的时候,就读取失败了。

全缓存的读写函数fread和fwrite

#include <stdio.h>

size_t fread(void *restrict ptr, size_t size, size_t nboj, FILE *restrict fp);

size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);

// 两个函数的返回值:读/写的对象个数

参数描述:

fread:

  • ptr:指向void的指针,表示读取的数据将存储在这个位置。
  • size:每个数据项的大小,以字节为单位;
  • nobj:要读取的数据项的数量
  • fp:指向FILE对象的指针,该对象标识了要被读取的文件流。

fwrite:

  • ptr:指向要写入文件的数据的指针。
  • size:每个数据项的字节大小。
  • nobj:要写入的数据项的数量
  • fp:指向FILE对象的指针,该对象标识了要被写入数据的文件流。

fgetc、getc和getchar函数

// 返回值:若正确,则返回读取的字符;若到文件结尾或出错时,则返回EOF
int fgetc(FILE *fp);

// 返回值:若成功,则返回输入的字符;出错,则返回EOF
int fputc(int c, FILE *fp);

int getchar();

**功能描述:**fgetc:从文件中读取一个字符。fputc:写一个字符到文件中。getchar函数的作用相当于getc(stdin),它从标准输入读取下一个字符。

fputc、putc和putchar函数

#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

**功能描述:**fputc函数把一个字符写到一个输出文件流中。它返回写入的值,如果失败,则返回EOF。putc的作用相当于fputc,但它可能被实现为一个宏。putchar函数相当于putc(c, stdout)。

刷新缓存函数:fflush()

#include <stdio.h>

// 返回值:若成功,返回0;若出错,返回EOF
int fflush(FILE *fp);

**功能描述:**fflush函数将流中所有未写的数据都被传送至内核。

ftell、fseek、rewind:定位流

#include <stdio.h>
// 返回值:若成功,则返回当前文件的位置指示符;若出错,则返回-1L
long ftell(FILE *fp);

// 返回值:若成功,则返回0;若出错,则返回-1
int fseek(FILE *fp, long offset, int whence);

void rewind(FILE *fp);

功能描述:

  • ftell:用于获取文件位置指针当前位置相对于文件首的偏移字节数。
  • fseek:用于重新定位文件内的读写位置指针。
  • rewind:用于将文件内部的位置指针重新指向文件的开头。等价于(void)fseek(fp,0, SEEK_SET)。

使用场景:

  • ftell
    • 1、确定文件当前读写位置:在读写文件时,可以使用tell来获取当前的读写位置,从而了解已经处理了多少数据。
    • 2、获取文件大小:通过将文件指针移动到文件末尾(使用fseek(stream,0,SEEK_END)),然后调用ftell,可以获取文件的总大小(以字节为单位)。

格式化IO

格式化输出

#include <stdio.h>
// 返回值:若成功,则输出字符数;若输出错误,则输出负值
int printf(const char*restrict format, ...);

// 返回值:若成功,则输出字符数;若输出错误,则输出负值
int fprintf(FILE *restrict fp, const char *restrict format, ...);

// 返回值:若成功,则输出字符数;若输出错误,则输出负值
int dprintf(int fd, const char *restrict format, ...);

// 返回值:若成功,则返回数组中存储的字符数;若编码错误,则返回负值
int sprintf(char *restrict buf, const char *restrict format, ...);

// 返回值:若缓冲区足够大,则返回存储在数组中的字符数;若编码错误,则返回负值
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);

**功能描述:**printf函数将数据写入标准输出,fprintf写入指定流,dprintf写入指定文件描述符,sprintf将格式化字符存放到数组buf中,sprintf函数自动在数组末尾添加一个null字节,但该null字节不包含在返回值中。sprintf可能会导致buf指向的缓冲区溢出,可能导致安全性问题。所以引入了snprintf。

格式化输入

#include <stdio.h>
int scanf(const char *restrict format, ...);

int fscanf(FILE *restrict fp, const char *restrict format, ...);

int sscanf(const char *restrict buf, const char *restrict format, ...);

// 三个函数的返回值:赋值的输入项的个数;若输入错误或者转换前已经到达文件末尾,则返回EOF

feof、ferror、clearerr

// 功能:判断是否已到文件结束。
// 返回值:到文件结束,返回为非0,没有则返回0.
int feof(FILE *stream);

// 功能:判断是否读写错误
// 返回值:是读写错误,返回为非0;没有则范湖0
int ferror(FILE *stream);

// 功能:清除流错误
void clearerr(FILE *stream);

目录文件

mkdir、rmdir——创建/删除目录

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

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

mkdir系统调用用于创建目录,它相当于mkdir程序。mkdir调用将参数path作为新建目录的名字。目录的权限由参数mode设定(当然,最终权限还是和umask有关)

#include <unistd.h>
int rmdir(const char *path);

rmdir系统调用用于删除目录,但只有在目录为空时才行。rmdir程序就是用这个系统调用来完成工作的。

chdir、getcwd——切换目录/显示当前目录

#include <unistd.h>
int chdir(const char *path);

chdir的作用是切换目录(像cd命令一样)。

#include <unistd.h>
char *getcwd(char *buf, size_t size);

getcwd的作用是获取当前的工作目录。将当前目录的名字写到给定的缓存buf中,如果目录名的长度超过了参数size给出的缓冲区长度,它就返回NULL。如果成功,则返回buf。

opendir——打开目录

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

DIR *opendir(const char *name);

opendir函数的作用是打开一个目录并建立一个目录流。如果成功,返回一个指向DIR结构的指针。如果失败,则返回一个空指针。

readdir——读取目录

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

struct dirent *readdir(DIR *dirp);

readdir函数返回一个指针,该指针指向的结构里保存着目录流dirp中下一个目录项的有关信息。后续的readdir调用将返回后续的目录项。如果发生错误或者到达目录尾,readdir将返回NULL。

dirent结构中至少包含的目录项内容如下:

  • ino_t d_ino:文件的inode节点号
  • char d_name[]:文件的名字

closedir——关闭目录

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

int closedir(DIR *dirp);

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

telldir、seekdir——获取/重置目录的目录项指针

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

long int telldir(DIR *dirp);

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

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

void seekdir(DIR *dirp, long int loc);

seekdir函数的作用是设置目录流dirp的目录项指针。loc的值用来设置指针位置,它通过telldir调用获取。

举例

下面的程序实现的是一个简单的目录列表功能。目录中的每个文件单独列在一行上,每个字目录会在它的名字后面加上一个斜杠/,子目录中的文件再缩进四个空格后依次排列。

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

/* 输出当前目录的内容,它将递归遍历各级子目录,使用depth参数控制缩进 */
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))
        {
            /* 跳过 . 和 .. 目录 */
            if (strcmp(".", entry->d_name) == 0 ||
                strcmp("..", entry->d_name) == 0)
            {
                continue;
            }
            printf("%*s%s/\n", depth, " ", entry->d_name);
            /* 如果是一个目录,则进行递归调用 */
            printdir(entry->d_name, depth + 4);
        }
        else
        {
            /* 如果不是目录,则根据depth的值缩进打印该文件数据项内容 */
            printf("%*s%s\n", depth, " ", entry->d_name);
        }
    }
    /* 一旦while循环完成,调用chdir("..")把它带回到目录的上一级 */
    chdir("..");
    closedir(dp);
}
int main(int argc, char *argv[])
{
    char *topodir = ".";
    if (argc >= 2)
    {
        topodir = argv[1];
    }
    printf("Directory scan of %s\n", topodir);
    printdir(topodir, 0);
    printf("done.\n");
    return 0;
}

运行结果:

[zld@localhost 0729]$ ./printdir /usr/local/ | more
Directory scan of /usr/local/
 redis-5.0.9.tar.gz
 bin/
    redis-sentinel
    redis-cli
    redis-benchmark
    redis-check-rdb
    redis-check-aof
    redis-server
 sbin/
 redis-5.0.9/
    00-RELEASENOTES
    COPYING
    deps/
        hiredis/
            CHANGELOG.md
            COPYING
            hiredis.h
            appveyor.yml
            fmacros.h
            async.c
            examples/
                example-glib.c
                example-libev.c
                example-qt.h
                example.c
                example-libuv.c
                example-ae.c
                example-ivykis.c
                example-qt.cpp
                example-macosx.c
                example-libevent.c
            dict.h
            .travis.yml
            read.c
            adapters/
                macosx.h
                libuv.h
                libevent.h
                qt.h
                libev.h
  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值