八、Linux编程之递归遍历目录
一、步骤分析
实现终端指令 ls -R
,即将所有内部的目录最终展开成文件,目录的目录…所有目录最终展开,并打印相应文件的大小
步骤:
1. 判断命令行参数,获取用户要查询的目录名,通过 int argc, char *argv[]
获取,若 argc == 1
,那么用户想要查询的是当前目录 ./
2. 判断用户指定的路径是否为一个目录,通过 stat()
和宏函数 S_ISDIR()
判断
3. 如果是一个目录,打开、读取、关闭:opendir()
、readdir()
、closedir()
,如果是文件,打印
4. 继续判断是否存在目录,通过 stat()
和宏函数 S_ISDIR()
判断,拼接所访问目录的绝对路径:通过函数 sprintf()
或 strcut()
,获得原路径/内部目录,回到步骤 3
二、isFile()函数
先定义一个函数 void isFile(char *name){...}
,用于判断是目录还是普通文件,如果是普通文件,就直接打印文件名和文件大小,如果是目录文件就需要进一步操作
void isFile(char *name);
// 判断文件类型
void isFile(char *name)
{
struct stat sbuf;
int ret = stat(name, &sbuf);
// 出错判断
if(ret == -1)
{
perror("stat error");
exit(1);
}
// 宏函数判断
if(S_ISDIR(sbuf.st_mode))
{
isDir(name); // 如果是一个目录,就需要进行目录相关操作
}else
{
printf("%s\t\t%ld\n", name, sbuf.st_size); // 如果是普通文件,就直接打印
}
}
三、isDir()函数
定义函数 void isDir(char *name){...}
,当所判断的文件是目录时,就需要调用该函数,进一步操作和判断,如:打开、读取、关闭目录,并判断目录内是否还有目录
由于 readdir()
读取后只有文件名,只有文件名没有路径无法正常访问文件,我们需要将路径拼接到它的前面,调用 sprintf()
函数,这个函数与 printf()
类似,只不过 printf()
是默认打印到 stdin 文件上,而 sprintf()
打印到一个字符串上
包含头文件:
#include <stdio.h>
int sprintf(char *str, const char *format, …);
将格式化输出内容打印到 char *str
上
char *str
传出参数
const char *format
类似 printf()
中的格式化输出,例:sprintf(out, "%s是%s", a, b);
返回值
成功时返回写入的字符大小
此外,readdir()
函数还会把 .
和 ..
也读出,如下图所示:为了防止重复循环遍历,需要把 .
和 ..
过滤掉
方法如下:
if(strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0)
{
continue;
}
void isDir(char *name){...}
函数代码如下:
void isDir(char *);
// 如果是一个目录,打开目录
void isDir(char *dir)
{
struct dirent *sdp;
// Linux规定路径长度不能超过256
char path[256];
DIR *dp = opendir(dir); // 打开目录
if(dp == NULL)
{
perror("opendir error");
return;
}
while((sdp = readdir(dp)) != NULL) // 读目录
{
// 由于readdir会把 . 和 .. 也读出来,因此需要过滤
if(strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0)
{
continue;
}
// 字符串拼接
sprintf(path, "%s/%s", dir, sdp->d_name);
/*
调用 isFile() 函数,判断是否为普通文件
如果是普通文件,则打印
如果是目录文件,则继续迭代
*/
isFile(path);
}
printf("\n");
int ret = closedir(dp);
if(ret == -1)
{
perror("closedir error");
return;
}
}
四、main()主函数
如果 int argc == 1
说明没有指定目录,默认为当前目录
int main(int argc, char *argv[])
{
if(argc == 1)
{
isFile(".");
}else
{
isFile(argv[1]);
}
return 0;
}
五、运行效果
六、补充要点
注意:上述内容并不完整,因为命令 ls -R
是可以指定多个目录的,因此我们需要将 main()
函数做微调:
int main(int argc, char *argv[])
{
if(argc == 1)
{
isFile(".");
}else
{
while(--argc)
{
isFile(*++argv);
}
}
return 0;
}
其中,while(--argc)
表示每次访问一个目录前就让 argc
减 1,以此来统计用户输入目录的个数
其次,isFile(*++argv);
表示每次执行前先让 argv++
,再取内容,那么每次循环时 isFile(*++argv);
代表的内容分别是:
isFile(argv[1]);
、isFile(argv[2]);
、isFile(argv[3]);
… … 如此类推,这样就可以对每个目录进行判断了
while(--argc)
{
isFile(*++argv);
}
七、源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
// 函数声明
void isDir(char *);
void isFile(char *);
// 如果是一个目录,打开目录
void isDir(char *dir)
{
struct dirent *sdp;
// Linux规定路径长度不能超过256
char path[256];
DIR *dp = opendir(dir); // 打开目录
if(dp == NULL)
{
perror("opendir error");
return;
}
while((sdp = readdir(dp)) != NULL) // 读目录
{
// 由于readdir会把 . 和 .. 也读出来,因此需要过滤
if(strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0)
{
continue;
}
// 字符串拼接
sprintf(path, "%s/%s", dir, sdp->d_name);
/*
调用 isFile() 函数,判断是否为普通文件
如果是普通文件,则打印
如果是目录文件,则继续迭代
*/
isFile(path);
}
printf("\n");
int ret = closedir(dp);
if(ret == -1)
{
perror("closedir error");
return;
}
}
// 判断文件类型
void isFile(char *name)
{
struct stat sbuf;
int ret = stat(name, &sbuf); // 获取文件属性,判断文件类型
// 出错判断
if(ret == -1)
{
perror("stat error");
exit(1);
}
// 宏函数判断是否为目录
if(S_ISDIR(sbuf.st_mode))
{
isDir(name); // 如果是一个目录,就需要进行目录相关操作
}else
{
printf("%s\t\t%ld\n", name, sbuf.st_size); // 如果是普通文件,就直接打印
}
}
int main(int argc, char *argv[])
{
// 判断命令行参数
if(argc == 1)
{
isFile(".");
}else
{ // 处理用户输入的一个或多个目录
while(--argc)
{
isFile(*++argv);
}
}
return 0;
}