C 语言实现 Linux ls 命令
ls
常用参数:
-l
:以长格式显示目录下的内容列表。输出的信息从左到右依次包括文件名,文件类型、权限模式、硬连接数、所有者、组、文件大小和文件的最后修改时间等;-a
:显示所有档案及目录;-r
:以文件名反序排列并输出目录内容列表;-t
:用文件和目录的更改时间排序;-R
:递归处理,将指定目录下的所有文件及子目录一并处理;-r
:以文件名反序排列并输出目录内容列表;
常用参数组合:
ls -rtl
:非常实用,反向按时间排序,通常我们需要查看一个目录下面最近修改的文件,在面临很多文件的时候,可以用这条命令很快的将最近修改的文件列在前面几行ls --full-time
:非常实用,在写一些shell脚本的时候需要获取文件被更新的时间,可以用ls
的列出完整的日期与时间,获取时间细微的变化
更多请参考: http://man.linuxde.net/ls
1.2 知识点
- Linux 下的
ls
实现原理 - C 语言基础
- 文件,目录结构体(dirent 和 stat)
readdir
系统函数调用opendir
系统函数调用
1.5 代码获取
wget http://labfile.oss.aliyuncs.com/courses/439/source_code.zip
unzip source_code.zip
请尽量按照实验步骤自己写出 C 语言程序,请确认文件保存在目录:“/home/shiyanlou/source_code/” 下。
二、步骤
在 Unix/Linux 的文件系统中,所有东西的储存形式都是文件(一切皆文件的理念)。这样的话,在磁盘上的这一棵目录树上,每一个节点只有两种情况,要么是文件,要么是目录。
不像 Winodws 那样有驱动器,分区之类。这样的目录结构让 ls
的实现变得简单和容易理解。
2.1 ls
原理
输入 ls
,输出一个文件名的列表,它大致是这样工作的:
- 打开一个目录
- 从入口开始读取
- 展示文件
- 关闭目录
然后我们看,可以用哪些函数,实现对于目录的操作
opendir //打开一个目录,成功则返回DIR*型态的目录流,打开失败则返回NULL readdir //读取目录 readdir 返回一个指向目录的当前记录的指针,记录的类是 struct dirent 这个结构定义 /usr/include/dirent.h closedir //关闭目录
2.2 ls
的实现
首先来看一看目录的结构***于 /usr/include/x86_64-linux-gnu/bits/dirent.h)。查看目录的结构体定义,看看计算机理解的“目录”是什么样子的。
struct dirent { ino_t d_ino; /* inode number */ off_t d_off; /* offset to the next dirent */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* type of file; not supportedby all file system types */ char d_name[256]; /* filename */ };
我们需要的就是 d_name,也就是目录的名字。 编写源程序 ls1.c:
/*这里关于main函数的参数,ac包括程序本身的参数个数,av是参数名*/
/*************************************************************************
> File Name: ls1.c
> Author:
> Mail:
************************************************************************/
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>
void do_ls(char[]);
main(int ac, char *av[])
{
if(ac==1)
do_ls(".");
else
while(--ac){
printf("%s:\n",*++av);
do_ls(*av);
}
}
void do_ls(char dirname[])
{
/*请同学们参考 1.5 节的下载链接在这里补全代码,所用的代码和知识点在 2.2 节已给出.*/
}
编译后运行结果如下:
2.3 ls -l
的实现
2.3.1 查看文件属性
那么,如果我们需要更多的信息呢?如果获取文件的属性呢?磁盘上的文件有很多属性,如文件大小,文件素有这的 ID 等。 我们先来学习一下文件的结构体 :
关于目录中文件的属性等信息使用
struct stat
结构体来描述的,其结构的定义如下:(位于 /usr/include/x86_64-linux-gnu/bits/stat.h)/*由于这个结构体比较大,我们选取比较关键的几个结构讲解,同学们可以用locate找到这个stat.h文件进行查看 该结构体提供了关于文件(或者设备)的如下重要信息:*/ struct stat { __dev_t st_dev; /* Device. */ __mode_t st_mode; /* 文件类型和许可权限 */ __nlink_t st_nlink; /* 文件链接数 */ __uid_t st_uid; /* 文件属主id */ __gid_t st_gid; /* 文件属主所在组的id*/ __dev_t st_rdev; /* 文件的字节数*/ unsigned short int __pad2; /* 用于填充对齐 */ __blkcnt_t st_blocks; /* 文件所占的块数 */ struct timespec st_atim; /* 文件最后访问时间 (access time) */ struct timespec st_mtim; /* 文件最后修改时间 (modification time)*/ struct timespec st_ctim; /* 文件属性/状态最后改变的时间 (change status time) */ ... }
如果需要得到文件属性,进程可以定义一个 struct stat,然后调用 stat,告诉内核把文件属性放到这个结构中。 stat 把文件 fname 的信息复制到指针 bufp 所指的结构中。
编写源程序 fileinfo.c:
/*************************************************************************
> File Name: fileinfo.c
> Author:
> Mail:
************************************************************************/
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(int argc,char *argv[])
{
struct stat info;
if(argc>1)
if(stat(argv[1],&info)!=-1){
show_stat_info(argv[1],&info);
return 0;
}
else
perror(argv[1]); /*report stat() error*/
return 1;
}
show_stat_info(char * fname,struct stat *buf)
{
/*请同学们参考 1.5 节的下载链接在这里补全代码,所用的代码和知识点在 2.3.1 节已给出.*/
}
编译后运行结果如下:
XXX 作为参数,直接输入你需要查看文件属性的文件名
2.3.2 查看用户权限
经过上面的练习,我们似乎明白了 ls -l
的工作原理,这里还有我们发现了一个比较重要的地方
-rw-rw-r--
这个是 Linux 下对于不同用户的权限展示,分别是用户,用户组,所有组在 stat 结构体中,也就是用 mode 这个字段进行存储的
它是一个 16 位的二进制数,前 4 位用作文件类型,1 代表具有某个属性,0 代表没有;
接下来的3位分为 user-ID,set-group-ID 位和 sticky 位,最后9位是许可权限,分三组,每组 3 位;
这里使用了
掩码
的技术来划分,就跟ip中的子网掩码一样比如把2进制的 100000110110100 分成 1,000,000,110,110,110。 从而得到8进制的 100664。
对2 进制进行位与操作,这也是我们所说的解码。代码如下(判断目录)
if((info.st_mode&0170000)==0040000) printf("this is a directory");
用宏定义去代替以上代码:
#define S_ISFIFO(m) (((m)&(0170000))==(0040000)) #define S_ISDIR(m) (((m)&(0170000))==(0020000)) #define S_ISCHR(m) (((m)&(0170000))==(0060000)) #define S_ISBLK(m) (((m)&(0170000))==(0100000))
if(S_ISDIR(info.st_mode)) printf("this is a directory");
2.3.3 编写 ls2.c
有关于 uid_to_name 和 gid_to_name 这个两个函数的实现简单调用了 pwd.h 的函数,可以先查看结构体然后编写,学习方法与上面相同:
编写源程序 ls2.c:
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>
void do_ls(char[]);
void dostat(char *);
void show_file_info(char *,struct stat *);
void mode_to_letters(int ,char[]);
char * uid_to_name(uid_t);
char * gid_to_name(gid_t);
main(int argc,char *argv[])
{
if(argc==1)
do_ls(".");
else
while(--argc){
printf("%s:\n",*++argv);
do_ls;
}
}
/*请同学们参考 1.5 节的下载链接在这里补全代码,所用的代码和知识点在 2.3 节已给出.*/
如果加 -l 参数,那么会分栏显示出,每个文件的详细信息,每一行都代表了一个文件和它的属性。编译后运行结果如下:
三、总结
本课程由浅入深的讲解了 ls
的实现过程,先讲了最基本的 ls
,然后加入查看文件属性功能,再加入了权限展示的功能,学习了 C 语言对于系统函数的调用。一个 ls
命令看起来很简单,其实是很复杂的。在平时使用的过程的中觉得很方便,甚至感觉不到内部的运转,是因为 Linux 的结构和设计比较巧妙,多学甚意。
参考资料