Linux C实现简单的shell

Linux C下实现简单的Shell


宗旨:技术的学习是有限的,分享的精神是无限的。


【需求描述】

用各种C函数实现一个简单的交互式Shell

1给出提示符,让用户输入一行命令,识别程序名和参数并调用适当的exec函数执行程序,待执行完成后再次给出提示符。

2、识别和处理以下符号:简单的标准输入输出重定向( <>),dup2然后exec管道(|): Shell进程先调用pipe创建一对管道描述符,然后fork出两个子进程,一个子进程关闭读端,调用dup2把写端赋给标准输出,另一个子进程关闭写端,调用dup2把读端赋给标准输入,两个子进程分别调用exec执行程序,而Shell进程把管道的两端都关闭,调用wait待两个子进程终止。程序应该可以处理以下命令:
○ls
-l-R○>○file1○
○cat○<○file1○|○wc
-c○>○file1○
表示零个或多个空格,表示一个或多个空格

 

//=====================================================================

一、结构定义

       用户输入一行命令,将分解后的命令存放在arg中,最多10个。之所以定义输入、输出重定向文件名是为了方便使用。还要余外处理重定向和管道问题。必须创建子进程(有多少命令就有多少个子进程),然后调用exec函数族。

typedef struct command
{char *arg[SHELL_CMD_MAX_LENGTH]; // 命令行参数,最多10个参数char *input_file; // 存放输入重定向的文件名char *output_file; // 存放输出重定向的文件名
} cmd_t;<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

二、接口定义

管道处理用strtok_r()函数,重定向处理使用strtok()函数,仔细研究一下他们的区别。

1、管道个数【管道的个数 =(命令个数 1)】

static uint8_t shell_parse_pipe(char *data, cmd_tcmd[]);

2、重定向

static uint8_t shell_parse_cmd_line(char *data,cmd_t *cmd);

填充命令结构体中的各个成员,输入输出填充input_file,output_file成员,命令填充arg成员。命令成员的最后一个字符指向NULL。

3、内建命令的单独处理 --- cd【内建命令都需要一个个处理 --- 如type cd查看命令是内建还是外部shell】

int shell_cd_command(char *command, char *path);

 

