ls命令简介

老规矩,直接在终端输入:man ls

(有关于man命令的简介可以参考笔者前期博客:Linux系统编程【1】——编写more命令)

可以看到,ls命令的作用是显示目录中的文件名,它带有可选的参数,如'-a'表示显示所有文件(包含隐藏文件,即以'.'开头的文件),'-l'表示显示文件及文件属性等等。

本次博客就只专注于如何显示出目录中的文件名,而显示文件属性这方面的实现将写在下一篇博客中。

如何实现初级版ls命令

既然我们的目的是要显示出目录中的文件,基于Linux文件编程的思想,我们只需找到存放指定目录文件信息的那个文件,然后读取其中的内容并显示就可以了。

根据之前对于more和who命令的实现中:打开文件、读取文件、关闭文件的思路,可以猜测对于目录的处理也可能为:打开目录、读取目录、关闭目录。

确定工具函数

利用man -k dir查找到readdir(3)就是我们想要的(另外的两个readdir(2)和readdir_r(3) man进去看一下描述,确实不是我们需要的)。

由readdir的“SEE ALSO”引出来的还有,opendir和closedir:

这里插一句,我们之前的思路是找到存储目录信息的文件,然后对这个文件内容进行处理。但是笔者发现这些文件不容易找到,好在linux已经给我们提供了处理这种事的工具函数,如这个readdir函数,传入目录指针(由opendir函数获得,而opendir函数仅需传入目录名)就可以获得一个包含所需信息的结构体指针。

这就好比是你要的东西在仓库(存目录相关信息的文件)里,但是仓库的位置(文件路径)你一下找不到,并且里面放有各种东西(各种参数),要自己去挑选(选出自己所需的数据),最后再自己扛出来并关仓库门(格式处理、数据复制、关闭文件等等)。现在好了,有了几个代理人,他们对仓库很熟悉,一下子就能找到仓库位置(opendir函数),然后根据位置拿里面的东西并打包出来交给你(readdir函数),最后还替你关仓库门(closedir函数),多舒服的一件事情。

找到这三个代理人(opendir/readdir/closedir)后,把整套流程交给他们去做,我们拿到打包好的东西(struct dirent)再自己简单处理下就行了。

确定所需参数

readdir函数返回的是一个dirent结构体指针,这个dirent结构体中包含的d_name(文件名)就是我们需要的。

所以整个的ls命令实现流程为:

1.opendir打开指定目录

循环:{

2.readdir获得目录中每一个文件的dirent结构体

3.打印结构体中的d_name字符串

}

4.closedir关闭指定目录

ls命令源代码

/*
* ls01.c
* writed by lularible
* 2021/02/07
*/
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<dirent.h>
#include<string.h> //函数声明
void do_ls(const char*);
void show_ls(struct dirent*);
void error_handle(const char*); int main(int argc,char* argv[])
{
//对输入的命令进行参数判断与处理
if(argc == 1){ //只输入了:ls
do_ls(".");
}
else{
while(--argc){
do_ls(*(++argv));
}
}
return 0;
} //主流程
void do_ls(const char* dir_name)
{
DIR* cur_dir;
struct dirent* cur_item;
//打开目录
if((cur_dir = opendir(dir_name)) == NULL){
error_handle(dir_name);
}
else{
//读取目录并显示信息
while((cur_item = readdir(cur_dir))){
show_ls(cur_item);
}
printf("\n");
//关闭目录
closedir(cur_dir);
}
} //显示文件名
void show_ls(struct dirent* cur_item){
printf("%s",cur_item->d_name);
printf("\n");
} //错误处理
void error_handle(const char* dir_name){
perror(dir_name);
exit(1);
}

增加可选参数"-a"和排序

现在对于ls做一点小小的优化:

  • 1.当不带任何参数时,显示的文件名不包括隐藏文件(以'.'开头的文件名),带上参数"-a"时,才把隐藏的文件名显示出来

  • 2.将文件名先按照字典序排序再显示

针对第一点,可以对输入的参数进行判断,依据判断结果进行不同的操作,这个很容易实现。

针对第二点,将文件名存到数组中,写一个字典序排序算法,对数组中文件名进行排序即可。

