🔥个人主页:guoguoqiang. 🔥专栏:Linux的学习
自主Shell实现
根据之前所学的内容,模拟实现一个Shell实现,一个shell程序需要循环做一下事情:
1.获取命令行
2.解析命令行
3.创建子程序
4.替换子程序
5.父进程等待子进程退出
获取基本变量
我们可以在env查看我们需要的变量,以及使用getenv()获取我们需要的变量
//获取用户名
const char* GetUserName(){
const char* name = getenv("USER");
if(name == NULL) return "None";
return name;
}
const char* GetHome(){
const char* home = getenv("HOME");
if(home == NULL) return "/";
return home;
}
const char* GetHostName(){
const char* hostname = getenv("HOSTNAME");
if(hostname == NULL) return "None";
return hostname;
}
const char* GetCwd(){
const char* cwd = getenv("PWD");
if(cwd == NULL) return "None";
return cwd;
}
实现命令行
我们要把env获取的内容拼接成一个字符串
#define SIZE 512
#define SkipPath(p) do{ p+= (strlen(p)-1); while(*p != '/') p--; }while(0)
void MakeCommandLineAndPrint(){
char line[SIZE];
const char* username = GetUserName();
const char* hostname = GetHostName();
const char* cwd = GetCwd();
//只保留相对路径
SkipPath(cwd);
//写入指定缓冲区
snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);
//打印命令行
printf("%s",line);
fflush(stdout);
}
获取用户命令字符串
#define SIZE 512
#define ZERO '\0'
//获取用户指令
int GetUserCommand(char command[], size_t n){
char* s = fgets(command, n, stdin);
if(s == NULL) return -1;
//由于fgets也会获取我们输入的回车换行符
//为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'
command[strlen(command)-1] = ZERO;
return strlen(command);
}
命令行字符串分割
认识字符串分割函数
#define SEP " "
#define NUM 32
#define SkipSpace(cmd, pos) do{\
while(1){\
if(isspace(cmd[pos]))\
pos++;\
else break;\
}\
}while(0)
char *gArgv[NUM];
//分割命令
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;
//故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL
//刚好让gArgv最后一个元素是NULL, 并且while判断结束
while((gArgv[index++] = strtok(NULL,SEP)));
}
检测是否为内建命令
char *gArgv[NUM];
int lastcode = 0;
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;
}
检查是否为重定向
void CheckRedir(char cmd[])
{
// > >> <
// "ls -a -l -n > myfile.txt"
int pos = 0;
int end = strlen(cmd);
while(pos < end)
{
if(cmd[pos] == '>')
{
if(cmd[pos+1] == '>')
{
cmd[pos++] = 0;
pos++;
redir_type = App_Redir;
SkipSpace(cmd, pos);
filename = cmd + pos;
}
else
{
cmd[pos++] = 0;
redir_type = Out_Redir;
SkipSpace(cmd, pos);
filename = cmd + pos;
}
}
else if(cmd[pos] == '<')
{
cmd[pos++] = 0;
redir_type = In_Redir;
SkipSpace(cmd, pos);
filename = cmd + pos;
}
else
{
pos++;
}
}
}
执行命令
void ExecuteCommand()
{
pid_t id = fork();
if(id < 0) Die();
else if(id == 0)
{
//重定向设置
if(filename != NULL){
if(redir_type == In_Redir)
{
int fd = open(filename, O_RDONLY);
dup2(fd, 0);
}
else if(redir_type == Out_Redir)
{
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
dup2(fd, 1);
}
else if(redir_type == App_Redir)
{
int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
dup2(fd, 1);
}
else
{}
}
// 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);
}
}
}
主函数设置
int main(){
int quit = 0;
while(!quit){
//1.我们需要自己输出一个命令行
MakeCommandLineAndPrint();
//2.获取用户命令字符串
char usercommand[SIZE];
int n = GetUserCommand(usercommand, sizeof(usercommand));
if(n <= 0) return 1;
//检测是否为重定向
CheckRedir(usercommand);
// 3. 命令行字符串分割.
SplitCommand(usercommand, sizeof(usercommand));
//4.检测是否为内建命令
n = CheckBuildin();
if(n) continue;
// 5. 执行命令
ExecuteCommand();
}
return 0;
}
测试
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.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)
#define SkipSpace(cmd, pos) \
do \
{ \
while (1) \
{ \
if (isspace(cmd[pos])) \
pos++; \
else \
break; \
} \
} while (0)
// "ls -a -l -n > myfile.txt"
#define None_Redir 0
#define In_Redir 1
#define Out_Redir 2
#define App_Redir 3
int redir_type = None_Redir;
char *filename = NULL;
// 为了方便,我就直接定义了
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 *GetHome()
{
const char *home = getenv("HOME");
if (home == NULL)
return "/";
return home;
}
const char *GetHostName()
{
const char *hostname = getenv("HOSTNAME");
if (hostname == NULL)
return "None";
return hostname;
}
const char *GetCwd()
{
const char *cwd = getenv("PWD");
if (cwd == NULL)
return "None";
return cwd;
}
void MakeCommandLineAndPrint()
{
char line[SIZE];
const char *username = GetUserName();
const char *hostname = GetHostName();
const char *cwd = GetCwd();
// 只保留相对路径
SkipPath(cwd);
// 写入指定缓冲区
snprintf(line, sizeof(line), "[%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;
// 由于fgets也会获取我们输入的回车换行符
// 为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'
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;
// 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL
// 刚好让gArgv最后一个元素是NULL, 并且while判断结束
while ((gArgv[index++] = strtok(NULL, SEP)))
;
}
void ExecuteCommand()
{
pid_t id = fork();
if (id < 0)
Die();
else if (id == 0)
{
// 重定向设置
if (filename != NULL)
{
if (redir_type == In_Redir)
{
int fd = open(filename, O_RDONLY);
dup2(fd, 0);
}
}
else if (redir_type == Out_Redir)
{
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
dup2(fd, 0);
}
else if (redir_type == App_Redir)
{
int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
dup2(fd, 1);
}
else
{
}
// child
execvp(gArgv[0], gArgv);
exit(errno);
}
else
{
// father
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
// wait success
}
}
}
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); // OK
}
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;
}
void CheckRedir(char cmd[])
{
// > >> <
// "ls -a -l -n > myfile.txt"
int pos = 0;
int end = strlen(cmd);
while (pos < end)
{
if (cmd[pos] == '>')
{
// 是否为追加重定向
if (cmd[pos + 1] == '>')
{
// 是追加重定向
cmd[pos++] = 0;
pos++;
redir_type = App_Redir;
SkipSpace(cmd, pos);
filename = cmd + pos;
}
else
{
// 是输出重定向
cmd[pos++] = 0;
redir_type = Out_Redir;
SkipSpace(cmd, pos);
filename = cmd + pos;
}
}
else if (cmd[pos] == '<') // 是输入重定向
{
cmd[pos++] = 0;
redir_type = In_Redir;
SkipSpace(cmd, pos);
filename = cmd + pos;
}
else
{
pos++;
}
}
}
int main()
{
int quit = 0;
while (!quit)
{
// 1.我们需要自己输出一个命令行
MakeCommandLineAndPrint();
// 2.获取用户命令字符串
char usercommand[SIZE];
int n = GetUserCommand(usercommand, sizeof(usercommand));
if (n <= 0)
return 1;
// 检测是否为重定向
CheckRedir(usercommand);
// 3. 命令行字符串分割.
SplitCommand(usercommand, sizeof(usercommand));
// 4.检测是否为内建命令
n = CheckBuildin();
if (n)
continue;
// 5. 执行命令
ExecuteCommand();
}
return 0;
}