/*
只能处理外部命令,内建命令不能处理,内建命令要一个一个单独实现
type命令可以查看命令是外部命令还是内建命令:type cd // 是shell的内建
*/#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>#define SHELL_CMD_MAX_COUNT        10
#define SHELL_CMD_MAX_LENGTH       20
#define SHELL_IN_OUT_FILE_MAX_SIZE 20
#define SHELL_BUFFER               64
#define SHELL_PIPE_MAX_COUNT       10
#define SHELL_PIPE_READ_WRITE      2typedef unsigned int uint32_t;
typedef unsigned char uint8_t;typedef struct command
{char *arg[SHELL_CMD_MAX_LENGTH]; // 命令行参数,最多10个参数char *input_file; // 存放输入重定向的文件名char *output_file; // 存放输出重定向的文件名
} cmd_t;/* 以空格符分开命令行字符串 */
static uint8_t
shell_parse_cmd_line(char *data, cmd_t *cmd)
{uint32_t i = 0;cmd->input_file = NULL;cmd->output_file = NULL;char *token = strtok(data, " ");while(token){/*如果>后面有空格,那么执行完strtok后,空格被替换成'\0',*(p+1)就是'\0',为假,不执行cmd_buf->out=p+1*//*如果>后面没有空格,那么执行完strtok后,>符号被替换成'\0'了,直接调用strtok函数*/if(*token == '>'){if(*(token + 1)){cmd->output_file = token + 1;}else{cmd->output_file = strtok(NULL, " ");}}else if(*token == '<'){if(*(token + 1)){cmd->input_file = token + 1;}else{cmd->input_file = strtok(NULL, " ");}}else{printf("token....\n");/*如果获取的命令行参数不是>或者<,那么就将它们保存在arg中*/cmd->arg[i++] = token;}token = strtok(NULL, " ");}cmd->arg[i] = NULL;return 0;
}/* 以管道符分开命令行字符串 */
static uint8_t
shell_parse_pipe(char *data, cmd_t cmd[])
{uint8_t count = 0;char *temp;char *token = strtok_r(data, "|",  &temp);while(token){/*以管道符分开的第一个字符串存放在结构数组cmd[0]中,第二个字符串存放在结构数组cmd[1]中,依次递推*/shell_parse_cmd_line(token, &cmd[count++]);token = strtok_r(NULL, "|", &temp);}return count;
}/* cd内部命令 */
int shell_cd_command(char *command, char *path)
{int return_value = 0;if(strncmp(command, "cd", 2) == 0)if((return_value = chdir(path)) < 0){perror("chdir");}return return_value;
}static void
shell_process(void)
{char cmd_buf[SHELL_BUFFER], pathname[SHELL_BUFFER];cmd_t cmds[SHELL_CMD_MAX_COUNT];pid_t pid; // 进程pid/* 输入输出重定向文件描述符, 管道个数, 10个管道描述符, 管道分隔的命令个数*/uint8_t fd_in, fd_out, pipe_num, pipe_fd[SHELL_PIPE_MAX_COUNT][SHELL_PIPE_READ_WRITE], cmd_num = 0;uint8_t i = 0, j = 0;while(1){memset(pathname, 0, sizeof(pathname));getcwd(pathname, sizeof(pathname)); /* 获取当前工作路径 */printf("[libang--%s--]$", pathname);fflush(stdout); /* 刷新缓冲区,这连续四行只是为了显示好看而已,不要也可以 */memset(cmd_buf, 0, sizeof(cmd_buf));fgets(cmd_buf, sizeof(cmd_buf), stdin);cmd_buf[strlen(cmd_buf) - 1] = '\0'; // 获取输入的命令到cmd_bufcmd_num = shell_parse_pipe(cmd_buf, cmds);shell_cd_command(cmds[0].arg[0], cmds[0].arg[1]); // 处理cd内建命令pipe_num = cmd_num - 1;if(pipe_num > SHELL_PIPE_MAX_COUNT){continue;}/* 一个管道有in和out, 创建pipe_num个管道 */for(i = 0; i < pipe_num; ++i){pipe(pipe_fd[i]);}/* 这一轮循环,创建了pipe_num+1个进程,其中一个父进程,pipe_num个子进程 */for(i = 0; i < cmd_num; ++i){if((pid = fork()) < 0){printf("fork fail!\n");exit(1);}if(pid == 0){break;}}/* 有多少个命令就会执行多少个子进程,最终调用exec函数族 */if(pid == 0){/* 上面循环中,子进程break,所以执行下面的语句,此时i就和循环变量i一样 */if(cmds[i].input_file){/* 重定向输入 */if((fd_in = open(cmds[i].input_file, O_RDONLY)) < 0){perror("open fail!\n");}dup2(fd_in, STDIN_FILENO);close(fd_in);}if(cmds[i].output_file){/* 重定向输出 */if((fd_out = open(cmds[i].output_file, O_RDWR | O_CREAT | O_TRUNC), 0644) < 0){perror("open fail!\n");}dup2(fd_out, STDOUT_FILENO);close(fd_out);}/* 管道是进程间通信的一种方式,输入命令中有管道 */if(pipe_num){/* 第一个子进程,读入写出,关闭读端,把标准输出重定向到写端*/if(0 == i){close(pipe_fd[i][0]);dup2(pipe_fd[i][1], STDOUT_FILENO); // 本来执行结果是在标准输出上close(pipe_fd[i][1]);/* 关闭掉多余的管道 */for(j = 1; j < pipe_num; ++j){close(pipe_fd[j][0]);close(pipe_fd[j][1]);}}/* 最后一个子进程,关闭写端,把标准输入重定向到读端 */else if(pipe_num == i){close(pipe_fd[i - 1][0]);dup2(pipe_fd[i - 1][0], STDIN_FILENO);close(pipe_fd[i - 1][0]);/* 关闭掉多余的管道读写端 */for(j = 0; j < pipe_num - 1; ++j){close(pipe_fd[j][0]);close(pipe_fd[j][1]);}}/* 1~pipe_num-1, */else{dup2(pipe_fd[i - 1][0], STDIN_FILENO);close(pipe_fd[i - 1][0]);dup2(pipe_fd[i][1], STDOUT_FILENO);close(pipe_fd[i][1]);for(j = 0; j < pipe_num; ++j){if((j != i - 1) || j != i){close(pipe_fd[j][0]);close(pipe_fd[j][1]);}}}}/* arg第1个参数是命令,后面的参数是命令选项如:-l */execvp(cmds[i].arg[0], cmds[i].arg);}else // 父进程阻塞{for(i = 0; i < pipe_num; ++i){close(pipe_fd[i][0]);close(pipe_fd[i][1]);}for(i = 0; i < cmd_num; ++i){wait(NULL);}}}
}int main(int argc, char **argv)
{shell_process();return 0;
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

上拉电阻的作用

1、介绍使用微控制器&#xff08;MCUs&#xff09; 或任何数字逻辑设备时&#xff0c;上拉电阻器非常常见。本教程将解释何时何地使用上拉电阻器&#xff0c;然后我们将做一个简单的计算&#xff0c;以显示为什么上拉很重要。2、上拉电阻是什么&#xff1f;假设您有一个将一个引…

NILMTK——深扒组合优化(CO)和FHMM细节

前面的博客讲了具体实现&#xff0c;现在深究算法代码实现细节&#xff01;&#xff01;&#xff01; 1.CO (1)关于train 从以下代码可知&#xff0c;CO首先是对各个电器的功率数据做了train&#xff0c;为了了解其原生实现对代码进行了深究&#xff1a; classifiers {CO:…

Python关键字

and  as  assert  break  class  continue   def  del  elif  else  except  exec  finally   for  from  global  if  import  in  is  lambda not  or  pass  print  raise  return  try while  with  yield  Non…

对51CTO的初步看法

决定给自己见一个技术博客之后&#xff0c;在网上搜了一下&#xff0c;发现了51CTO网站&#xff0c;进入之后发现网速够快&#xff0c;有尝试了博客的功能&#xff0c;也基本满足了我的要求&#xff0c;那就是它了&#xff0c;于是我就在51CTO安家了。写了两片共近千字的文章之…

深圳工资指导价出炉!最高月薪6万!你拖同行后腿了吗?

2020 年只剩下不到一个月了&#xff0c;年初立的 flag 有没有实现呢&#xff1f;我想多数人面临的尴尬是升职、加薪、赢取白富美、走上人生巅峰可能一步都没实现~对比周围混得风生水起的小伙伴感觉自己也不差啥啊&#xff0c;怎么就莫名其妙被甩了八条街&#xff1f;想一探究竟…

NILMTK——因子隐马尔可夫之隐马尔可夫

因子隐马尔可夫(FHMM)由Ghahramani在1997年提出&#xff0c;是一种多链隐马尔可夫模型&#xff0c;适合动态过程时间序列的建模&#xff0c;并具有强大的时序模型的分类能力&#xff0c;特别适合非平稳、再现性差的序列的分析。 1. 马尔可夫链 随机过程的研究对象是随时间演变…

CodeForces 903D Almost Difference

题目描述 Lets denote a function You are given an array aa consisting of nn integers. You have to calculate the sum of d(a_{i},a_{j})d(ai​,aj​) over all pairs (i,j)(i,j) such that 1<i<j<n1<i<j<n . 输入输出格式 输入格式&#xff1a; The fi…

如何使用资源文件

摘要.NET 中有一套非常完善的地方化系统被定义在 System.Resources 名字空间中。不过大多数人都被 MissingManifestResourceException 这个错误困惑着。本文就是要让大家了解什么是资源文件&#xff0c;它有什么用处以及如何正确的调用从而避免一些"奇怪"的错误&…

据悉,深圳某工程师沦为C语言笔试枪手

事情是这样的&#xff0c;昨晚晚上&#xff0c;有个网友发消息给我&#xff0c;说他有几道C语言笔试题不会写&#xff0c;所以&#xff0c;就出现了解题的这一幕。文章中&#xff0c;我只讲解了一部分&#xff0c;有一些题目觉得没必要讲&#xff0c;然后我在pdf上做了注释&…

大数据工具使用——安装Hadoop(多台服务器)和Hive、Hbase

1.配置环境版本 资料上传百度云&#xff0c;自取&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1evVp5Zk0_X7VdjKlHGkYCw 提取码&#xff1a;ypti 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 &#xff08;之前安装的是apache版本的Hadoop2.6.4,在启…

进程间通信——信号

进程间通信——信号 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 一、信号和中断 1、信号基本概念 &#xff08;1&#xff09;发送信号&#xff1a;产生信号&#xff0c;有多种发送信号的方式【一个进程到另一个进程&#xff0c;内核向用户&#x…

[转] 关于 WCF 中数据压缩的几篇文章

原文:http://www.cnblogs.com/jiabao/archive/2007/12/04/982534.html在.net3.0出现以前我们进行分布式开发式有两个选择一个是webservice&#xff0c;另一个是remoting&#xff1b;在早期的项目中&#xff0c;比较喜欢remoting&#xff0c;因为remoting可控性好&#xff0c;也…

聊一聊我自己的从业经历和感悟

嵌入式学习&#xff0c;是一个很枯燥的过程&#xff0c;我记得在学习三极管的时候&#xff0c;我真的对这个东西一点感觉都没有&#xff0c;我知道三极管可以放大&#xff0c;然后电子从一个地方去到了另一个地方&#xff0c;然后就触发了某个开关&#xff0c;就发了大水。然后…

gmake与make的区别

gnu make在linux下一般是叫make但是如果是在其他的unix系统下&#xff0c;因为有一个原生的makegnu make就改个名字叫gmake了。就这们简单当port一个老的unix程序&#xff0c;如老的SunOS上的程序时往往需要sed s/gmake/make/ggmake是GNU Make的缩写。Linux系统环境下的make就是…

大数据——sqoop操作mysql和hive导出导入数据

1.sqoop安装 &#xff08;1&#xff09;下载CDH版本的sqoop &#xff08;2&#xff09;解压并进行环境配置 环境变量为&#xff1a; export SQOOP_HOME/home/sqoop-1.4.6-cdh5.15.1 export PATH$PATH:$SQOOP_HOME/bin 在sqoop安装目录/conf/下&#xff1a; #新建sqoop-en…

LinuxC高级编程——线程

LinuxC高级编程——线程 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 一、线程基础 main函数和信号处理函数是同一个进程地址空间中的多个控制流程&#xff0c;多线程也是如 此&#xff0c;但是比信号处理函数更加灵活&#xff0c;信号处理函数的控制…

来自专业的RIA咨询strechmedia机构提供的Flex组件

具体内容见这里&#xff0c;其中最有用的是chart range selection组件&#xff0c;可以用作历史数据浏览和分析&#xff0c;不光能用slider来选择查看的范围&#xff0c;还能控制范围的大小&#xff0c;而且通过图形也能对range selection进行反向操作&#xff0c;非常酷&#…

年终了,肿一下

也没有没有跟大家好好唠唠&#xff0c;一年时间过得飞快&#xff0c;我还记得那时候从老家开车来深圳&#xff0c;一路狂奔&#xff0c;在广西入境广东的时候&#xff0c;因为疫情排查&#xff0c;我们在那里堵了3个小时&#xff0c;还因为路途颠簸&#xff0c;车子一起一停&am…

大数据——spark安装部署和python环境配置

需要配置多台服务器&#xff0c;实验环境&#xff1a;master和data两台服务器&#xff0c;已安装好hadoop&#xff0c;可参考前文&#xff01;&#xff01;&#xff01; 1.spark安装 master安装 &#xff08;1&#xff09;下载scala和spark &#xff08;2&#xff09;解压并…

LinuxC高级编程——线程间同步

LinuxC高级编程——线程间同步 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 1、 互斥锁mutex 多个线程同时访问共享数据时可能会冲突。对于多线程的程序&#xff0c;访问冲突的问题是很普遍的&#xff0c;解决的办法是引入互斥锁&#xff08;Mutex&a…