2025年shell编程实战(shell编程实例详解)

shell编程实战(shell编程实例详解)Linux nbsp 红帽认证 nbsp IT 技术 nbsp 运维工程师 1000 人技术交流 nbsp 备注 公众号 更快通过 一 获取输入 在构建简易 Shell 的时候我们首先就是要获取输入 获取环境变量 能够像 shell 一样运行会出现部分环境变量 获取用户输入 获取用户输入的指令 获取环境变量 在运行 shell 时就会出现一些环境变量

大家好,我是讯享网,很高兴认识大家。



Linux | 红帽认证 | IT技术 | 运维工程师

👇1000人技术交流 备注【公众号】更快通过


讯享网

一. 获取输入

在构建简易Shell的时候我们首先就是要获取输入获取环境变量:能够像shell一样运行会出现部分环境变量获取用户输入:获取用户输入的指令

讯享网

获取环境变量

在运行shell时就会出现一些环境变量,我们自定义构建的shell中,也可以实现这一步

用户名:pxt

主机名:hecs -

当前目录:myshell

讯享网// 获取环境变量 user,hostname,pwd,homechar *homepath(){ char home= getenv(“HOME”); if(home) return home; else return (char)”.”;}const char *getUsername(){ const char *name = getenv(“USER”); if(name) return name; else return “none”; }const char *getHostname(){ const char *hostname = getenv(“HOSTNAME”); if(hostname) return hostname; else return “none”;}const char *getCwd(){ const char *cwd = getenv(“PWD”); if(cwd) return cwd; else return “none”;}printf(”[%s@%s %s]\( "</span>,getUsername(), getHostname(), getCwd());</span></code></pre></section><p style="text-align: center;"><img src="https://mmbiz.qpic.cn/mmbiz_png/esZiajffQShQmUKWhcsicJvRsIDKo62wocUS898wB5E8dDpKJFtic3YwEAWhlp9lZ8B8j010vF9oLzvrWUjkRNjuQ/640?wx_fmt=png&amp;from=appmsg" class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="" data-ratio="0.017253" data-s="300,640" data-src="https://mmbiz.qpic.cn/mmbiz_png/esZiajffQShQmUKWhcsicJvRsIDKo62wocUS898wB5E8dDpKJFtic3YwEAWhlp9lZ8B8j010vF9oLzvrWUjkRNjuQ/640?wx_fmt=png&amp;from=appmsg" data-type="png" data-w="597" style="" /></p><p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;letter-spacing: 1px;">这里我们直接将绝对路径展示了出来,当然没什么影响</span></p><p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 1px;">这里我们用到了一个函数getenv(),这个函数用于获取环境变量的值,它的头文件是&lt;stdlib.h&gt;,在shell脚本中,获取环境变量的值是通过直接使用变量名来实现的,而不需要特别的函数或方法</span></p><p style="text-align: center;"><img src="https://mmbiz.qpic.cn/mmbiz_png/esZiajffQShQmUKWhcsicJvRsIDKo62wocFnJcMpo5ZH0fJp4VnjLS6zHZU5nXAyh9EmWQxriawJgz61xy1kG12lA/640?wx_fmt=png&amp;from=appmsg" class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="" data-ratio="0." data-s="300,640" data-src="https://mmbiz.qpic.cn/mmbiz_png/esZiajffQShQmUKWhcsicJvRsIDKo62wocFnJcMpo5ZH0fJp4VnjLS6zHZU5nXAyh9EmWQxriawJgz61xy1kG12lA/640?wx_fmt=png&amp;from=appmsg" data-type="png" data-w="766" style="" /></p><p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 1px;">在我们完成最基础的一步之后,我们要开始模拟我们使用的shell的使用方式,接下来一步就是获取用户输入</span></p><p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><strong><span style="font-size: 16px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 1px;">获取用户输入</span></strong><span style="font-size: 16px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 1px;"></span></p><p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 1px;">获取用户输入时,我们可以创建一个字符数组用来存储用户的输入</span></p><section class="code-snippet__fix code-snippet__js"><ul class="code-snippet__line-index code-snippet__js"><li></li><li></li></ul><pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer"><span class="code-snippet__meta">#<span class="code-snippet__meta-keyword">define</span> NUM 1024</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">char</span> usercommand[NUM];</span></code></pre></section><p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;letter-spacing: 1px;">在获取用户输入时,输完一个指令后不换行,进行下一次输入。</span></p><p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 1px;">但是在我们输入完成时,总会回车确认,因此我们需要修改最后一次输入</span></p><p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><strong><span style="font-size: 16px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 1px;">getUserCommand:</span></strong><span style="font-size: 16px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 1px;"></span></p><section class="code-snippet__fix code-snippet__js"><ul class="code-snippet__line-index code-snippet__js"><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li></ul><pre class="code-snippet__js" data-lang="cpp"><code><span class="code-snippet_outer"><span class="code-snippet__comment">// 对用户输入进行封装</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">int</span> <span class="code-snippet__title">getUserCommand</span><span class="code-snippet__params">(<span class="code-snippet__keyword">char</span> *command, <span class="code-snippet__keyword">int</span> num)</span></span></span></code><code><span class="code-snippet_outer">{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__built_in">printf</span>(<span class="code-snippet__string">"[%s@%s %s]\),getUsername(), getHostname(), getCwd());  char *r = fgets(command, num, stdin); // 最后还是会输入’\n’,回车 if(r == NULL) return -1; // TODO // 将最后一次输入修改成’\0’ command[strlen(command)-1] = ’\0’; // 这里不会越界 return strlen(command); }

