文章目录
- 五、进程
- 12. 自定义shell
- 完整程序
- 未完待续
五、进程
12. 自定义shell
根据我们之前学的知识,这里来编写一个 shell 程序。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#define SIZE 1024
#define MAX_ARGC 64
// 用户名
const char* UserName()
{
char* username = getenv("USER");
if (username) return username;
else return "None";
}
// 主机名
const char* HostName()
{
char* hostname = getenv("HOSTNAME");
if (hostname) return hostname;
else return "None";
}
// 路径
const char* CurrentWorkDir()
{
char* currentworkdir = getenv("PWD");
if (currentworkdir) return currentworkdir;
else return "None";
}
上面代码可以通过环境变量接口获取指定信息。我们先输出一下命令行提示符试试看。
封装输出命令行提示符的信息和接收用户输入成一个函数。
我们获取到了用户一行输入的整个字符串,现在需要对这个字符串进行切割。
// 分隔符是空格
#define SEP " "
void Split(char in[])
{
int i = 0;
argv[i++] = strtok(in, SEP);
while (argv[i++] = strtok(NULL, SEP));
}
当做完以上操作后就可以执行命令了,我们创建一个子进程,让子进程来程序替换为我们要执行的命令。我们封装一下执行命令。
我们不想自己写的shell程序只跑一遍,所以我们要在主函数里加上死循环。
运行比较成功,但是我们发现,使用 cd … 命令却没有反应,这是怎么回事?因为执行cd命令的是子进程而不是我们的shell 。像这种不让子进程来执行的命令,而是让我们的shell执行的命令叫做 内建命令 。我们需要特殊处理一下。
// 获取环境变量
char *Home()
{
return getenv("HOME");
}
int BuildinCmd()
{
// 是内建命令则返回1,不是则返回0
int ret = 0;
if (strcmp("cd", argv[0]) == 0)
{
ret = 1;
char *target = argv[1];
// cd 空表示进入家目录
if (!target) target = Home();
chdir(target);
}
return ret;
}
我们再来测试一下:
这里已经完成的差不多了,再来处理一下细节问题,比如环境变量没有更新。
// 记录路径字符
char pwd[SIZE];
int BuildinCmd()
{
int ret = 0;
if(strcmp("cd", argv[0]) == 0)
{
ret = 1;
char *target = argv[1];
if(!target) target = Home();
chdir(target);
char temp[1024];
// 获取当前路径
getcwd(temp, 1024);
// 写入数组
snprintf(pwd, SIZE, "PWD=%s", temp);
// 更新
putenv(pwd);
}
}
再来测试一下:
比较完善了。但是实际上内建命令绝对不止 cd 命令,至少还包含 export 和 echo 等命令。就不再一一赘述了。感兴趣的可以自己再去完善,也可以使ls命令带上颜色等等。
完整程序
#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];
// 用户名
const char* UserName()
{
char* username = getenv("USER");
if (username) return username;
else return "None";
}
// 主机名
const char* HostName()
{
char* hostname = getenv("HOSTNAME");
if (hostname) return hostname;
else return "None";
}
// 路径
const char* CurrentWorkDir()
{
char* currentworkdir = getenv("PWD");
if (currentworkdir) return currentworkdir;
else return "None";
}
// 家目录
char* Home()
{
return getenv("HOME");
}
int Interactive(char out[], int size)
{
// 输出命令行提示符
printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
// 接收用户输入
fgets(out, size, stdin);
// 去掉最后一个 回车符
out[strlen(out) - 1] = '\0';
return strlen(out);
}
void Split(char in[])
{
int i = 0;
argv[i++] = strtok(in, SEP);
while (argv[i++] = strtok(NULL, SEP));
}
void Execute()
{
pid_t id = fork();
if(id == 0)
{
// 让子进程执行命令 文件名 + 选项数组
execvp(argv[0], argv);
exit(1);
}
waitpid(id, NULL, 0);
}
int BuildinCmd()
{
// 是内建命令则返回1,不是则返回0
int ret = 0;
if (strcmp("cd", argv[0]) == 0)
{
ret = 1;
char *target = argv[1];
// cd 空表示进入家目录
if (!target) target = Home();
chdir(target);
char temp[SIZE];
getcwd(temp, SIZE);
snprintf(pwd, SIZE, "PWD=%s", temp);
putenv(pwd);
}
return ret;
}
int main()
{
while(1)
{
char commandline[SIZE];
int n = Interactive(commandline, SIZE);
// 空串跳出
if (n == 0) continue;
Split(commandline);
n = BuildinCmd();
if (n) continue;
Execute();
}
return 0;
}