linux中myshell的实现
目录
引言
全局信息
核心代码
核心函数
引言
随着计算机技术的飞速发展,操作系统在人们日常工作和生活中扮演着越来越重要的角色。Linux作为一款开源、自由且功能强大的操作系统,已经成为了众多服务器和开发者的首选平台。Shell作为Linux系统的用户界面,为用户提供了执行命令、管理文件、自动化任务等功能。
在本项目中,我们将实现一个简单的Shell程序——myshell。在myshell中,将模拟shell如何进程执行内建指令、普通指令与进程切换。
全局信息
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];
// 自定义环境变量表
char myenv[LINE_SIZE];
我们将特定的元素定义为宏,方便代码的理解与阅读。
核心代码
主体分为:指令的交互(打印对话栏,获取指令字符串)
指令字符串的拆解
判断是否为内建指令
不是内建指令,将进行frok执行
在循环中,不断进行交互
int main()
{
while(!quit){
// 2. 交互问题,获取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txt
interact(commandline, sizeof(commandline));
// commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"
// 3. 子串分割的问题,解析命令行
int argc = splitstring(commandline, argv);
if(argc == 0) continue;
// 4. 指令的判断
//内键命令,本质就是一个shell内部的一个函数
int n = buildCommand(argv, argc);
// 5. 普通命令的执行
if(!n) NormalExcute(argv);
}
return 0;
}
核心函数
1.交互
const char *getusername()
{
return getenv("USER");
}
const char *gethostname()
{
return getenv("HOSTNAME");
}
void getpwd()
{
getcwd(pwd, sizeof(pwd)); //#include <unistd.h> char* getcwd(char* buf, size_t size); //buf是一个输出型参数,size是buf的大小,返回值是buf指向的字符串的地址。
}
void interact(char *cline, int size)
{
getpwd();
printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);
char *s = fgets(cline, size, stdin);
assert(s);
(void)s;
// "abcd\n\0"
cline[strlen(cline)-1] = '\0';
}
2.拆解
// ls -a -l | wc -l | head
int splitstring(char cline[], char *_argv[])
{
int i = 0;
argv[i++] = strtok(cline, DELIM);
while(_argv[i++] = strtok(NULL, DELIM)); // 故意写的=
return i - 1;
}
3.普通指令
子进程执行,父进程等待
void NormalExcute(char *_argv[])
{
pid_t id = fork();
if(id < 0){
perror("fork");
return;
}
else if(id == 0){
//让子进程执行命令
//execvpe(_argv[0], _argv, environ);
execvp(_argv[0], _argv);
exit(EXIT_CODE);
}
else{
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id)
{
lastcode = WEXITSTATUS(status);
}
}
}
4.内建指令
int buildCommand(char *_argv[], int _argc)
{
if(_argc == 2 && strcmp(_argv[0], "cd") == 0){
chdir(argv[1]);
getpwd();
sprintf(getenv("PWD"), "%s", pwd);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0], "export") == 0){
strcpy(myenv, _argv[1]);
putenv(myenv);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){
if(strcmp(_argv[1], "$?") == 0)
{
printf("%d\n", lastcode);
lastcode=0;
}
else if(*_argv[1] == '$'){
char *val = getenv(_argv[1]+1);
if(val) printf("%s\n", val);
}
else{
printf("%s\n", _argv[1]);
}
return 1;
}
// 特殊处理一下ls
if(strcmp(_argv[0], "ls") == 0)
{
_argv[_argc++] = "--color";
_argv[_argc] = NULL;
}
return 0;
}
列举了export建立环境变量、echo打印、cd改变路径等信息
建立环境变量只需要
else if(_argc == 2 && strcmp(_argv[0], "export") == 0){
strcpy(myenv, _argv[1]);
putenv(myenv);
return 1;
在bash中putenv即可。
注意:
putenv只是修改了环境变量表的一个指针,让他指向了这个区域。但是这个区域改变之后,就无法获得对应的环境变量。
因此我们需要将区域固定住,额外开辟了一个myenv的空间(cline是变化的)。