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. 【ORACLE错误】SP2-0618: Cannot find the Session Identifier. Check PLUSTRACE role is enabled

    执行set autotrace traceonly的时候,报错 SQL> set autotrace traceonly SP2-0618: Cannot find the Session Id ...

  2. kubernetes机理之调度器以及控制器

    一 了解调度器 1.1  调度器是如何将一个pod调度到节点上的 我们都已然知晓了,API服务器不会主动的去创建pod,只是拉起系统组件,这些组件订阅资源状态的通知,之后创建相应的资源,而负责调度po ...

  3. 针对Linux系统主机,进入修复模式,解决开机报错问题

    1.让主机重启,进入开机时的内核选择界面,按e进入编辑界面 2.找到linux16那一行,将光标移动到最前面,按下End键,到这一行的末尾,然后空格 rd.break console=tty0 3.第 ...

  4. 用 UniRx 实现 Timeline 式的异步操作

      没接触 UniRx 之前,我在 Unity 中通常用 Coroutine 或 Callback 来实现异步操作.根据我的任务,一般都是去实现游戏组件的演出,比如:敌方角色图形显示后,我方角色 UI ...

  5. let关键字:加强版的var关键字

    本文首发于个人网站:let关键字:加强版的var关键字 你好,今天大叔想和你唠扯唠扯 ES6 新增的关键字 -- let.再说 let 的具体用法之前,大叔想先和你说说大叔自己对 let 的感受 -- ...

  6. Java-web易混淆知识点整理

    Java-web易混淆知识点 post和get区别 post: 数据不会显示在地址栏 安全 大小无限制 可以提交二进制文件 get: 数据显示在地址栏 不安全 get方式提交有大小限制(约4kb) 相 ...

  7. [APUE] 进程环境

    APUE 一书的第七章学习笔记. 进程终止 有 8 种方式可以使得进程终止,5 种为正常方式: Return from main Calling exit() Calling _exit or _Ex ...

  8. 面试官:你说说ReentrantLock和Synchronized区别

    大家好!又和大家见面了.为了避免面试尴尬,今天同比较通俗语言和大家聊下ReentrantLock和Synchronized区别! 使用方式 Synchronized可以修饰实例方法,静态方法,代码块. ...

  9. SpringBoot深入理解

    SpringBoot深入理解 项目打包SpringBoot启动过程 当使用打包时,会下载org-springframework-boot-loader的jar,并且不会放在lib存放的第三方jar包文 ...

  10. 中央事件总线 事件驱动架构(EDA) 解析事件总线的4种实现方式

    事件驱动架构(EDA)https://mp.weixin.qq.com/s/nA8XFD2Rx_7qA_LxltGGHw https://mp.weixin.qq.com/s/cD3auglgKzOb ...