二. 分割字符串

在Shell中,分割字符串是一个常见的操作,它涉及到将一串包含多个子字符串(可能由空格、逗号、冒号等分隔符分隔)的文本分割成单独的部分,以便进行进一步的处理或赋值给不同的变量

在我们完成用户输入指令的读取之后,我们需要将字符串进行分割,让我们的指令能够被正确的读取并且实现出来,通常我们的分隔符是’ ‘(空格)

#define SIZE 64 // 设置argv的大小#define SEP ” “ // 分隔符 ” “#define debug 1 // 用来测试是否能够成功分割字符串char *argv[SIZE]; // 用来储存分割后的字符串

而关于分割字符串,我们在C语言的学习中可能提到过一个字符串函数strtok(),它的头文件是&lt;string.h&gt;,用于分割字符串。它基于一系列的分隔符来分割字符串,并返回指向被分割出的字符串的指针

讯享网char *strtok(char *str, const char *delim);

commandSplit:void commandSplit(char *in, char *out[]) // in -&gt; usercommand{ // out -&gt; argv int argc = 0; out[argc++] = strtok(in, SEP); while(out[argc++] = strtok(NULL, SEP)); // 我们只需要将用户输入全部读取即可// 用来测试是否能够成功分割字符串#ifdef debug int i = 0; for(i = 0; out[i]; i++) { printf(”%d:%s\n”, i, out[i]); } #endif}

在完成分割字符串之后,我们就可以用很久之前学习的进程的知识,让它运行起来的,但是仅仅如此我们的很多命令是根本无法实现的,所以我们还需要进行一步,检查内建命令!

三. 检查内建命令

内建命令(也称为内置命令)是由Shell(如Bash)自身提供的命令,而不是文件系统中的某个可执行文件。这些命令通常比外部命令执行得更快,因为它们不需要通过磁盘I/O来加载外部程序,也不需要创建新的进程

我们简单实现3个内建命令:cd,exprot,echo