源代码

/*
* ls02.c
* writed by lularible
* 2021/02/07
*/ #include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<dirent.h>
#include<string.h>
#include</home/lularible/bin/sort.h> //字典序排序算法 //函数声明
void do_ls(const char*);
void restored_ls(struct dirent*);
void error_handle(const char*); //全局变量
int has_a = 0; //标记是否带'-a'参数
char *filenames[4096]; //存放文件名
int file_cnt = 0; //目录中文件个数 int main(int argc,char* argv[])
{
//对输入的命令进行参数判断与处理
if(argc == 1){ //只输入了:ls
do_ls(".");
}
else{
if(strcmp(argv[1],"-a") == 0){
has_a = 1;
--argc;
++argv;
}
if(argc == 1){
do_ls(".");
}
else{
while(--argc){
do_ls(*(++argv));
}
}
}
return 0;
} void do_ls(const char* dir_name)
{
DIR* cur_dir;
struct dirent* cur_item;
//打开目录
if((cur_dir = opendir(dir_name)) == NULL){
error_handle(dir_name);
}
else{
//读取目录并显示信息
//将文件名存入数组
while((cur_item = readdir(cur_dir))){
restored_ls(cur_item);
}
//字典序排序
sort(filenames,0,file_cnt-1);
//输出结果
int i = 0;
for(i = 0;i < file_cnt;++i){
printf("%s\n",filenames[i]);
}
//关闭目录
closedir(cur_dir);
}
} void restored_ls(struct dirent* cur_item){
char* result = cur_item->d_name;
//当不带-a参数时,隐藏以'.'开头的文件
if(!has_a && *result == '.') return;
filenames[file_cnt++] = cur_item->d_name;
} void error_handle(const char* dir_name){
perror(dir_name);
exit(1);
}

其中的字典序排序算法如下:(利用快排的思想)

/*
* sort.h
* writed by lularible
* 2021/02/07
*/ #include<stdio.h>
#include<stdlib.h>
#include<string.h> void swap(char** s1,char** s2);
int compare(char* s1,char* s2);
int partition(char** filenames,int start,int end);
void sort(char** filenames,int start,int end); //交换两字符串
void swap(char** s1,char** s2)
{
char* tmp = *s1;
*s1 = *s2;
*s2 = tmp;
} //比较两字符串的字典序
//s1靠前,返回负数,s1靠后,返回正数
//s1和s2完全一样,返回0
int compare(char* s1,char* s2){
while(*s1 && *s2 && *s1 == *s2){
++s1;
++s2;
}
return *s1 - *s2;
} int partition(char** filenames,int start,int end){
if(!filenames) return -1;
char* privot = filenames[start];
while(start < end){
while(start < end && compare(privot,filenames[end]) < 0)
--end;
swap(&filenames[start],&filenames[end]);
while(start < end && compare(privot,filenames[start]) >= 0)
++start;
swap(&filenames[start],&filenames[end]);
}
return start;
} void sort(char** filenames,int start,int end){
if(start < end){
int position = partition(filenames,start,end);
sort(filenames,start,position - 1);
sort(filenames,position + 1,end);
}
}

效果展示

与原版ls命令不同点

虽然已经基本实现了ls命令,但是与原版ls相比,还存在一些不同,比如输出的格式是一行一个文件名,原版的为一行多个且对齐,还有就是原版ls显示的不同文件带有不同的颜色。这个估计和文件类型有关,待下一篇博客仔细研究。

参考资料

《Understanding Unix/Linux Programming A Guide to Theory and Practice》

