初识Linux · 自主Shell编写
发布时间 - 2025-06-19 00:00:00 点击率:次本文介绍了自主shell编写的过程,模拟实现了bash解释器,并详细讲解了所需的预备知识,如进程的多方面知识。接下来,我们将直接进入shell编写部分。
1 命令行解释器部分
我们在Centos版本下进行演示,通常看到的命令行解释器显示为当前用户名(如_lazy)、主机名(如VM-12-14-centos)和当前目录
(如~)。我们的目标是复制一个类似的命令行解释器。
首先,如何获取用户名、主机名和当前目录?我们可以通过环境变量来获取这些信息。
在环境变量表中,我们可以看到HOSTNAME、PWD和USER分别代表主机名、当前路径和当前用户名。我们可以通过三种方式获取这些变量:environ、命令行参数表和getenv。我们选择使用getenv来获取这些信息:
char* argv[] = {
getenv("HOSTNAME"),
getenv("USER"),
getenv("PWD")
};将获取到的环境变量放在数组argv中,然后进行打印。我们使用printf来打印数组的三个元素,但由于命令行参数是在后面输入的,我们不能使用\n作为结束符。相反,我们使用snprintf函数来将所有输出放入一个字符串中,这样更容易控制输出。如果你的man手册配置不全,可以使用以下命令:
因此,第一部分的临时代码如下:
void OutputBash()
{
char line[SIZE];
char* username = GetUser();
char* hostname = Gethost();
char* cwd = Getcwd();
snprintf(line, sizeof(line), "[%s@%s %s]youjiankuohaophpcn ", username, hostname, cwd);
printf("%s", line);
fflush(stdout);
// char* argv[] = {
// getenv("HOSTNAME"),
// getenv("USER"),
// getenv("PWD")
// };
// char* line;
// //printf("[%s@%s %s]youjiankuohaophpcn", argv[0], argv[1], argv[2]);
// fflush(stdout);}
相关函数的定义如下:
#define SIZE 512char GetUser()
{
char user = getenv("USER");
if(user == NULL) return NULL;
return user;
}
char Gethost()
{
char host = getenv("HOSTNAME");
if(host == NULL) return NULL;
return host;
}
char Getcwd()
{
char cwd = getenv("PWD");
if(cwd == NULL) return NULL;
return cwd;
}
但这只是临时的,因为我们的pwd显示不完善:
目前显示的并不是最完善的,应该只显示当前目录。我们可以通过分割字符串来解决这个问题,使用指针指向最后一个/,但不建议使用函数,因为需要二级指针。我们可以使用宏来实现:
我们可以使用以下方法操作:
这样,较为完善的命令行解释器部分就完成了。
2 获取用户命令行参数
我们已经解决了第一个问题,现在需要获取用户的命令行参数。在获取用户命令行参数时,我们应该使用什么函数呢?可以使用scanf吗?如果使用scanf,输入ls -l -n -a时,只能获取到ls,因为scanf是通过空格或换行符来获取的。因此,我们推荐使用fgets或gets,但由于后面有文件IO操作,我们选择使用fgets作为缓冲:
int GetUserCommand(char usercommand, size_t n)
{
char s = fgets(usercommand, n, stdin);
if(s == NULL) return -1;return strlen(s);
}
但此代码存在一些缺陷,详见第4部分。
3 命令行参数进行分割
获取命令后,不能带空格执行,因此我们需要使用函数将命令行参数分割。我们使用C语言的库函数strtok来进行分割:
第一个参数是待分割的字符串,第二个参数是分割符。第一次分割后,将第一个参数置为NULL以继续分割。我们将分割后的字符串放入数组中,方便后面的进程替换工作。我们定义一个全局变量来存储分割后的字符串:
#define SEP " "
char gArgv[SIZE];
需要注意的是,使用单引号的空格与strtok不匹配,因为这只是一个字符,不是const char。
void SplitCommand(char* usercommand)
{
gArgv[0] = strtok(usercommand, SEP);
int index = 1;
while((gArgv[index++] = strtok(NULL, SEP)));//分割之后函数返回NULL 恰好作为结尾
}分割后的函数返回NULL,可以作为数组的结束标志。
4 执行命令
现在我们可以直接执行命令了,至少目前可以执行简单的命令。我们需要涉及进程程序替换,因为分割好的命令已经放在了全局变量中,我们可以直接创建函数:
void ExcuteCommand()
{
pid_t id = fork();if(id zuojiankuohaophpcn 0)
{
perror("fork error");
return;
}
else if(id == 0)
{
execvp(gArgv[0], gArgv);
perror("execvp error");
exit(-1);
}
else
{
int status;
waitpid(id, &status, 0);
int lastcode = WEXITSTATUS(status);
if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
}}
这些代码是进程替换时介绍过的,我们只需进行一些修饰,使代码更美观。然而,执行时仍会遇到问题,因为命令行输入时会自动输入回车,导致无法执行。我们需要去掉回车:
使用宏定义ZERO来解决这个问题。
5 判断命令是否为内建命令
如果我们执行的是echo、cd等内建命令,只能由父进程执行,我们不能创建子进程。我们需要判断是否为内建命令,如果是,则由父进程执行,并跳过下一步。我们可以通过strcmp来判断内建命令:
void Cd()
{
const char *path = gArgv[1];
if(path == NULL) path = Gethost();
// path 一定存在
chdir(path);// 刷新环境变量
char temp[SIZE*2];
getcwd(temp, sizeof(temp));
snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
putenv(cwd); // OK
}
int IsInorder()
{
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;
}
以cd为例,判断cd是内建命令后,在cd函数中实现。我们使用chdir函数改变当前工作目录,之后更新环境变量中的PATH。此时,自主Shell编写工作基本完成。
感谢阅读!
# linux
# centos
# c语言
# ai
# bash
# echo
# NULL
# if
# fgets
# printf
# const
# 全局变量
# 字符串
# 命令行参数
# char
# int
# 指针
# 命令行
# 我们可以
# 内建
# 可以使用
# 第一个
# 的是
# 放在
# 解决这个问题
# 是一个
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?
Swift中swift中的switch 语句
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
如何在服务器上配置二级域名建站?
实例解析Array和String方法
Laravel模型事件有哪些_Laravel Model Event生命周期详解
如何在云虚拟主机上快速搭建个人网站?
Laravel广播系统如何实现实时通信_Laravel Reverb与WebSockets实战教程
JS去除重复并统计数量的实现方法
高端网站建设与定制开发一站式解决方案 中企动力
Laravel如何处理文件下载请求?(Response示例)
Laravel Facade的原理是什么_深入理解Laravel门面及其工作机制
rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted
Laravel如何使用缓存系统提升性能_Laravel缓存驱动和应用优化方案
jQuery中的100个技巧汇总
如何快速搭建FTP站点实现文件共享?
在Oracle关闭情况下如何修改spfile的参数
Laravel Eloquent关联是什么_Laravel模型一对一与一对多关系精讲
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
laravel怎么配置和使用PHP-FPM来优化性能_laravel PHP-FPM配置与性能优化方法
Laravel中DTO是什么概念_在Laravel项目中使用数据传输对象(DTO)
javascript基于原型链的继承及call和apply函数用法分析
Laravel如何使用集合(Collections)进行数据处理_Laravel Collection常用方法与技巧
Laravel如何使用Sanctum进行API认证?(SPA实战)
如何选择可靠的免备案建站服务器?
如何在 Pandas 中基于一列条件计算另一列的分组均值
韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐
Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册
JS弹性运动实现方法分析
Laravel模型关联查询教程_Laravel Eloquent一对多关联写法
详解阿里云nginx服务器多站点的配置
Laravel怎么使用Collection集合方法_Laravel数组操作高级函数pluck与map【手册】
如何在 Python 中将列表项按字母顺序编号(a.、b.、c. …)
ai格式如何转html_将AI设计稿转换为HTML页面流程【页面】
Laravel怎么在Controller之外的地方验证数据
HTML5段落标签p和br怎么选_文本排版常用标签对比【解答】
Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】
香港服务器租用每月最低只需15元?
如何确认建站备案号应放置的具体位置?
Laravel Vite是做什么的_Laravel前端资源打包工具Vite配置与使用
Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转
百度浏览器如何管理插件 百度浏览器插件管理方法
如何在云主机快速搭建网站站点?
Laravel如何实现多对多模型关联?(Eloquent教程)
动图在线制作网站有哪些,滑动动图图集怎么做?
详解Android——蓝牙技术 带你实现终端间数据传输
简单实现jsp分页
如何用PHP工具快速搭建高效网站?
Laravel怎么发送邮件_Laravel Mail类SMTP配置教程
晋江文学城电脑版官网 晋江文学城网页版直接进入