讯享网doBuildin:char cwd[1024]; // 存储char enval[1024]; // for testint lastcode = 0; // 存储上一条指令的返回信息void cd(const char *pash) { chdir(pash); char tmp[1024]; getcwd(tmp, sizeof(tmp)); sprintf(cwd, “PWD=%s”, tmp); // 改变当前的路径 putenv(cwd); // 改变环境变量}int doBuildin(char *argv[]){ if(strcmp(argv[0], “cd”) == 0) { char pash = NULL; if(argv[1] == NULL) pash = homepath(); // 当我们cd之后不更任何输入时, //我们直接返回家目录 else { pash = argv[1]; } cd(pash); return 1; }else if(strcmp(argv[0], “exprot”) == 0) { if(argv[1] == NULL) return 1;  strcpy(enval, argv[1]); putenv(enval); // 用于增加环境变量内容 return 1; } else if(strcmp(argv[0], “echo”) == 0){if(argv[1] == NULL) { printf(”\n”); return 1; } if((argv[1]) == \('</span> &amp;&amp; <span class="code-snippet__built_in">strlen</span>(argv[<span class="code-snippet__number">1</span>]) &gt; <span class="code-snippet__number">1</span>)</span></code><code><span class="code-snippet_outer"> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">char</span> *val = argv[<span class="code-snippet__number">1</span>]+<span class="code-snippet__number">1</span>; <span class="code-snippet__comment">// argv[1]为'\)’,argv[1]+1则为’\('后的字符</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span>(<span class="code-snippet__built_in">strcmp</span>(val, <span class="code-snippet__string">"?"</span>) == <span class="code-snippet__number">0</span>)</span></code><code><span class="code-snippet_outer"> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// lastcode 存储上一条指令的返回信息,初始状态为0</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 当程序执行时,出错时会改变lastcode的值,打印后,重新赋值为0</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__built_in">printf</span>(<span class="code-snippet__string">"%d\n"</span>, lastcode);</span></code><code><span class="code-snippet_outer"> lastcode = <span class="code-snippet__number">0</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">else</span> <span class="code-snippet__comment">// '\)‘后不跟”?“,则获取环境变量 { const char *enval = getenv(val); if(enval) printf(”%s\n”, enval); else printf(”\n”); } return 1; }else // 直接输出字符{ printf(”%s\n”,argv[1]); return 1; } } return 0;}

在内建命令的检查中,我们又会用到一些函数:

getcwd:用于获取当前工作目录的绝对路径chdir:用于改变当前工作目录chdir命令通常通过cd命令来实现,因为cdchdir的别名,两者具有相同的功能。不过,在编程语言中(如C、PHP等),chdir则是一个具体的函数,用于在程序中动态改变当前工作目录sprintf:用于将格式化的数据写入字符数组中putenv:用于改变或增加环境变量内容的函数

四. 执行程序

在完成前面的铺垫之后,我们就可以创建进程来实现我们的程序了,在之前学习进程时,我们需要用到3个头文件

讯享网#include&lt;unistd.h&gt;#include&lt;sys/types.h&gt;#include&lt;wait.h&gt;

execute:

int execute(char *argv[]){ pid_t id = fork(); if(id &lt; 0) return -1; else if(id == 0) { // 子进程 execvp(argv[0], argv); exit(1); } else{ // 父进程 int status = 0; pid_t rid = waitpid(id, &status, 0); if(rid &gt; 0) { // 将现在状态提供个lastcode lastcode = WEXITSTATUS(status); } } return 0; }

execvp是C语言(特别是在Unix和类Unix系统,如Linux)中用于执行外部程序的一个系统调用函数。这个函数通过查找环境变量(特别是PATH环境变量)来定位并执行指定的文件,同时将参数列表传递给该程序

以上就是对一些基本操作的封装,让我们看一下主函数main

main:

讯享网int main(){// shell是一个一直循环的程序 while(1) { char usercommand[NUM]; char *argv[SIZE];  // 获取输入 int n = getUserCommand(usercommand, sizeof(usercommand)); // 当获取输入时,返回一个小于0的数时,我们直接continue返回,不用往下继续走了 if(n &lt;= 0) continue; // 分割字符串 (strtok) commandSplit(usercommand, argv); // check Buildin n = doBuildin(argv); // 一般内建命令不会出错 if(n) continue; // 执行命令 execute(argv);  }  return 0;}

注意:当我们输入指令输出字符时,Backspace键是不能直接删除的,我们需要Ctrl + Backspace 组合键用于删除光标前的一个单词

我们来思考函数和进程之间的相似性

讯享网exec/exit就像call/return一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信

这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间

一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值

五. 总结

在探索和学习编写Linux中简易shell脚本的旅程即将告一段落之际,我们不禁回望这段充满挑战与收获的时光。Shell脚本,作为Linux系统中不可或缺的一部分,以其强大的自动化能力和灵活的语法结构,成为了系统管理员、开发者以及任何希望提高工作效率的用户的得力助手


课程咨询添加:HCIE666CCIE

↑或者扫描上方二维码↑


你有什么想看的技术点和内容

可以在下方留言告诉小盟哦!

小讯
上一篇 2025-04-28 22:49
下一篇 2025-04-22 21:22

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/190066.html