用c语言的实现一个简单的交互式shell

2024-06-22 09:58

本文主要是介绍用c语言的实现一个简单的交互式shell,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

处理思想:

1.读取命令行参数:fgets(buff,50,stdin)

2.以管道符号|将命令行字符串分解成若干个子字符串,推荐使用函数strtok_r()

3.以空格符号‘  ’将第二步分解得到的子字符串继续分解,得到每一个参数,推荐使用函数strtok()

4.strtok与strtok_r函数的区别,见我的博客点击打开链接

代码如下:

/*************************************************************************
    > File Name: my_shell.c
    > Author: lipan
    > Mail: ma6174@163.com 
    > Created Time: Sun 27 Jul 2014 10:20:56 PM EDT
 ************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
struct command
{
char *arg[15]; /*用于存放命令行参数的指针数组*/
char *in; /*存放输入重定向的文件名,其实这个变量也可以直接存放到arg中的,将他们分开存放方便读取数值*/
char *out; /*存放输出重定向的文件名*/
};
typedef struct command cmd;
int main(int argc,char *argv[])
{
char buff[50];
char pathname[50];
cmd cmds[10]; /*结构数组,它的每一个元素都是一个结构体*/
pid_t pid;
int fd_in; /*输入重定向文件描述符*/
int fd_out; /*输出重定向文件描述符*/
int i=0;
int pipe_num=0;
int j=0;
int fd[10][2]; /*开辟10个管道描述符*/
int cmd_num=0; /*以管道符号分开的命令数目*/


while(1)
{
/*获取初始的工作路径*/
memset(pathname,50,0);
getcwd(pathname,50); /*获取当前工作路经*/
printf("[my@localhost %s]",pathname);
fflush(stdout); /*刷新缓冲区*/



memset(buff,50,0);
fgets(buff,50,stdin); /*获取命令行所有参数*/
buff[strlen(buff)-1]='\0'; /*这点处理非常有必要,直接输入内容给一个buffer赋值时,最好给buffer后面添加一个'\0'*/
/*cmds存放的是以管道符|分开的命令行字符串,该字符串中又包含若干个命令参数,而arg中存放的是单个的命令行参数*/


cmd_num=parse_pipe(buff,cmds); /*cmd_num就是以管道符号分开的命令字符串的个数*/


cd_command(cmds[0].arg[0],cmds[0].arg[1]);  /*调用cd命令函数*/

/*便于分析结构数组的值而打印输出*/
/*printf("cmds[0]=        %s\n",cmds[0]);
printf("cmds[0].arg[0]=          %s\n",cmds[0].arg[0]);
printf("cmds[0].arg[1]=          %s\n",cmds[0].arg[1]);
printf("cmds[0].in=       %s\n",cmds[0].in);
printf("cmds[0].out=          %s\n",cmds[0].out);


printf("cmds[1]=        %s\n",cmds[1]);
printf("cmds[1].arg[0]=          %s\n",cmds[1].arg[0]);
printf("cmds[1].arg[1]=          %s\n",cmds[1].arg[1]);
printf("cmds[1].in=       %s\n",cmds[1].in);
printf("cmds[1].out=          %s\n",cmds[1].out);
fflush(stdout);*/


pipe_num=cmd_num- 1;


if(pipe_num>10) /*因为预先最多分配了10个管道描述符,所以如果从终端获取的管道个数大于10,就重新输入命令行*/
continue;
for(i=0;i<pipe_num;i++)
pipe(fd[i]);


for(i=0;i<cmd_num;i++) /*i等于以管道符分开的命令个数*/
{
if((pid=fork())==0) /*创建进程,如果子进程先执行,跳出循环,执行子进程代码段,如果父进程先执行,接着创建,最后的结果就是产生一个父进程,cmd_num个子进程*/
break;
if(pid<0)
perror("fork");
}
if(pid==0)   /*这里有多少个子进程就执行多少次*/
{
/*重定向输入*/
if(cmds[i].in)  /*执行第一个子进程时i=0,执行第二个子进程时i=1,依次递增*/
{
fd_in=open(cmds[i].in,O_RDONLY);
if(fd_in<0)
perror("open");
dup2(fd_in,STDIN_FILENO);
close(fd_in);
}
/*重定向输出*/
if(cmds[i].out)
{
fd_out=open(cmds[i].out,O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd_out<0)
perror("open_out");
dup2(fd_out,STDOUT_FILENO);
close(fd_out);
}
/*管道: 它是进程间的一种通信方式,这里采取的是多个子进程间通信*/
if(pipe_num) /*如果管道个数为0则不执行,否则执行*/
{
if(i==0)
{
close(fd[i][0]); /*i=0肯定是第一个子进程,关闭第一个子进程的管道的读端*/
dup2(fd[i][1],1); /*将标准输出重定向到第一个子进程管道的写端,本来第一个命令字符串的执行结果输出到屏幕的,现在输出到管道的读端*/
close(fd[i][1]); /*关闭第一个子进程管道的写端*/
for(j=1;j<pipe_num;++j) /*关闭多余的管道,当然这里只创建了一个管道*/
close(fd[j][0]),close(fd[j][1]);
}
else if(i==pipe_num) /*假设i=1肯定是第二个子进程,并且只有一个管道,那么条件成立*/
{
close(fd[i-1][1]); /*关闭管道的写端*/
dup2(fd[i-1][0],0); /*将标准输入重定向到第二个子进程的读端,本来第二个命令字符串的输入是直接由键盘输入的,现在直接从管道的读端获取,从而达到了两个进程的通信*/
close(fd[i-1][0]); /*关闭第二个子进车管道的读端*/
for(j=0;j<pipe_num-1;++j)
close(fd[j][0]),close(fd[j][1]);
}
else
{
dup2(fd[i - 1][0], 0);
close(fd[i][0]);
dup2(fd[i][1], 1);
close(fd[i][1]);
for (j = 0; j < pipe_num; ++j)
{
if((j!=i-1)||(j!=i))
close(fd[j][0]),close(fd[j][1]);
}
}
}
/*execlp(cmds[0].arg[0],cmds[0].arg[0],cmds[0].arg[1],cmds[0].arg[2],cmds[0].arg[3],NULL);*/
/*结构数组中指针数组元素的第0个参数一定是命令(这是由命令行输入决定的),第二个参数是命令参数*/
execvp(cmds[i].arg[0],cmds[i].arg);
}
if(pid>0)
{
for(i=0;i<pipe_num;++i)
close(fd[i][0]),close(fd[i][1]);
for(i=0;i<cmd_num;i++)
wait(NULL);
}
}
return 0;
}


