shell简易版编写

2024-08-29 07:04
文章标签 编写 shell 简易版

本文主要是介绍shell简易版编写,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、制造命令行提示符

二、获取命令

三、分割命令

四、执行命令

五、内建命令

全部代码


一、制造命令行提示符

命令行主要有三个组成:用户名、主机名和路径。这也是需要优先获取的。

将每块功能封装起来,这样阅读性高一些。

这三个都存放在环境变量里,可以通过getenv去获取。

对应的环境变量:

  • 用户名:USER
  • 主机名:HOSTNAME
  • 路径:PWD

将这三个字符串放入到line中,使用snprintf函数。

命令行是不能加\n的,我们要在命令行后面输出内容,通过刷新缓冲区的方式来实现。

scanf代替一下获取命令,能更好看出制造命令行的效果。原命令行是$结尾,这里使用>结尾做区分。

基本和原命令行是一样的,不过路径因该是只保留最后一个,后面再对这个进行更改。

二、获取命令

输入内容使用fgets,它可以以\n为结束读取的字符。

可以看到确实读到了空格,但打印的usercommand是多了一行,%s\n因该是只换行一次。这是因为fgets把\n也读了进去,我们需要把字符串最后一个字符给改一下(\n是一个字符,不是\和n两个字符)。

在输入命令时输入错误,需要Ctrl+Backspace才能删除。

三、分割命令

命令需要分割成argv字符串数组一样进行使用。定义一个全局的Argv存放。

for是为了测试。分割字符串有对应的函数strtok来分割。

使用while进行这样的循环赋值,strtok再分割完后会返回NULL,刚好符合Argv以NULL结尾,也可以时循环停止。这里多加一层()是为了不报wrning。

四、执行命令

只执行PATH内的可执行文件,使用execvp替换函数。再将这几个功能一起循环就有一个简易shell效果了。

现在就能完成一些PATH内的功能,不过对于内建命令还不能使用。

五、内建命令

内建命令指的是那些shell不需要创建子进程的命令,内建命令就是shell内的一段代码。如cd、echo [环境变量]等。

cd测试:

cd是改变当前的工作目录,如果是让子进程来执行那就是子进程的工作目录发生了改变,父进程的并没有。echo $[环境变量]也是子进程查看的是自己的环境变量,不是父进程的,父进程的内建命令在使用时会改变一些环境变量需要自己维护。

单独判断内建命令

内建命令的判断非常朴实,有几个就使用几个if。

echo $?:拿到最近一次进程的退出信息。只需要将lastcode设为全局变量,就可以拿到上一次的退出信息,需要注意在用户的角度echo也是在执行文件,所以需要将lastcode值改为0,视作echo的退出信息。

cd:更改路径可以使用chdir,修改完路径环境变量PWD也需要跟着修改,不过不能直接拿输入的路劲来修改,PWD需要绝对路径,cd会有一些特殊的符号来表示一些路劲(~:家目录;-:上一次所处路劲;不跟任何路劲和符号:家目录),这个需要单独处理。更改路劲后先通过getcwd拿到当前路径,再将当前路劲以环境变量的形式存起来。

运行结果:

现在在来处理一下命令行提示符中路劲的问题,要只显示当前所属目录名。

要在获取路劲后修改,找最后一个目录。只需要从字符串尾向前找到第一个/就可以,使用一个宏函数来封装。

使用{}是为了让宏函数在使用时,能在结尾加;看起来规范些。

原shell在命令行是没有/的,打印路劲时也只打印/后面的内容,cwd+1就可以。不过这会导致在跟目录下来回打印/需要单独判断。

运行结果:

