目录
补充
一.引入
二.自助微型shell
1:输出一个命令行
2:获取用户命令字符串
3:命令行字符串的分割
4:先试着执行一下命令
5:关键点:需要在执行命令前检查是否为内建命令
其他:
三:完整代码
补充
二号手册的是系统调用,而上面的程序替换有关的函数都是三号手册,即不是系统调用
实际上是由我们Linux支持的标准GNU C语言
一.引入
前面我们写的关于程序替换的一个小代码:
进程创建子进程,然后由子进程去执行命令,1.子进程可以执行系统当中的任意一条命令,包括我们自己写的可执行程序;2.子进程在执行命令的时候,执行一次就结束了那么如果让它一直可以执行命令,那它不就是个shell吗??
二.自助微型shell
所谓的shell,就是可以一直去执行,而且执行各种命令,所以要自定义写个shell的话,本质上就是要去做命令行字符串的解析,还要打循环不断地去创建进程执行命令
我们输入的指令其实本质就是一段字符串,需要bash去解析然后执行
如: --- > "ls -a -l"
1:输出一个命令行
输出一个类似这个的命令行,我们就需要去取得用户名、主机名以及路径
而这些东西刚好可以从env环境变量中取得,通过getenv函数
先写获取这些字符串的接口,最后到一个Print函数中去引用这些接口得到值后再打印就好了
2:获取用户命令字符串
为什么这里printf中我只输入了一个\n换行符号,却出现的一个空行呢???
因为在输入字符串时,在最后一下按了回车键,导致fgets读取了,然后由自己加了一个\n,因此换行了两次
解决方法:把尾部的\n换成\0:(这里define了ZERO为\0)
3:命令行字符串的分割
若果要以空格分隔的话,第二个参数应该传" ",而不是' ',因为其类型是char*
故意写成 = ,表示先赋值,再判断,分割完了之后,strtok就会返回NULL,刚好让gArgv最后一个元素是NULL,并且while判断结束
这个方法很巧妙
4:先试着执行一下命令
errno是一个全局变量,会记录最近一次进程执行的退出码
5:关键点:需要在执行命令前检查是否为内建命令
比如cd这个命令,如果按照原来的逻辑去执行的话,那么他只是子进程在cd,父进程却没动
因此我们又要提及到内建命令
其他:
系统的是lessen12 而我们写的是整个路径,如何改善呢??
#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)
这里SkipPath的作用是跳过前面所有的字符,直到最后一个 /lessen12
三:完整代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)
// 定义到一起 易观
char cwd[SIZE*2];
char *gArgv[NUM];
int lastcode = 0;
void Die()
{
exit(1);
}
const char *GetUserName()
{
const char* name = getenv("USER");
if(name == NULL) return "None";
return name;
}
const char *GetHostName()
{
const char *hostname = getenv("HOSTNAME");
if(hostname == NULL) return "Node";
return hostname;
}
const char *GetCwd()
{
const char *cwd = getenv("PWD");
if(cwd == NULL) return "None";
return cwd;
}
void MakeCommandLineAndPrint(char line[], size_t size)
{
const char *username = GetUserName();
const char *hostname = GetHostName();
const char *cwd = GetCwd();
SkipPath(cwd);
snprintf(line, size, "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);
printf("%s", line);
fflush(stdout);
}
int GetUserCommand(char command[], size_t n)
{
char *s = fgets(command, n, stdin);
if(s == NULL) return -1;
command[strlen(command) - 1] = ZERO;
return strlen(command);
}
void SplitCommand(char command[], size_t n)
{
(void)n;
// "ls -a -l -n" -> "ls" "-a" "-l" "-n"
gArgv[0] = strtok(command, SEP);
int index = 1;
while((gArgv[index++] = strtok(NULL, SEP))); // done, 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL, 并且while判断结束
}
void ExecuteCommand()
{
pid_t id = fork();
if(id < 0) Die();
else if(id == 0)
{
// child
execvp(gArgv[0], gArgv);
exit(errno);
}
else
{
// fahter
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
lastcode = WEXITSTATUS(status);
if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
}
}
}
const char *GetHome()
{
const char* home = getenv("HOME");
if(home == NULL) return "/";
return home;
}
void Cd()
{
const char *path = gArgv[1];
if(path == NULL) path = GetHome();
// path 一定存在
chdir(path);
// 刷新环境变量
char temp[SIZE*2];
getcwd(temp, sizeof(temp));
snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
putenv(cwd);
}
int CheckBuildin()
{
int yes = 0;
const char *enter_cmd = gArgv[0];
if(strcmp(enter_cmd, "cd") == 0)
{
yes = 1;
Cd();
}
else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
{
yes = 1;
printf("%d\n", lastcode);
lastcode = 0;
}
return yes;
}
int main()
{
int quit = 0;
while(!quit)
{
// 1.输出一个命令行
char commandline[SIZE];
MakeCommandLineAndPrint(commandline, sizeof(commandline));
// 2.获取用户命令字符串
char usercommand[SIZE];
int n = GetUserCommand(usercommand, sizeof(usercommand));
// 3.命令行字符串的分割
SplitCommand(usercommand, sizeof(usercommand));
// 4. 检测命令是否是内建命令
n = CheckBuildin();
if(n) continue;
// n.执行命令
ExecuteCommand();
printf("echo : %s\n", usercommand);
}
return 0;
}