#include "myshell.h"
// 命令分割的分隔符(实现内部使用,不对外暴露)
#define SEP " "
// 全局变量定义
char buf[MaxBuf_SIZE];
char *g_argv[MAXARGV_SIZE];
// 定义分隔符的数量
int g_argc;
// 判定是否出现重定向操作
int g_redir = NONE_REDIR;
char filename[MAXFN_SIZE];
// 获取用户名
const char *GetUserName()
{
const char *name = getenv("USER");
return name == NULL ? "None" : name;
}
// 获取主机名
const char *GetHostName()
{
static char hostname[128];
if (gethostname(hostname, sizeof(hostname)) != 0)
return "None";
hostname[sizeof(hostname) - 1] = '\0';
return hostname;
}
// 获取当前目录的路径
// getenv("PWD")读取的是进程启动时从父进程继承的环境变量。它只是一份静态的快照,不会因为你调用了 chdir() 而自动更新。
const char *GetPwd()
{
// const char* pwd=getenv("PWD");
const char *pwd = getcwd(buf, sizeof(buf));
return pwd == NULL ? "None" : pwd;
}
// 只显示当前用户路径,不显示全路径
const char *DirName(const char *route)
{
if (!route || *route == '\0')
return ".";
const char *slash = strrchr(route, '/');
if (slash == route && *(slash + 1) == '\0')
return "/";
return slash ? slash + 1 : route;
}
// 将命令行提示词输出到 字符串cmd_prompt
void MakeCommandLine(char cmd_prompt[], int size)
{
snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()));
}
// 打印命令行提示词
void PrintCommandPrompt()
{
char prompt[COMMAND_SIZE];
MakeCommandLine(prompt, sizeof(prompt));
printf("%s", prompt);
fflush(stdout);
}
// 获取用户输入的命令
int GetCommandLine(char *out, int size)
{
// 获取用户输入的指令
char *str = fgets(out, size, stdin);
if (str == NULL)
return 0;
// 清理\n
str[strlen(str) - 1] = '\0';
return 1;
}
// 分割命令行参数 "ls -l -a" -> "ls" "-l" "-a"
int ParseCommandLine(char *commandline)
{
g_argc = 0;
char *token = strtok(commandline, SEP);
while (token != NULL && g_argc < MAXARGV_SIZE)
{
g_argv[g_argc++] = token;
token = strtok(NULL, SEP);
}
g_argv[g_argc] = NULL;
return g_argc > 0 ? 1 : 0;
}
// 判定是否出现重定向操作
void RedirCheck(char *commandline)
{
// 初始化操作
g_redir = NONE_REDIR;
filename[0] = '\0';
char *p; // 遍历字符串的指针
char *name_start; // 文件名的起始位置
int name_len;
if (commandline == NULL)
{
return;
}
p = commandline;
while (*p != '\0')
{
// 处理单引号' 和 双引号 "
if (*p == '"' || *p == '\'')
{
// 记录引号的类型
char quote = *p;
// 寻找闭合的引号,防止出现>在输入的字符串中: echo "hello > world"
p++;
while (*p != '\0' && *p != quote)
{
p++;
}
// 找到闭合引号,进行寻找 重定向符号 > 、 >> 和 <
if (*p != '\0')
p++;
continue;
}
if (*p == '<')
{
g_redir = INPUT_REDIR;
*p = '\0';
p++;
break;
}
if (*p == '>')
{
*p = '\0'; // 截断原字符串 commandline
// 在输入重定向>的基础上,进一步检测是否为追加重定向 >>
p++;
if (*p == '>')
{
g_redir = APPEND_REDIR;
p++;
}
else
{
g_redir = OUTPUT_REDIR;
}
break;
}
p++;
}
// 如果没有找到重定向符号,直接提前返回
if (g_redir == NONE_REDIR)
{
return;
}
// 清除空格
// echo "hello" > filename
while (*p != '\0' && isspace((unsigned char)*p))
{
p++;
}
if (*p == '\0')
{
fprintf(stderr, "语法错误: 重定向符号后缺少文件名\n");
g_redir = NONE_REDIR;
return;
}
// 获取文件名
// 向后扫描,直到遇到空格或字符串结尾,确定文件名的结束位置
name_start = p;
while (*p != '\0' && !isspace((unsigned char)*p))
{
p++;
}
// 文件名的长度
name_len = (int)(p - name_start);
// 如果文件名的长度超过了缓冲区的长度,则拷贝字符数最多为filename长度-1,需要留一个字符'\0'
if (name_len >= (int)sizeof(filename))
{
name_len = (int)sizeof(filename) - 1;
}
// 拷贝到filename中
strncpy(filename, name_start, name_len);
filename[name_len] = '\0';
*name_start = '\0';
}
// 测试字符串重定向符号和filename是否被正确分割
void TestRedirCheck()
{
printf("g_redir: %d,filename: %s\n", g_redir, filename);
}
// 测试字符串分割
void TestCmd()
{
for (int i = 0; i < g_argc; i++)
{
printf("g_argv[%d]->%s\n", i, g_argv[i]);
}
printf("g_argc:%d\n", g_argc);
}
// cd命令的执行
void Execd()
{
const char *path = g_argv[1];
if (path == NULL) // 1.执行cd命令
{
path = getenv("HOME");
if (path == NULL)
{
fprintf(stderr, "cd: HOME 环境变量未设置\n");
return;
}
}
else if (strcmp(path, "-") == 0) // 2.执行cd - 命令
{
path = getenv("OLDPWD");
if (path == NULL)
{
fprintf(stderr, "cd: OLDPWD 未设置\n");
return;
}
// 回显当前路径
printf("%s\n", path);
}
// 3.执行 cd <path> 命令
// 记录旧目录
char oldpwd[4096];
if (getcwd(oldpwd, sizeof(oldpwd)) == NULL)
{
oldpwd[0] = '\0';
}
// 更新当前目录路径
if (chdir(path) != 0)
{
perror("cd");
return;
}
// 更新环境变量
char newpwd[4096];
if (oldpwd[0] != '\0')
{
setenv("OLDPWD", oldpwd, 1);
}
if (getcwd(newpwd, sizeof(newpwd)) != NULL)
{
setenv("PWD", newpwd, 1);
}
}
// 判断内键命令,例如:"cd"
int CheckAndExecBuiltin()
{
const char *cmd = g_argv[0];
if (cmd == NULL)
return 0;
if (strcmp(cmd, "cd") == 0)
{
Execd();
return 1;
}
return 0;
}
// 执行命令
int Execute()
{
pid_t id = fork();
if (id < 0)
{
perror("fork fail");
exit(1);
}
else if (id == 0)
{
int fd = -1;
if (g_redir == INPUT_REDIR)
{
fd = open(filename, O_RDONLY, 0666);
if (fd < 0)
exit(1);
dup2(fd, 0);
close(fd);
}
else if (g_redir == OUTPUT_REDIR)
{
fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd < 0)
exit(2);
dup2(fd, 1);
close(fd);
}
else if (g_redir == APPEND_REDIR)
{
fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
if (fd < 0)
exit(3);
dup2(fd, 1);
close(fd);
}
// 子进程进行程序替换
execvp(g_argv[0], g_argv);
// 执行失败打印提示词
perror(g_argv[0]);
exit(127);
}
else
{
// 父进程回收子进程pid
pid_t rid = waitpid(id, NULL, 0);
if (rid < 0)
perror("waitpid fail");
}
return 0;
}