用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 Boot整合Redis注解实现增删改查功能(Redis注解使用)

《SpringBoot整合Redis注解实现增删改查功能(Redis注解使用)》文章介绍了如何使用SpringBoot整合Redis注解实现增删改查功能,包括配置、实体类、Repository、Se... 目录配置Redis连接定义实体类创建Repository接口增删改查操作示例插入数据查询数据删除数据更

Java Lettuce 客户端入门到生产的实现步骤

《JavaLettuce客户端入门到生产的实现步骤》本文主要介绍了JavaLettuce客户端入门到生产的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录1 安装依赖MavenGradle2 最小化连接示例3 核心特性速览4 生产环境配置建议5 常见问题

linux ssh如何实现增加访问端口

《linuxssh如何实现增加访问端口》Linux中SSH默认使用22端口,为了增强安全性或满足特定需求,可以通过修改SSH配置来增加或更改SSH访问端口,具体步骤包括修改SSH配置文件、增加或修改... 目录1. 修改 SSH 配置文件2. 增加或修改端口3. 保存并退出编辑器4. 更新防火墙规则使用uf

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础