底层文件访问
文件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 |
---|---|
open | fopen |
close | fclose |
lseek | fseek、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