全部代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<errno.h>#define Size 512
#define SkipPath(p) { p += (strlen(p)-1); while(*p != '/') p--;}
char* Argv[32];
char cwd[Size*2];
int lastcode;const char* GetUserName()
{const char* name = getenv("USER");if(name == NULL) return "None";return name;
}const char* GetHomeName()
{const char* name = getenv("HOSTNAME");if(name == NULL) return "None";return name;
}const char* GetCwd()
{const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}const char* GetHome()
{const char* home = getenv("HOME");if(home == NULL) return "/";return home;
}void MakeCommandLineAndPrint()
{char line[Size];//存放命令行//获取用户名const char* username = GetUserName();//获取主机名const char* hostname = GetHomeName();//获取路径const char* cwd = GetCwd();SkipPath(cwd);//让cwd去指向最后一个/的位置//结合起来并打印snprintf(line,sizeof(line),"[%s@%s %s]> " ,username, hostname, strlen(cwd)==1 ? "/" : cwd+1);printf("%s",line);fflush(stdout);
}int GetUserCommand(char* usercommand, size_t size)
{if(fgets(usercommand, size, stdin) == NULL){return -1;}usercommand[strlen(usercommand)-1] = '\0';return strlen(usercommand);
}void SplitCommand(char* usercommand)
{Argv[0] = strtok(usercommand," ");int i = 1;while((Argv[i++] = strtok(NULL," ")));
}void ExecuteCommand()
{pid_t id = fork();if(id < 0){perror("fork");//创建失败exit(1);}else if(id == 0){execvp(Argv[0],Argv);//去PATH中查找可执行文件exit(errno);//子进程走到这里就表示替换失败,也就是命令执行失败}int status = 0;pid_t rid = waitpid(id,&status,0);if(rid>0){lastcode = WEXITSTATUS(status);if(lastcode != 0){//命令执行错误,打印错误信息printf("%s:%s:%d\n", Argv[0], strerror(lastcode), lastcode);}}
}void Cd()
{const char *path = Argv[1];if(path == NULL || strcmp(path,"~") == 0) path = GetHome();//处理特殊符// 这时path 一定存在路劲chdir(path);//更改路径char temp[Size*2];getcwd(temp, sizeof(temp));//拿到当前绝对路劲//cwd是保存的全局变量,除了env内要有绝对路劲,程序内部也需要保存使用snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd); // OK
}int CheckBuildin()
{char* s = Argv[0];int ret = 0;if(strcmp(s,"cd") == 0){ret = 1;Cd();}else if(strcmp(s,"echo") == 0 && strcmp(Argv[1],"$?") == 0){ret = 1;printf("%d\n", lastcode);lastcode = 0;}return ret;
}int main()
{while(1){//制造命令行MakeCommandLineAndPrint();//获取命令char usercommand[Size];int n = GetUserCommand(usercommand,sizeof(usercommand));//将命令放入usercommandif(n == -1) return 1;//获取失败//分割命令SplitCommand(usercommand);//判断是否是内建命令if(CheckBuildin())//是内建返回真,会在函数内处理执行{continue;}//执行命令ExecuteCommand();}return 0;
}

这篇关于shell简易版编写的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1117177

相关文章

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo

jenkins 插件执行shell命令时,提示“Command not found”处理方法

首先提示找不到“Command not found,可能我们第一反应是查看目标机器是否已支持该命令,不过如果相信能找到这里来的朋友估计遇到的跟我一样,其实目标机器是没有问题的通过一些远程工具执行shell命令是可以执行。奇怪的就是通过jenkinsSSH插件无法执行,经一番折腾各种搜索发现是jenkins没有加载/etc/profile导致。 【解决办法】: 需要在jenkins调用shell脚

Wondows dos下怎么编写bat批处理文件

最近搞php,在运行时,以Nginx+php-cgi.exe方式运行Wordpress项目 打开dos,先cd到php-cgi.exe文件当前目录下执行启动命令:php-cgi.exe -b 127.0.0.1:9001再打开一个dos,再cd到nginx.exe文件当前目录下执行启动命令:start nginx 大概过程要经过这些步骤,觉得很麻烦,就学下怎么编写一个bat文件,以双击运行代替

站长常用Shell脚本整理分享(全)

站长常用Shell脚本整理分享 站长常用Shell脚本整理分享1-10 站长常用Shell脚本整理分享11-20 站长常用Shell脚本整理分享21-30 站长常用Shell脚本整理分享31-40 站长常用Shell脚本整理分享41-50 站长常用Shell脚本整理分享51-59 长期更新

Shell脚本实现自动登录服务器

1.登录脚本 login_server.sh #!/bin/bash# ReferenceLink:https://yq.aliyun.com/articles/516347#show all host infos of serverList.txtif [[ -f ./serverList.txt ]]thenhostNum=`cat ./serverList.txt | wc -l`e

用Python编写倒计时程序:详细教程

目录 引言 环境准备 基本概念 代码实现 步骤一:导入必要的库 步骤二:获取用户输入 步骤三:实现倒计时逻辑 步骤四:整合代码 运行程序 高级功能 扩展功能示例:支持分钟和小时输入 扩展功能示例:图形用户界面 (GUI) 总结 引言 倒计时程序是一个非常常见的小工具,广泛用于各种应用场景中,例如考试时间提醒、烹饪计时器、会议倒计时等。Python 作为一种

[轻笔记]ubuntu shell脚本切换conda环境

source /home/yourhostname/anaconda3/etc/profile.d/conda.sh # 关键!!!conda activate env_name

[轻笔记] ubuntu Shell脚本实现监视指定进程的运行状态,并能在程序崩溃后重启动该程序

根据网上博客实现,发现只能监测进程离线,然后对其进行重启;然而,脚本无法打印程序正常状态的信息。自己通过不断修改测试,发现问题主要在重启程序的命令上(需要让重启的程序在后台运行,不然会影响监视脚本进程,使其无法正常工作)。具体程序如下: #!/bin/bashwhile [ 1 ] ; dosleep 3if [ $(ps -ef|grep exe_name|grep -v grep|

adb shell 执行后台程序后断开adb后台进程被结束的解决办法

环境:Android 版本 Android8 通常让程序后台执行就是在命令 最后加上 &即可,但是在Android 8上实验发现,程序的确后台了,但是拔掉USB线再连接上发现进程已结束。不确定Android早期版本是否存在此问题。 参考网上一些Linux方法,如加nohup 仍然无效,还是会结束。看来Android adb shell 与 Linux shell 还是有一定区别。 后来在网上

shell脚本中变量中字符串替换的测试 /和//的区别

test_char=abbbcbbbf echo "bf:test_char = " $test_char test_char=${test_char/bbb/ddd} echo "af:test_char = " $test_char 输出: bf:test_char =  abbbcbbbf af:test_char =  adddcbbbf 只匹配第一个