1、Shell
Shell最简单的定义:命令行解释器主要包含如下:
1、将用户的命令翻译给核心(kernel)- 操作系统处理
2、同时,将核心的处理结果翻译给使用者
通过Shell,用户可以输入命令并与操作系统进行交互。Shell可以执行各种任务,如文件管理、进程控制、系统配置等。
2、Shell执行命令流程
shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:
1、获取命令行
2、解析命令行
3、建立一个子进程(fork)
4、替换子进程(execvp)
5、父进程等待子进程退出(wait)
3、编写shell
主接口如下
3.1 Interactive
接口功能描述:打印命令行提示符,获取用户输入的命令行字符串
3.2 Split
接口功能描述:对命令行字符串进行切割
3.3 Execute
接口功能描述:子进程中执行命令
3.4 BuildinCmd
接口功能描述:处理内建命令
4、代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "
char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; // for test
int lastcode = 0;
const char* HostName()
{
char *hostname = getenv("HOSTNAME");
if(hostname) return hostname;
else return "None";
}
const char* UserName()
{
char *hostname = getenv("USER");
if(hostname) return hostname;
else return "None";
}
const char *CurrentWorkDir()
{
char *hostname = getenv("PWD");
if(hostname) return hostname;
else return "None";
}
char *Home()
{
return getenv("HOME");
}
int Interactive(char out[], int size)
{
// 输出提示符并获取用户输入的命令字符串"ls -a -l"
printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
fgets(out, size, stdin);
//commandline是一个空串(只读取换行符),fgets此时读取的为‘/n(因为我们肯定会换行)
//我们需要将换行符的地方置为0,不让其换行
out[strlen(out)-1] = 0; //'\0', commandline是空串的情况?
return strlen(out);
}
void Split(char in[])
{
int i = 0;
argv[i++] = strtok(in, SEP); // "ls -a -l"
while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =
//如果命令行参数表中argv[0]位置是ls,则需自动补充--color
//目的是为了让后续的的文件或文本可以显示对应的颜色
if(strcmp(argv[0], "ls") ==0)
{
argv[i-1] = (char*)"--color";
argv[i] = NULL;
}
}
void Execute()
{
pid_t id = fork();
if(id == 0)
{
// 让子进程执行命名
execvp(argv[0], argv);
exit(1);
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id) lastcode = WEXITSTATUS(status);
//printf("run done, rid: %d\n", rid);
}
int BuildinCmd()
{
int ret = 0;
// 1. 检测是否是内建命令, 是 1, 否 0
if(strcmp("cd", argv[0]) == 0)
{
// 2. 执行
ret = 1;
char *target = argv[1]; //cd XXX or cd
//如果直接执行cd后面没有跟路径,则需要跳到该用户所对应家目录。
if(!target) target = Home();
chdir(target);
//当前路径是发生了改变的,但是前面的命令提示符上面的路径没有改变,
//命令提示符提取的是环境变量,即环境变量没有发生变化
//故,当前路径发生改变时,同步更新环境变量,才可保证命令行提示符上的路径与我们当前路径保持一致。
char temp[1024];
getcwd(temp, 1024);
snprintf(pwd, SIZE, "PWD=%s", temp);
putenv(pwd);
}
else if(strcmp("export", argv[0]) == 0)
{
ret = 1;
if(argv[1]) //argv[1]所对应的环境变量存在
{
//这里必须要先将argv[1]所指向的环境变量保存,然后在putenv
//如果是直接put(argv[1]),则由于argv[1]指向的是命令行参数
//当我们的命令行参数发生变化时,argv[1]中指向的内容也会发生变化,导致环境变量被覆盖
strcpy(env, argv[1]);
putenv(env);
}
}
else if(strcmp("echo", argv[0]) == 0)
{
ret = 1;
//命令行echo的几种情况 1、echo 2、echo$? 3、echo$HOME / 即echo$环境变量 4、echo 字符串
if(argv[1] == NULL) {
printf("\n");
}
else{
if(argv[1][0] == '$')
{
if(argv[1][1] == '?')
{
printf("%d\n", lastcode);
lastcode = 0;
}
else{
char *e = getenv(argv[1]+1);
if(e) printf("%s\n", e);
}
}
else{
printf("%s\n", argv[1]);
}
}
}
return ret;
}
int main()
{
while(1)
{
char commandline[SIZE];
// 1. 打印命令行提示符,获取用户输入的命令字符串
int n = Interactive(commandline, SIZE);
if(n == 0) continue;
// 2. 对命令行字符串进行切割
Split(commandline);
// 3. 处理内建命令
n = BuildinCmd();
if(n) continue;
// 4. 执行这个命令
Execute();
}
return 0;
}