0%

定制自己的ls命令

前言

在Linux系统中,一切皆为文件。
前段时间在Linux课上学习了Linux系统的底层调用,由此可以来通过C/C++来编写自己的Linux命令来对目录文件进行读写。

定制自己的ls命令就是通过命令行参数(不会的自行百度|Google,就是运行时传入的参数),选择选项然后输出某个目录下面的内容。


基础知识

文件描述符

每个打开的文件都被分配一个文件描述符。文件描述符是一个非负整数。linux内核通过文件描述符来访问文件。前三个描述符具有特定含义:

  • 0 标准输入(stdin) –键盘
  • 1 标准输出(stdout) –显示器
  • 2 标准错误(stderr) –默认在终端
  1. 如果打开的文件没有,那么fd为-1,可由此判断打开是否成功。
  2. fd的值慢慢增加的,从3开始,因为0,1,2已经被占用。
  3. 注意文件描述符(int)和文件流(FILE*)之间的区别。

目录dirent结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include  <sys/types.h>
#include <dirent.h>
struct dirent{
ino_t d_ino;
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[NAME_MAX+1];
}
d_ino: 当前目录的inode节点号;
d_off:目录文件首部到下个dirent结构的位移;
d_reclen:该记录的长度;
d_type:目录文件类型;
d_name:目录文件名;

文件信息stat结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
struct stat{
dev_t st_dev;
ino_t st_ino;
mode_t st_mode;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
off_t st_size;
unsigned long st_blksize;
unsigned long st_blocks;
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
}
st_dev:文件所处的磁盘设备ID号;
st_ino:文件的inode索引号;
st_mode:文件访问权限和文件类型;
st_nlink:文件的硬链接数;
st_uid:文件所属的用户ID;
st_gid:文件所属的组ID;
st_rdev:如果文件是字符或块设备,该值给出该设备的标识符信息;
st_size:对常规文件,该值为文件大小;对符号链接,该值为该符号链接所指向的目录长度;对设备文件,该值为0;
st_blksize:文件系统存储文件的块大小;
st_blocks:分配给文件的块数;
st_atime:文件的最近访问时间;
st_mtime:文件内容的最近修改时间;
st_ctime:文件属性的最近修改时间;

系统调用

打开关闭读写文件这里仅做记录,在本次内容并没有使用到。

1
2
3
4
5
6
7
8
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int open(char *fileName,int mode[,int permissions])
int close(int fd)
ssize_t read(int fd, void* buf, size_t count)
ssize_t write(int fd, void* buf, size_t count)

详细设计及代码

文件结构

1
2
3
4
- src
* main.c
* myls.h
* myls.c

流程

1.在main.c中判断传入的命令行参数是否符合要求。如果没有传入路径根据getwcd(char *buffer,size_t size)函数来获得当前路径。

2.在myls.h中声明两个函数分别为void printdir(char *dir,char option)和void printdetail(struct stat st)来进行调用。

3.printdir():使用全局变量来保存用户选择的选项,是否输出详细信息或者隐藏文件。涉及到的系统调用有:
DIR结构体:保存一个opendir()得到的内容
dirent结构体:存放了目录的信息,有文件名,目录文件类型,节点号和下一个结构的位移等。

4.chdir(dir)切换到该目录下。然后使用readdir(dp)来进行对该目录的循环读取,如果该读到文件是一个隐藏文件,那么判断给出的选项是否需要隐藏(即判断文件名的第一个字母是否则为’.’),还有是否需要输出详细信息的内容。

代码

感觉写的真丑,好久以前的。

1
2
3
4
5
6
7
8
9
10
11
//myls.h
#include<stdio.h>
#include<stdlib.h>

#include<unistd.h>
#include<dirent.h>
#include<string.h>
#include<sys/stat.h>

void printdir(char *dir,char option);
void printdetail(struct stat st);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//main.c
#include"myls.h"

int main(int argc,char **argv)
{
char dir[256];
if(argc==1)
{//当没有命令行参数传入的情况
if((getcwd(dir,256))!= NULL)
printdir(dir,'n');
else
fprintf(stderr,"cannot open current dierectory:%s\n",dir);
}else if(argc==2)
{//支持两种参数
if(argv[1][0]=='-')
{//加入选项的模式
if((getcwd(dir,256))!= 0)
printdir(dir,argv[1][1]);
else
fprintf(stderr,"cannot open current dierectory:%s\n",dir);
}else
{//传入绝对路径
printdir(argv[1],'n');
}
}else if(argc==3)
{
printdir(argv[2],argv[1][1]);
}else
printf("please check your input\n");
// printdir("/home/xuan/homework",0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//myls.c
#include"myls.h"

void printdetail(struct stat st)
{//输出文件的详细信息
int i;
switch(st.st_mode & S_IFMT)
{
case S_IFREG: printf("-"); break;
case S_IFDIR: printf("d"); break;
case S_IFLNK: printf("l"); break;
case S_IFBLK: printf("b"); break;
case S_IFCHR: printf("c"); break;
case S_IFIFO: printf("p"); break;
case S_IFSOCK: printf("s"); break;
}
for(i = 8; i >= 0; i--)
{
if(st.st_mode & (1 << i))
{
switch(i%3)
{
case 2: printf("r"); break;
case 1: printf("w"); break;
case 0: printf("x"); break;
}
}
else
printf("-");
}
}

void printdir(char *dir,char option) //option传递默认参数为n
{
DIR *dp;
struct dirent *entry;
struct stat statbuf;

if((dp=opendir(dir)) == NULL) //打开文件流指针
{
fprintf(stderr,"cannot open directory:%s\n",dir);
return;
}
chdir(dir); //切换到该目录
while((entry=readdir(dp)) != NULL)
{
lstat(entry->d_name,&statbuf);
//把目录中的文件名得到statbuf
if(S_ISDIR(statbuf.st_mode))
{
if(entry->d_name[0]=='.')
if(option!='a')
continue;
if(option=='l')
{
printdetail(statbuf);
printf(" %s/\n",entry->d_name);
}else
printf("%s/ ",entry->d_name);
}
if(S_ISREG(statbuf.st_mode))
{
if(entry->d_name[0]=='.')
if(option!='a')
continue;
if(option=='l')
{
printdetail(statbuf);
printf(" %s\n",entry->d_name);

}else
printf("%s ",entry->d_name);
}
}
printf("\n");
chdir("..");
closedir(dp);
}

编译和运行

使用gcc进行编译之后即可得到一个执行程序,进行执行

1
2
3
4
5
6
7
8
9
10
$ gcc main.c myls.c -o myls
$ ./myls
这样就可以显示当前目录的内容
当然也还可以加入参数,显示home下的内容
$ ./myls /home/
显示/home/xuan下的详细信息
$ ./myls -a /home/xuan
还可以放到系统命令中的话可以直接调用
$ sudo cp myls /usr/bin
$ myls

在写了myls的命令之后还写了点其他的命令,比如mydir通过系统调用来递归遍历文件夹之后层次性输出等,方法也都大同小异,也就不过多写了。

听说好看的人都关注了我的公众号《泫言》