欢迎大家转载本人的博客(需注明出处),本人另外还有一个个人博客网站:[https://www.lularible.cn],欢迎前去浏览。

Linux系统编程【3.1】——编写ls命令的更多相关文章

  1. Linux系统编程【2】——编写who命令

    学到的知识点 通过实现who命令,学到了: 1.使用man命令寻找相关信息 2.基于文件编程 3.体会到c库函数与系统调用的不同 4.加深对缓冲技术的理解 who命令的作用 who命令的使用 在控制终 ...

  2. Linux系统编程【3.2】——ls命令优化版和ls -l实现

    前情提要 在笔者的上一篇博客Linux系统编程[3.1]--编写ls命令中,实现了初级版的ls命令,但是与原版ls命令相比,还存在着显示格式和无颜色标记的不同.经过笔者近两天的学习,基本解决了这两个问 ...

  3. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  4. linux系统编程综合练习-实现一个小型的shell程序(一)

    之前已经花了不少篇幅学习了linux系统编程的很多知识点:文件与io.进程.信号.管道,而零散的知识点,怎么能够综合的串接起来是学习的一个很重要的目的,当然最好的方式就是用所学的知识点做一个项目了,所 ...

  5. Linux 系统编程 学习:01-进程的有关概念 与 创建、回收

    Linux 系统编程 学习:01-进程的有关概念 与 创建.回收 背景 上一讲介绍了有关系统编程的概念.这一讲,我们针对 进程 开展学习. 概念 进程的身份证(PID) 每一个进程都有一个唯一的身份证 ...

  6. Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

    Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...

  7. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

  8. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  9. Linux系统编程温故知新系列 --- 01

    1.大端法与小端法 大端法:按照从最高有效字节到最低有效字节的顺序存储,称为大端法 小端法:按照从最低有效字节到最高有效字节的顺序存储,称为小端法 网际协议使用大端字节序来传送TCP分节中的多字节整数 ...

随机推荐

  1. ASP.NET Core错误处理中间件[1]: 呈现错误信息

    NuGet包"Microsoft.AspNetCore.Diagnostics"中提供了几个与异常处理相关的中间件.当ASP.NET Core应用在处理请求过程中出现错误时,我们可 ...

  2. SpringBoot Logback无法获取配置中心属性

    SpringBoot Logback无法获取配置中心属性 前言 最近在做项目中,需要把项目中的日志信息通过RabbitMQ将规定格式的消息发送到消息队列中,然后ELK系统通过消息队列拿日志并且保存起来 ...

  3. 2021年正确的Android逆向开发学习之路

    2021年正确的Android逆向开发学习之路 说明 文章首发于HURUWO的博客小站,本平台做同步备份发布.如有浏览或访问异常或者相关疑问可前往原博客下评论浏览. 原文链接 2021年正确的Andr ...

  4. 检查Mysql主从状态

    .检查MySQL主从同步状态 #!/bin/bash USER=bak PASSWD=123456 IO_SQL_STATUS=$(mysql -u$USER -p$PASSWD -e  show s ...

  5. REUSE_ALV_FIELDCATALOG_MERGE函数

    今天使用REUSE_ALV_FIELDCATALOG_MERGE函数,就是获取不到fieldcat, 搞了半天才发现,原来参数要全部大写才行!!小写字符就是获取不到,唉,悲哀...

  6. bootstrap 后端模板

    Twitter Bootstrap 框架已经广为人知,用于加快网站,应用程序或主题的界面开发,并被公认为是迄今对于 Web 开发的最有实质性帮助的工具之一.在此之前的,各种各样的界面库伴随着高昂的维护 ...

  7. ubuntu安装mysql5.6

    安装mysql5.6在ubuntu上安装mysql5.6的版本 1.添加mysql5.6的源 sudo apt-get install software-properties-common sudo ...

  8. P5689 多叉堆

    写在前面 OI 生涯中 AC 的首道组合数学应用题. 开题 5min 发现规律,写了半下午代码,调了两天,然而甚至没过样例,心态崩了.几天之后重新写了一份代码才 AC. 虽然思维难度不大,但毕竟是联赛 ...

  9. 用RabbitMQ了好几年之后,我总结出来5点RabbitMQ的使用心得

    大概从 2013 年开始,我就开始了自己和 RabbitMQ 的接触,到现在已经有七年多了. 在这七年中,既有一些对 RabbitMQ 的深度体验,更有无数的血泪史. 而根据我这么多年的使用经验,我将 ...

  10. 引入 Gateway 网关,这些坑一定要学会避开!!!

    Spring cloud gateway是替代zuul的网关产品,基于Spring 5.Spring boot 2.0以上.Reactor, 提供任意的路由匹配和断言.过滤功能.上一篇文章谈了一下Ga ...