/*以空格符分开命令行字符串*/
int parse_command_line(char *buf,cmd *cmd_buf) /*cmd_buf是一个结构体指针*/
{
int i=0;
cmd_buf->in=NULL;
cmd_buf->out=NULL;
char *p=strtok(buf," ");
while(p)
{
if(*p=='>')
{
if(*(p+1))      /*如果>后面有空格,那么执行完strtok后,空格被替换成'\0',*(p+1)就是'\0',为假,不执行cmd_buf->out=p+1*/
cmd_buf->out=p+1;
else
cmd_buf->out=strtok(NULL," "); /*如果>后面没有空格,那么执行完strtok后,>符号被替换成'\0'了,直接调用strtok函数*/
}
else if(*p=='<')
{
if(*(p+1))
cmd_buf->in=p+1;
else
cmd_buf->in=strtok(NULL," ");
}
else
cmd_buf->arg[i++]=p; /*如果获取的命令行参数不是>或者<,那么就将它们保存在arg中*/
p=strtok(NULL," "); /*当提取完成时,p=NULL,跳出while循环,把命令行的所有参数分开存放到arg中了*/
}
cmd_buf->arg[i]=NULL; /*把没有赋值的指针数组元素赋值为NULL*/
return 0;
}


/*以管道符分开命令行字符串*/
int parse_pipe(char *buf,cmd cmd_s[]) /*cmd是结构数组,它的每一个元素都是一个结构体*/
{
int n=0;
char *p;
/*这里使用strtok函数是不行的,必须使用strtok_r函数,它们的区别见博客*/
char *pt=strtok_r(buf,"|",&p);
while(pt)
{
parse_command_line(pt,&cmd_s[n++]);   /*以管道符分开的第一个字符串存放在结构数组cmd_s[0]中,第二个字符串存放在结构数组cmd_s[1]中,依次递推*/
pt=strtok_r(NULL,"|",&p);
}
return n;
}


/*cd内部命令*/
int cd_command(char *cd_command,char *path)
{
int return_value=0;


if(strncmp(cd_command,"cd",2)==0)
if((return_value=chdir(path))<0)
perror("chdir");
return return_value;
}

这篇关于用c语言的实现一个简单的交互式shell的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1083970

相关文章

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被