利用Linux中IO接口实现MiniShell
项目要求:
1.进入软件后显示字符终端界面,界面中包含用户名(可以写死
为linux)、操作系统名(可以写死为ubuntu)、当前所在的文件名
[linux@ubuntu:Desktop]
2.软件中支持用户输入如下命令:
1.ls 实现
ls -a
ls -l
ls filename
ls -l filename
2.touch 实现
touch filename
3.rm 实现
rm filename
4.mkdir 实现
mkdir dirname
5.rmdir 实现
rmdir dirname
6.cd 实现
cd ..
7.cp 实现
cp src.txt dstdir
8.mv 实现
mv oldpath newpath
9.cat 实现
cat filename.txt
10.chmod 实现
chmod 0777 filename
11.ln
ln -s file.txt a.txt 实现
ln file.txt b.txt
12.find 实现
find filepath -name filename
3.minishell记录了用户输入命令的时间和内容,可以支持用户
使用history命令查看用户输入的历史命令及其输入的时间
history
[2024-08-03 13:51:30]history
项目要求:
1.编写项目文档
1.项目需求
项目背景
描述实现该原因及背景
需求分析
分析项目要求(不涉及技术)
概要设计
根据需求设计相关技术实现功能
软件框图:各个模块之间的功能及
其关系
模块设计
具体每个功能的模块设计
流程图:每个模块实现功能的具
体步骤及方式
2.根据代码功能,将代码划分成若干个文件,并使用多文件编程来
实现代码
main.c
main.h
terminal.c
terminal.h
command.c
command.h
Makefile设计
实现如下:
commend.c
/*************************************************
*
* minihshell项目代码中和命令相关的代码
*
*************************************************/
#include <string.h>
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
static int DisplayFile(char *pfilename);
static int ListDir(char *pdirname, char *pfilename);
/************************************************
* 函数名:DisplayFile
* 功 能:
* 显示文件详细信息
* 参 数:
* pfilename:文件名
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int DisplayFile(char *pfilename)
{
struct stat buf;
int ret = 0;
struct passwd *pwd = NULL;
struct group *grp = NULL;
struct tm *ptm = NULL;
char *pmon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
ret = lstat(pfilename, &buf);
if (-1 == ret)
{
return -1;
}
#if 0
//001 000 000 110 110 100
// 100 000 000
//001 111 000 000 000 000
//001 000 000 000 000 000
printf("buf.st_mode = %#o\n", buf.st_mode);
switch (buf.st_mode & S_IFMT)
{
case S_IFSOCK:putchar('s');break;
case S_IFLNK:putchar('l');break;
case S_IFREG:putchar('-');break;
case S_IFBLK:putchar('b');break;
case S_IFCHR:putchar('c');break;
case S_IFIFO:putchar('p');break;
}
#endif
if (S_ISREG(buf.st_mode))
{
printf("-");
}
else if (S_ISDIR(buf.st_mode))
{
printf("d");
}
else if (S_ISCHR(buf.st_mode))
{
printf("c");
}
else if (S_ISBLK(buf.st_mode))
{
printf("b");
}
else if (S_ISFIFO(buf.st_mode))
{
printf("p");
}
else if (S_ISSOCK(buf.st_mode))
{
printf("s");
}
else if (S_ISLNK(buf.st_mode))
{
printf("l");
}
buf.st_mode & S_IRUSR ? putchar('r') : putchar('-');
buf.st_mode & S_IWUSR ? putchar('w') : putchar('-');
buf.st_mode & S_IXUSR ? putchar('x') : putchar('-');
buf.st_mode & S_IRGRP ? putchar('r') : putchar('-');
buf.st_mode & S_IWGRP ? putchar('w') : putchar('-');
buf.st_mode & S_IXGRP ? putchar('x') : putchar('-');
buf.st_mode & S_IROTH ? putchar('r') : putchar('-');
buf.st_mode & S_IWOTH ? putchar('w') : putchar('-');
buf.st_mode & S_IXOTH ? putchar('x') : putchar('-');
printf(" %ld", buf.st_nlink);
pwd = getpwuid(buf.st_uid);
printf(" %s", pwd->pw_name);
grp = getgrgid(buf.st_gid);
printf(" %s", grp->gr_name);
printf(" %ld", buf.st_size);
ptm = localtime(&buf.st_mtime);
printf(" %s %d %02d:%02d", pmon[ptm->tm_mon], ptm->tm_mday, ptm->tm_hour, ptm->tm_min);
printf(" %s\n", pfilename);
return 0;
}
/************************************************
* 函数名:MyLs
* 功 能:
* 执行Ls命令
* 参 数:
* pparg:存放解析到的字符串首地址的指针数组名
* curargcnt:指针数组当前元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int MyLs(char **pparg, int arglen)
{
DIR *dp = NULL;
struct dirent *pp = NULL;
dp = opendir(".");
if (NULL == dp)
{
return -1;
}
while (1)
{
pp = readdir(dp);
if (NULL == pp)
{
break;
}
if ('.' == *pp->d_name && !(2 == arglen && 0 == strcmp(pparg[1], "-a")))
{
continue;
}
if (2 == arglen && 0 == strcmp(pparg[1], "-l"))
{
DisplayFile(pp->d_name);
}
else
{
printf("%s ", pp->d_name);
}
}
if (!(2 == arglen && 0 == strcmp(pparg[1], "-l")))
{
printf("\n");
}
closedir(dp);
return 0;
}
/************************************************
* 函数名:MyCd
* 功 能:
* 执行Cd命令
* 参 数:
* pparg:存放解析到的字符串首地址的指针数组名
* curargcnt:指针数组当前元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int MyCd(char **pparg, int arglen)
{
if (2 == arglen)
{
chdir(pparg[1]);
}
return 0;
}
/************************************************
* 函数名:MyTouch,Myrm
* 功 能:
* 执行Touch和myrm命令
* 参 数:
* pparg:存放解析到的字符串首地址的指针数组名
* curargcnt:指针数组当前元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int MyTouch(char **pparg, int arglen)
{
if (2 == arglen)
{
fclose(fopen(pparg[1], "w"));
}
return 0;
}
int Myrm(char **parg, int arglen)
{
if (2 == arglen)
{
remove(parg[1]);
}
return 0;
}
/************************************************
* 函数名:Mymkdir,Myrmdir
* 功 能:
* 执行Mymkdir和Myrmdir命令
* 参 数:
* pparg:存放解析到的字符串首地址的指针数组名
* curargcnt:指针数组当前元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int Mymkdir(char **parg, int arglen)
{
if (2 == arglen)
{
mkdir(parg[1],0777);
}
return 0;
}
int Myrmdir(char **parg, int arglen)
{
if (2 == arglen)
{
rmdir(parg[1]);
}
return 0;
}
/************************************************
* 函数名:MyHistory
* 功 能:
* 执行History命令
* 参 数:
* pparg:存放解析到的字符串首地址的指针数组名
* curargcnt:指针数组当前元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int MyHistory(char **pparg, int arglen)
{
FILE *fp = NULL;
char tmpbuff[4096] = {0};
char *pret = NULL;
fp = fopen("/home/linux/history.txt", "r");
if (NULL == fp)
{
return -1;
}
while (1)
{
pret = fgets(tmpbuff, sizeof(tmpbuff), fp);
if (NULL == pret)
{
break;
}
printf("%s", tmpbuff);
}
fclose(fp);
return 0;
}
/************************************************
* 函数名:MyChmod
* 功 能:
* 执行chmod命令
* 参 数:
* pparg:存放解析到的字符串首地址的指针数组名
* curargcnt:指针数组当前元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int MyChmod(char **pparg, int arglen)
{
char *ptmp = NULL;
int val = 0;
int ret = 0;
if (3 != arglen)
{
return -1;
}
ptmp = pparg[1]+1;
while (*ptmp != '\0')
{
val = val * 8 + (*ptmp - '0'); //输入的是字符,然后需要转化为int 数字。
ptmp++;
}
ret = chmod(pparg[2], val); //需要传入10进制的数
if (-1 == ret)
{
return -1;
}
return 0;
}
/************************************************
* 函数名:MyFind
* 功 能:
* 执行find命令
* 参 数:
* pparg:存放解析到的字符串首地址的指针数组名
* curargcnt:指针数组当前元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int MyFind(char **pparg, int arglen)
{
int ret = 0;
if (4 != arglen)
{
return -1;
}
//find dir -name filename
ret = ListDir(pparg[1], pparg[3]);
if (-1 == ret)
{
return -1;
}
return 0;
}
/************************************************
* 函数名:ListDir
* 功 能:
* 遍历目录及子目录下所有文件
* 参 数:
* pdirname:要遍历的目录
* pfilename:要找的文件名
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int ListDir(char *pdirname, char *pfilename)
{
DIR *dp = NULL;
struct dirent *pp = NULL;
char filepath[1024] = {0};
//1.打开目录文件
dp = opendir(pdirname);
if (NULL == dp)
{
return -1;
}
//2.读取目录项信息
//3.如果为目录文件
//4.递归调用遍历此目录文件
while (1)
{
pp = readdir(dp);
if (NULL == pp)
{
break;
}
if ('.' == *pp->d_name)
{
continue;
}
sprintf(filepath, "%s/%s", pdirname, pp->d_name);
if (0 == strcmp(pp->d_name, pfilename))
{
printf("%s\n", filepath);
}
if (DT_DIR == pp->d_type)
{
ListDir(filepath, pfilename);
}
}
//5.关闭目录文件
closedir(dp);
return 0;
}
/************************************************
* 函数名:Mycp
* 功 能:
* 拷贝源文件到目的文件
* 参 数:
* pdirname:要遍历的目录
* pfilename:要找的文件名
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int Mycp(char **parg, int arglen)
{
int ret = 0;
char tmpbuff[1024] = {0};
char * p = NULL;
char *p2 = NULL;
if(3 != arglen)
{
return -1;
}
FILE *fp = NULL;
FILE *fp2 = NULL;
fp = fopen(parg[1],"r");
if(fp == NULL)
{
perror("chucuo\n");
return -1;
}
fp2 = fopen(parg[2],"a");
if(fp2 == NULL)
{
perror("chucuofp2\n");
return -1;
}
while(1)
{
p = fgets(tmpbuff,sizeof(tmpbuff),fp);
if(p == NULL)
{
break;
}
fputs(tmpbuff,fp2);
}
fclose(fp);
fclose(fp2);
return 0;
}
int Mymv(char **parg, int arglen)
{
int ret = 0;
if(3 != arglen)
{
return -1;
}
ret = link(parg[1],parg[2]);
remove(parg[1]);
return ret;
}
int Mycat(char **parg, int arglen)
{
int ret = 0;
if(2 != arglen)
{
return -1;
}
char tmpbuff[4096] = {0};
FILE *fp = NULL;
char * p = NULL;
fp = fopen(parg[1],"r");
while(1)
{
p = fgets(tmpbuff,sizeof(tmpbuff),fp);
if(p == NULL)
{
break;
}
fputs(tmpbuff,fp);
printf("%s",tmpbuff);
}
printf("\n");
fclose(fp);
return ret;
}
int Myln(char **parg, int arglen)
{
if (4 == arglen && 0 == strcmp(parg[1], "-s"))
{
symlink(parg[2],parg[3]);
}
if (3 == arglen)
{
link(parg[1],parg[2]);
}
return 0;
}
commend.h
#ifndef __COMMAND_H__
#define __COMMAND_H__
extern int MyTouch(char **pparg, int arglen);
extern int MyLs(char **pparg, int arglen);
extern int MyCd(char **pparg, int arglen);
extern int MyHistory(char **pparg, int arglen);
extern int MyChmod(char **pparg, int arglen);
extern int MyFind(char **pparg, int arglen);
extern int Myrm(char **parg, int arglen);
extern int Mymkdir(char **parg, int arglen);
extern int Myrmdir(char **parg, int arglen);
extern int Mycp(char **parg, int arglen);
extern int Mymv(char **parg, int arglen);
extern int Mycat(char **parg, int arglen);
extern int Myln(char **parg, int arglen);
#endif
main.c
/*************************************************
*
* minihshell项目代码中主函数
*
*************************************************/
#include "terminal.h"
#include <stdio.h>
int main(void)
{
char commandbuf[1024] = {0};
char *parg[10] = {NULL};
int curargcnt = 0;
while (1)
{
//1.显示命令
ShowTerminalCommand();
//2.接收命令
GetTerminalCommand(commandbuf, sizeof(commandbuf));
//3.记录命令
RecordTerminalCommand(commandbuf);
//4.切割命令
curargcnt = SplitTerminalCommand(commandbuf, parg, 10);
//5.执行命令
ExecuteTerminalCommand(parg, curargcnt);
}
return 0;
}
terminal.c
/*************************************************
*
* minihshell项目代码中与终端相关的代码
*
*************************************************/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include "command.h"
/************************************************
* 函数名:ShowTerminalCommand
* 功 能:
* 显示终端命令行
* 参 数:
* void 缺省
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
* 1.根目录处理特别对待
************************************************/
int ShowTerminalCommand(void)
{
char path[4096] = {0};
char *ptmp = NULL;
char *pret = NULL;
//获得路径
pret = getcwd(path, sizeof(path));
if (NULL == pret)
{
return -1;
}
//获得路径最后一部分的文件名(根目录需要特殊处理)
ptmp = path + strlen(path);
while (*ptmp != '/')
{
ptmp--;
}
if (0 != strcmp(path, "/"))
{
ptmp++;
}
//打印命令行
printf("[linux@ubuntu#%s]", ptmp);
return 0;
}
/************************************************
* 函数名:GetTerminalCommand
* 功 能:
* 接收用户命令
* 参 数:
* pcmdbuf:存放命令行空间首地址
* maxlen:最多接收字节数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
* 1.存放命令的空间必须是外部传入的,如果接收到内部数组,会随函数结束被回收
************************************************/
int GetTerminalCommand(char *pcmdbuf, int maxlen)
{
fgets(pcmdbuf, maxlen, stdin);
pcmdbuf[strlen(pcmdbuf)-1] = '\0';
return 0;
}
/************************************************
* 函数名:SplitTerminalCommand
* 功 能:
* 解析用户命令
* 参 数:
* pcmdbuf:存放命令行空间首地址
* pparg:存放解析到的字符串首地址的指针数组名
* argmaxlen:指针数组最大的元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
* 1.指针数组传递二级指针(只能在函数体外部定义,函数体内部定义会被函数结束回收)
************************************************/
int SplitTerminalCommand(char *pcmdbuf, char **pparg, int argmaxlen)
{
int cnt = 0;
pparg[cnt] = strtok(pcmdbuf, " ");
if (NULL == pparg[cnt])
{
return cnt;
}
cnt++;
while (cnt < argmaxlen)
{
pparg[cnt] = strtok(NULL, " ");
if (NULL == pparg[cnt])
{
return cnt;
}
cnt++;
}
return cnt;
}
/************************************************
* 函数名:ExecuteTerminalCommand
* 功 能:
* 执行用户命令
* 参 数:
* pparg:存放解析到的字符串首地址的指针数组名
* curargcnt:指针数组当前元素个数
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int ExecuteTerminalCommand(char **parg, int curargcnt)
{
if (0 == curargcnt)
{
return 0;
}
if (0 == strcmp(parg[0], "ls"))
{
MyLs(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "cd"))
{
MyCd(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "touch"))
{
MyTouch(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "history"))
{
MyHistory(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "exit"))
{
exit(0);
}
else if (0 == strcmp(parg[0], "chmod"))
{
MyChmod(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "find"))
{
MyFind(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "rm"))
{
Myrm(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "mkdir"))
{
Mymkdir(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "rmdir"))
{
Myrmdir(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "cp"))
{
Mycp(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "mv"))
{
Mymv(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "cat"))
{
Mycat(parg, curargcnt);
}
else if (0 == strcmp(parg[0], "ln"))
{
Myln(parg, curargcnt);
}
return 0;
}
/************************************************
* 函数名:RecordTerminalCommand
* 功 能:
* 记录命令
* 参 数:
* pcommandbuf: 命令
* 返回值:
* 成功返回0
* 失败返回-1
* 注意事项:
************************************************/
int RecordTerminalCommand(char *pcommandbuf)
{
//1.打开文件
FILE *fp = NULL;
time_t t;
struct tm *ptm = NULL;
fp = fopen("/home/linux/history.txt", "a");
if (NULL == fp)
{
return -1;
}
//2.记录命令及其时间
time(&t);
ptm = localtime(&t);
fprintf(fp, "[%04d-%02d-%02d %02d:%02d:%02d]%s\n", ptm->tm_year+1900, ptm->tm_mon+1,
ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, pcommandbuf);
//3.关闭文件
fclose(fp);
return 0;
}
terminal.h
#ifndef __TERMINAL_H__
#define __TERMINAL_H__
extern int ShowTerminalCommand(void);
extern int GetTerminalCommand(char *pcmdbuf, int maxlen);
extern int SplitTerminalCommand(char *pcmdbuf, char **pparg, int argmaxlen);
extern int ExecuteTerminalCommand(char **parg, int curargcnt);
extern int RecordTerminalCommand(char *pcommandbuf);
#endif
Makefile
1.Makefile是一个文件,工程代码编译规则写在makefile文件中
文件名:makefile Makefile2.makefile语法规则
要生成的文件:所有依赖的文件
编译规则3.makefile维护文件时间戳,需要编译的重新编译,不需要重新的编译则省略编译的时间
4.makefile复杂化:
$< :第一个依赖文件
$^ :所有依赖文件
$@ :目标文件定义变量:
变量名=变量值= 赋值
+= 追加
:= 覆盖
?= 之前没被赋值则赋值,之前被赋值不再重复赋值
OBJ:=minishell //目标生成文件
SRC_PATH:=./src/ //.c文件的路径
INCLUDE_PATH:=./include //.h文件的路径
OBJS+=$(SRC_PATH)command.c //要编译的文件,下边三个都是
OBJS+=$(SRC_PATH)terminal.c
OBJS+=$(SRC_PATH)main.c
CC=gcc //编译指令
$(OBJ):$(OBJS) //$()代表引用:后边为编译对象
$(CC) $^ -o $@ -I$(INCLUDE_PATH) //tab到位置,$^代表所有依赖文件,$@代表目标文件
.PHONY: //伪指令
clean: //代表清除文件
rm $(OBJ)
install: //安装文件指令 sudo make install
sudo cp $(OBJ) /bin
uninstall: //卸载软件指令 sudo make uninstall
sudo rm /bin/$(OBJ)
加油啊