用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

相关文章

Python使用python-can实现合并BLF文件

《Python使用python-can实现合并BLF文件》python-can库是Python生态中专注于CAN总线通信与数据处理的强大工具,本文将使用python-can为BLF文件合并提供高效灵活... 目录一、python-can 库:CAN 数据处理的利器二、BLF 文件合并核心代码解析1. 基础合

Python使用OpenCV实现获取视频时长的小工具

《Python使用OpenCV实现获取视频时长的小工具》在处理视频数据时,获取视频的时长是一项常见且基础的需求,本文将详细介绍如何使用Python和OpenCV获取视频时长,并对每一行代码进行深入解析... 目录一、代码实现二、代码解析1. 导入 OpenCV 库2. 定义获取视频时长的函数3. 打开视频文

golang版本升级如何实现

《golang版本升级如何实现》:本文主要介绍golang版本升级如何实现问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录golanwww.chinasem.cng版本升级linux上golang版本升级删除golang旧版本安装golang最新版本总结gola

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分

MySQL 定时新增分区的实现示例

《MySQL定时新增分区的实现示例》本文主要介绍了通过存储过程和定时任务实现MySQL分区的自动创建,解决大数据量下手动维护的繁琐问题,具有一定的参考价值,感兴趣的可以了解一下... mysql创建好分区之后,有时候会需要自动创建分区。比如,一些表数据量非常大,有些数据是热点数据,按照日期分区MululbU

MySQL中查找重复值的实现

《MySQL中查找重复值的实现》查找重复值是一项常见需求,比如在数据清理、数据分析、数据质量检查等场景下,我们常常需要找出表中某列或多列的重复值,具有一定的参考价值,感兴趣的可以了解一下... 目录技术背景实现步骤方法一:使用GROUP BY和HAVING子句方法二:仅返回重复值方法三:返回完整记录方法四:

IDEA中新建/切换Git分支的实现步骤

《IDEA中新建/切换Git分支的实现步骤》本文主要介绍了IDEA中新建/切换Git分支的实现步骤,通过菜单创建新分支并选择是否切换,创建后在Git详情或右键Checkout中切换分支,感兴趣的可以了... 前提:项目已被Git托管1、点击上方栏Git->NewBrancjsh...2、输入新的分支的

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os