linux编程 实现ls -R的命令
关于C++语言的基础知识可以点开笔者主页专栏【跟我一起从C到C++】
使用到的知识点:
3.1.1 bool类型
4.1 string
6.2 异常处理
ls
命令,想必大家在使用Linux时,都感觉十分亲切,日常必用命令之一:
它可以罗列文件夹和文件,让人们更加清晰地管理自己的文件。
而ls -R
更是夸张,可以将每个文件夹下的文件(以及文件夹下的文件夹,无限套娃)都列出来:
实现ls
的第一步就是读取目录
读取目录之前,肯定要先打开目录:
DIR * opendir(const char * name);
opendir
用来打开参数name
指定的目录, 并返回DIR*
形态的目录流, 和open
(老朋友了)类似, 接下来对目录的读取和搜索都要使用此返回值。
参阅资料,我们知道了readdir
函数可以实现读取目录的功能,于是我们man readdir
:
readdir
返回值:成功则返回下个目录进入点;有错误发生或读取到目录文件尾则返回NULL
。
dirent
定义如下:
struct dirent
{
ino_t d_ino; //d_ino 此目录进入点的inode
ff_t d_off; //d_off 目录文件开头至此目录进入点的位移
signed short int d_reclen; //d_reclen _name 的长度, 不包含NULL 字符
unsigned char d_type; //d_type d_name 所指的文件类型 d_name 文件名
har d_name[256];
};
dirent
的成员d_name
即是文件/文件夹名称,我们即将用到。
我们先引入头文件
#include
我们编写一个读取目录:
std::string file_name;
cin >> file_name;
DIR *dir;
dir = opendir(file_name.c_str());
if(dir == nullptr)
{
cout << "opendir is NULL" << endl;
return -1;
}
dirent *dp;
while ((dp = readdir(dir)) != nullptr)
{
cout << dp->d_name << endl;
}
closedir(dir);
return 0;
我们再进行一次封装,并用上错误检测和处理:
#include
using std::runtime_error;
DIR *My_OpenDir(const char *path)
{
DIR *dir;
try
{
dir = opendir(path);
if (dir == nullptr)
{
throw runtime_error("opendir error: DIR* is NULL");
}
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
return nullptr;
}
return dir;
}
我们再引入头文件
#include
这样就能用return EXIT_SUCCESS;
表示成功结束
而return EXIT_FAILURE;
表示错误结束
于是:
DIR *dir;
dir = My_OpenDir(file_name.c_str());
if (dir == nullptr) goto fail_opendir;
dirent *dp;
while ((dp = readdir(dir)) != NULL)
{
cout << dp->d_name << endl;
}
closedir(dir);
return EXIT_SUCCESS;
fail_opendir:
return EXIT_FAILURE;
}
getopt
方法是用来分析命令行参数的,该方法由Linux标准库提供,包含在
头文件中。
int getopt(int argc, char * const argv[], const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
getopt
参数说明:
argc
:通常由 main
函数直接传入,表示参数的数量
argv
:通常也由 main
函数直接传入,表示参数的字符串变量数组
optstring
:一个包含正确的参数选项字符串,用于参数的解析。例如 “abc:”
,其中 -a
,-b
就表示两个普通选项,-c
表示一个必须有参数的选项,因为它后面有一个冒号
外部变量说明:
optarg
:如果某个选项有参数,这包含当前选项的参数字符串
optind
:argv
的当前索引值
opterr
:正常运行状态下为 0
。非零时表示存在无效选项或者缺少选项参数,并输出其错误信息
optopt
:当发现无效选项字符时,即 getopt
方法返回 ?
字符,optopt
中包含的就是发现的无效选项字符。
故,我们引入头文件:
#include
使用bool
类型的真假表示某个选项是否被输入:
struct option_t
{
bool R = false;
bool help = false;
};
option_t opt;
编写函数:
int analyseOpt(int argc, char* argv[])
{
try
{
char c;
while((c = getopt(argc, argv, "Rh")) != -1)
{
switch (c)
{
case 'R':
opt.R = true;
break;
case 'h':
opt.help = true;
break;
default:
throw runtime_error("Unknown options:" + std::string(1, optopt));
}
}
}
catch(const std::exception& e)
{
std::cerr << "Error " << e.what() << '\n';
goto fail_opt;
}
return EXIT_SUCCESS;
fail_opt:
return EXIT_FAILURE;
}
于是我们编写我们的main
函数,提前用ls_help()
(一般不会出错,故不添加错误检测和处理,返回值为void
)和ls_R()
占好位置,搭好框架:
#include
#include
#include
#include
#include
using std::runtime_error;
using std::cout; using std::endl;
struct option_t
{
bool R = false;
bool help = false;
};
option_t opt;
int analyseOpt(int argc, char* argv[]);
void ls_help();//还没想好,未定义
int ls_R();//还没想好,未定义,参数也没有添加
DIR *My_OpenDir(const char *path);
int main(int argc, char **argv)
{
std::string file_name;
bool exist_dir = false;//用户是否提供目录
if(analyseOpt(argc, argv) != EXIT_SUCCESS)
goto fail_analyseOpt;
for (size_t i = 1; i < argc; ++i)
{
if(argv[i][0] == '-')
continue;//假如是选项就下一个
exist_dir = true;//发现了目录
file_name = argv[i];//把目录给file_name
}
if (opt.help)
{
ls_help();
return EXIT_SUCCESS;
}
if (!exist_dir)
{
file_name = ".";//没有提供目录,就当前目录
}
if (opt.R)
{
if(ls_R() == EXIT_SUCCESS)
return EXIT_SUCCESS;
else
return EXIT_FAILURE;
}
DIR *dir;
dir = My_OpenDir(file_name.c_str());
if (dir == nullptr) goto fail_opendir;
dirent *dp;
while ((dp = readdir(dir)) != NULL)
{
cout << dp->d_name << endl;
}
closedir(dir);
return EXIT_SUCCESS;
fail_opendir:
fail_analyseOpt:
return EXIT_FAILURE;
}
DIR *My_OpenDir(const char *path)
{
DIR *dir;
try
{
dir = opendir(path);
if (dir == nullptr)
{
throw runtime_error("opendir error: DIR* is NULL");
}
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
return nullptr;
}
return dir;
}
int analyseOpt(int argc, char* argv[])
{
try
{
char c;
while((c = getopt(argc, argv, "Rh")) != -1)
{
switch (c)
{
case 'R':
opt.R = true;
break;
case 'h':
opt.help = true;
break;
default:
throw runtime_error("Unknown options:" + std::string(1, optopt));
}
}
}
catch(const std::exception& e)
{
std::cerr << "Error " << e.what() << '\n';
goto fail_opt;
}
return EXIT_SUCCESS;
fail_opt:
return EXIT_FAILURE;
}
因为-h
提供帮助信息这里比较简单,故可以里面完成:
void ls_help()
{
cout << "help:\n"
<< "-h help" << "\n"
<< "-r list dir" << endl;
}
我们可以编写一个一直检测当前目录path
的函数,它第一次先遍历整个目录流并展示名字,第二次则开始判断目录流的类型,如果是文件就跳过,如果是文件夹name
就修改路径为path/name
,并递归调用函数。
查阅资料得知stat
能获取与文件系统及文件相关的许多信息,位于
头文件的它拥有结构:
struct stat
{
dev_t st_dev; /* ID of device containing file */文件使用的设备号
ino_t st_ino; /* inode number */ 索引节点号
mode_t st_mode; /* protection */ 文件对应的模式,文件,目录等
nlink_t st_nlink; /* number of hard links */ 文件的硬连接数
uid_t st_uid; /* user ID of owner */ 所有者用户识别号
gid_t st_gid; /* group ID of owner */ 组识别号
dev_t st_rdev; /* device ID (if special file) */ 设备文件的设备号
off_t st_size; /* total size, in bytes */ 以字节为单位的文件容量
blksize_t st_blksize; /* blocksize for file system I/O */ 包含该文件的磁盘块的大小
blkcnt_t st_blocks; /* number of 512B blocks allocated */ 该文件所占的磁盘块
time_t st_atime; /* time of last access */ 最后一次访问该文件的时间
time_t st_mtime; /* time of last modification */ /最后一次修改该文件的时间
time_t st_ctime; /* time of last status change */ 最后一次改变该文件状态的时间
};
我们用stat
函数或者lstat
函数来获取linux操作系统下文件的属性。
stat
函数和lstat
函数的区别:
这里就主要介绍一下stat
:
函数原型: int stat(const char *pathname,struct stat *buf);
参数:第一个参数为传入参数,pathname
为文件的绝对路径或相对路径。第二参数为传出参数,一个struct stat
类型的结构体指针。传出参数可以采用下边两种方法,定义结构体变量struct stat st
,或定义结构体指针变量strut stat *st = &st
(注意这里一定要进行初始化,说明其为一块有效的内存空间),相对而言,使用结构体变量更为方便。
参考:
linux系统调用函数 lstat–获取文件属性
stat
结构中的st_mode
则定义了下列情况:(可以使用类似if (s.st_mode & S_IXUSR)
这样的方式)
st_mode的情况:
S_IFMT 0170000 文件类型的位遮罩
S_IFSOCK 0140000 套接字
S_IFLNK 0120000 符号连接
S_IFREG 0100000 一般文件
S_IFBLK 0060000 区块装置
S_IFDIR 0040000 目录
S_IFCHR 0020000 字符装置
S_IFIFO 0010000 先进先出
S_ISUID 04000 文件的(set user-id on execution)位
S_ISGID 02000 文件的(set group-id on execution)位
S_ISVTX 01000 文件的sticky位
S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限
S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限
S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
S_IRGRP 00040 用户组具可读取权限
S_IWGRP 00020 用户组具可写入权限
S_IXGRP 00010 用户组具可执行权限
S_IROTH 00004 其他用户具可读取权限
S_IWOTH 00002 其他用户具可写入权限
S_IXOTH 00001 其他用户具可执行权限
上述的文件类型在POSIX中定义了检查这些类型的宏定义:
S_ISLNK (st_mode) 判断是否为符号连接
S_ISREG (st_mode) 是否为一般文件
S_ISDIR (st_mode) 是否为目录
S_ISCHR (st_mode) 是否为字符装置文件
S_ISBLK (s3e) 是否为先进先出
S_ISSOCK (st_mode) 是否为socket
若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名,在linux中,最典型的就是这个/tmp目录啦。
注意,在linux操作系统下,一切皆文件。文件共有七种类型,分别是普通文件、目录文件、管道文件、可执行文件、压缩文件、设备文件(字符、管道和块)和其他文件。
参见:
Linux系统函数之文件系统管理(二)
而我们主要使用S_ISDIR()
,判断其是否为目录文件:
int ls_R(std::string path)
{
//普通的罗列目录
cout << path + ":"<<endl;
DIR *dir;
dir = My_OpenDir(path.c_str());
if (dir == nullptr) goto fail_Myopendir1;
dirent *dp;
while ((dp = readdir(dir)) != NULL)
{
if (dp->d_name[0] == '.')
continue;
cout << dp->d_name << endl;
}
closedir(dir);
//检查是否为目录,是就更改路径,递归调用
dir = My_OpenDir(path.c_str());
if (dir == nullptr) goto fail_Myopendir2;
while ((dp = readdir(dir)) != NULL)
{
if (dp->d_name[0] == '.')
continue;
struct stat file_stat;
std::string temp;
temp = path + "/" + dp->d_name;//更改路径
try
{
if (stat(temp.c_str(), &file_stat) == -1)
throw runtime_error(temp + ":get file stat error");
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
goto fail_getstat;
}
if(S_ISDIR(file_stat.st_mode))//判断是否为目录,如果是目录就进入递归
{
ls_R(temp);
}
}
closedir(dir);//不要忘记关闭dir
return EXIT_SUCCESS;
fail_getstat:
fail_Myopendir2:
closedir(dir);
fail_Myopendir1:
return EXIT_FAILURE;
}
我们仔细观察ls
命令,发现不同文件类型有不同的颜色,笔者这里就举两个例子来实现:
具体颜色可以参考:
超级终端的字体背景和颜色显示
调配颜色的方法就是:
输出一段咒语:
C语言
printf("\033[32m");//绿色
或C++
cout << "\033[1m\033[32m";
于是我们提前定义好宏:
#define LightGreen {cout << "\033[1m\033[32m" << std::flush;}
#define LightBlue {cout << "\033[1m\033[34m" << std::flush;}
#define CLOSE {cout << "\033[0m" << std::flush;}
接着判断文件类型:
回顾 判断文件类型的方法
if(S_ISDIR(file_stat.st_mode))//判断是否为目录
{
LightBlue;
cout << dp->d_name << " ";
CLOSE;
}
else if (file_stat.st_mode & S_IXUSR)//如果是可执行文件
{
LightGreen;
cout << dp->d_name << " ";
CLOSE;
}
else
cout << dp->d_name << " ";
结尾附上全文以供参考:
#include
#include
#include
#include
#include
#include
#define LightGreen {cout << "\033[1m\033[32m" << std::flush;}
#define LightBlue {cout << "\033[1m\033[34m" << std::flush;}
#define CLOSE {cout << "\033[0m" << std::flush;}
using std::runtime_error;
using std::cout; using std::endl;
struct option_t
{
bool R = false;
bool help = false;
};
option_t opt;
int analyseOpt(int argc, char* argv[]);
void ls_help();
int ls_R(std::string path);
DIR *My_OpenDir(const char *path);
int main(int argc, char **argv)
{
std::string file_name;
bool exist_dir = false;
if(analyseOpt(argc, argv) != EXIT_SUCCESS)
goto fail_analyseOpt;
for (size_t i = 1; i < argc; ++i)
{
if(argv[i][0] == '-')
continue;
exist_dir = true;
file_name = argv[i];
}
if (opt.help)
{
ls_help();
return EXIT_SUCCESS;
}
if (!exist_dir)
{
file_name = ".";
}
if (opt.R)
{
if(ls_R(file_name) == EXIT_SUCCESS)
return EXIT_SUCCESS;
else
return EXIT_FAILURE;
}
DIR *dir;
dir = My_OpenDir(file_name.c_str());
if (dir == nullptr) goto fail_opendir;
dirent *dp;
while ((dp = readdir(dir)) != NULL)
{
if (dp->d_name[0] == '.')
continue;
struct stat file_stat;
std::string temp;
temp = file_name + "/" + dp->d_name;
try
{
if (stat(temp.c_str(), &file_stat) == -1)
throw runtime_error(temp + ":get file stat error");
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
goto fail_getstat;
}
if(S_ISDIR(file_stat.st_mode))//判断是否为目录,如果是目录就进入递归
{
LightBlue;
cout << dp->d_name << " ";
CLOSE;
}
else if (file_stat.st_mode & S_IXUSR)//如果是可执行文件
{
LightGreen;
cout << dp->d_name << " ";
CLOSE;
}
else
cout << dp->d_name << " ";
}
cout << endl;
closedir(dir);
return EXIT_SUCCESS;
fail_getstat:
closedir(dir);
fail_opendir:
fail_analyseOpt:
return EXIT_FAILURE;
}
DIR *My_OpenDir(const char *path)
{
DIR *dir;
try
{
dir = opendir(path);
if (dir == nullptr)
{
throw runtime_error("opendir error: DIR* is NULL");
}
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
return nullptr;
}
return dir;
}
int analyseOpt(int argc, char* argv[])
{
try
{
char c;
while((c = getopt(argc, argv, "Rh")) != -1)
{
switch (c)
{
case 'R':
opt.R = true;
break;
case 'h':
opt.help = true;
break;
default:
throw runtime_error("Unknown options:" + std::string(1, optopt));
}
}
}
catch(const std::exception& e)
{
std::cerr << "Error " << e.what() << '\n';
goto fail_opt;
}
return EXIT_SUCCESS;
fail_opt:
return EXIT_FAILURE;
}
void ls_help()
{
cout << "help:\n"
<< "-h help" << "\n"
<< "-r list dir" << endl;
}
int ls_R(std::string path)
{
cout << path + ":"<<endl;
DIR *dir;
dir = My_OpenDir(path.c_str());
if (dir == nullptr) goto fail_Myopendir1;
dirent *dp;
while ((dp = readdir(dir)) != NULL)
{
if (dp->d_name[0] == '.')
continue;
struct stat file_stat;
std::string temp;
temp = path + "/" + dp->d_name;
try
{
if (stat(temp.c_str(), &file_stat) == -1)
throw runtime_error(temp + ":get file stat error");
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
goto fail_getstat;
}
if(S_ISDIR(file_stat.st_mode))//判断是否为目录,如果是目录就进入递归
{
LightBlue;
cout << dp->d_name << " ";
CLOSE;
}
else if (file_stat.st_mode & S_IXUSR)//如果是可执行文件
{
LightGreen;
cout << dp->d_name << " ";
CLOSE;
}
else
cout << dp->d_name << " ";
}
cout << endl;
closedir(dir);
dir = My_OpenDir(path.c_str());
if (dir == nullptr) goto fail_Myopendir2;
while ((dp = readdir(dir)) != NULL)
{
if (dp->d_name[0] == '.')
continue;
struct stat file_stat;
std::string temp;
temp = path + "/" + dp->d_name;
try
{
if (stat(temp.c_str(), &file_stat) == -1)
throw runtime_error(temp + ":get file stat error");
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
goto fail_getstat;
}
if(S_ISDIR(file_stat.st_mode))//判断是否为目录,如果是目录就进入递归
{
ls_R(temp);
}
}
closedir(dir);
return EXIT_SUCCESS;
fail_getstat:
fail_Myopendir2:
closedir(dir);
fail_Myopendir1:
return EXIT_FAILURE;
}