编写一个Sheel程序(一)(全网最详细介绍------初学者上机实验总结)

2021/10/12 22:14:30

本文主要是介绍编写一个Sheel程序(一)(全网最详细介绍------初学者上机实验总结),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

这里我们默认的前提是:已经安装好了虚拟机和可视化操作界面,这里将以一个实例带大家了解我是如何创建shell程序的过程。

图形化界面:CentOS 7

文章目录

  • 一、初识Sheel
    • 1.1、什么是 sheel?
    • 1.2、什么是脚本?
    • 1.3、Linux中有哪些脚本解释器?
    • 1.4、shell命令
  • 二、体验自己创建运行shell
    • 2.1 在CentOS文本编辑器创建一个名为 Test.sh 的文件
    • 2.2 运行脚本
      • 2.2.1 方式一: 将 Shell 脚本作为程序运行
      • 2.2.2 方式二: 将 Shell 脚本作为程序运行
  • 三、进程相关知识
    • 3.1 fork(),exit(),wait(),execve()........
    • 3.2 execve()
    • 3.3 通常运行另一个程序,而同时保留原程序运行的方法是,fork+exec。~~~就是一个完整的进程启动故事!
  • 四、零散的有用知识
    • 4.1 perror()
      • 4.1.1 标准错误输出设备 stderr()
      • 4.1.2 errno()
    • 4.2 waitpid()
  • 五、上机实验
    • 5.1 实验报告
      • 5.1.1 实验目的
      • 5.1.2 实验步骤
      • 5.1.3 实验报告
    • 5.2 操作实现

一、初识Sheel

1.1、什么是 sheel?

定义 :http://c.biancheng.net/view/706.html
简单概括
初衷:是一个命令解释器,使用命令行操控操作系统的内核。 但是直接操作内核没必要也不安全,创建一个程序(在linux成为shell)直接调用内核接口,安全方便。

1.2、什么是脚本?

(1) 我们会写许多的程序,把这些 程序名字 (叫做shell命令)放到一个文件里面。脚本不需要编译,通过 解释器解释 执行,所以比较慢。
(2) 比如系统定义的 ls命令,我们在控制台输入这个命令就可以查看当前目录下的所有文件。(后文会讲)
再比如我们自定义一个helloworld.c的程序,其实也是一个命令(后文会讲)

1.3、Linux中有哪些脚本解释器?

有dash和bash两种,但dash没有bash功能全面,所以通常使用bash。 可以通过命令来查看系统中的脚本解释器: ls -l /bin/*sh
在这里插入图片描述

1.4、shell命令

以 ls -l /bin/*sh 为例:

  1. ls 是常用的一个命令,它属于目录操作命令,用来列出当前目录下的文件和文件夹。ls 可以附带选项,也可以不带,不带选项的写法为:
[zjh@localhost ~]$ cd demo
[zjh@localhost demo]$ ls
abc          demo.sh    a.out         demo.txt
getsum       main.sh    readme.txt    a.sh
module.sh    log.txt    test.sh       main.c

先执行cd demo命令进入 demo 目录,这是我在自己的主目录下创建的文件夹,用来保存教学使用的各种代码和数据。

接着执行 ls 命令,它列出了 demo 目录下的所有文件,并且进行了格式对齐。

  1. 文件信息打印具有短格式选项和长格式选项,实例提供可视化参考。
    (所以说需要什么东西,以什么格式等等,就按照这样一串子写下来。不同那个字符串了直接搜索查询即可!)
[zjh@localhost demo]$ ls -l
总用量 140
-rwxrwxr-x. 1 zjh zjh 8675 4月   2 15:01 a.out
-rwxr-xr-x. 1 zjh zjh 116 4月   3 09:24 a.sh
-rw-rw-r--. 1 zjh zjh 44 4月   2 16:41 check.sh
-rw-r--r--. 1 zjh zjh 399 3月  11 17:12 demo.sh
-rw-rw-r--. 1 zjh zjh 4 4月   8 17:56 demo.txt

如果加一个-l选项,则可以看到显示的内容明显增多了。-l是长格式(long list)的意思,也就是显示文件的详细信息。

可以看到,选项的作用是调整命令功能。如果没有选项,那么命令只能执行最基本的功能;而一旦有选项,则能执行更多功能,或者显示更加丰富的数据。


基本的概念大概了解了一下,对于shell一定充满了很多疑问,接下来尝试编写一个shell脚本并且运行感受一下。

二、体验自己创建运行shell

2.1 在CentOS文本编辑器创建一个名为 Test.sh 的文件

填写代码:

#!/bin/bash
echo "Hello World !"  #这是一条语句

解释代码内容:
第 1 行的#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell;后面的/bin/bash就是指明了解释器的具体位置。

第 2 行的 echo 命令用于向标准输出文件(Standard
Output,stdout,一般就是指显示器)输出文本。在.sh文件中使用命令与在终端直接输入命令的效果是一样的。

第 2 行的#及其后面的内容是注释。Shell
脚本中所有以#开头的都是注释(当然以#!开头的除外)。写脚本的时候,多写注释是非常有必要的,以方便其他人能看懂你的脚本,也方便后期自己维护时看懂自己的脚本——实际上,即便是自己写的脚本,在经过一段时间后也很容易忘记。

2.2 运行脚本

2.2.1 方式一: 将 Shell 脚本作为程序运行

Shell 脚本也是一种解释执行的程序,可以在终端直接调用(需要使用 chmod 命令给 Shell 脚本加上执行权限),如下所示:

[zjh@localhost ~]$ cd demo                #切换到 test.sh 所在的目录
[zjh@localhost demo]$ chmod +x ./Test.sh  #给脚本添加执行权限
[zjh@localhost demo]$ ./Test.sh           #执行脚本文件
Hello World !                                  #运行结果

chmod +x表示给 test.sh 增加执行权限。
./程序名字 (运行该程序的格式)

2.2.2 方式二: 将 Shell 脚本作为程序运行

你也可以直接运行 Bash 解释器,将脚本文件的名字作为参数传递给 Bash,如下所示:

[zjh@localhost ~]$ cd demo               #切换到 test.sh 所在的目录
[zjh@localhost demo]$ /bin/bash Test.sh  #使用Bash的绝对路径

Hello World !                            #运行结果

通过这种方式运行脚本,不需要在脚本文件的第一行指定解释器信息,写了也没用。


接下来进一步介绍一下:进程相关的东西,实验涉及到的知识

三、进程相关知识

Linux 中的每一个进程都有一个唯一的 ID,称为 PID,查看PID可以跟踪到目的进程。

3.1 fork(),exit(),wait(),execve()…

进程的创建是通过fork()函数完成的,

1)了解什么是fork?: https://zhuanlan.zhihu.com/p/53527981.

2)对fork有了基本的了解,更系统的学习一个fork() 的使用以及必须了解的一些函数吧!https://www.cnblogs.com/dongguolei/p/8098181.html.

3.2 execve()

1)进程的执行代码是execve()加载的
2)exec系列的系统调用是把当前程序替换成要执行的程序
3)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他也是(execl,execle,execlp,execv,execvp)功能一样,参数不同。

链接: https://cloud.tencent.com/developer/article/1592678.

1、execve是Linux的系统函数
2、execve在头文件unistd.h中。
3、execve的定义形式:
int execve(const char *filename, char *const argv[], char *const envp[]);
4、参数说明: const char

*filename:执行文件的完整路径。 char *const argv[]:传递给程序的完整参数列表,包括argv[0],它一般是程序的名。 char *const
envp[]:一般传递NULL,表示可变参数的结尾。

#include <stdio.h>
#include <unistd.h>
 
int main(int argc, char *args[])
{
	// 定义一个字符数组,保存程序的参数信息
	// 数组的第一个元素是程序的名称
	char *buf[] = { "/bin/ls", "-l", NULL };
	// 调用execve函数执行程序
	// 第一个参数是程序的完整路径,第二个参数是字符数组,第三个参数是NULL
	execve("/bin/ls", buf, NULL);
	return 0;
}

3.3 通常运行另一个程序,而同时保留原程序运行的方法是,fork+exec。~~~就是一个完整的进程启动故事!

(3.2的链接最后的案例有提到)

四、零散的有用知识

4.1 perror()

1)C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。

2)str – 这是 C 字符串,包含了一个自定义消息,将显示在原本的错误消息之前。

3)实例参考

#include <stdio.h>
int main ()
{
   FILE *fp;
   /* 首先重命名文件 */
   rename("file.txt", "newfile.txt");

   /* 现在让我们尝试打开相同的文件 */
   fp = fopen("file.txt", "r");
   if( fp == NULL ) {
      perror("Error: ");
      return(-1);
   }
   fclose(fp);
      
   return(0);
}
运行结果
Error: : No such file or directory

在这里插入图片描述

可以发现如果只是使用这个函数的话十分简单,当我准备下一个步骤时我看到有人这么定义,我一下看不懂了,不过我认为如果了解了这段话就可以认识到这个函数背后的逻辑,于是我产生许多思考

他是如何找到对应的错误信息文本?是如何打印的呢?打印的信息保存在哪里呢?带着这些问题我继续探索…

后面将为大家解释这段话的意思

4.1.1 标准错误输出设备 stderr()

(4.1中perror概念提到了标准错误输出设备怎么理解呢)

1)stdout, stdin, stderr的中文名字分别是标准输出,标准输入和标准错误。
2)实例参考

fprintf(stdout,"Hello ");
fprintf(stderr,"World!");
运行结果
World!Hello

原因:
在默认情况下,stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。而stderr是无缓冲的,会直接输出,举例来说就是printf(stdout,“xxxx”) 和 printf(stdout, “xxxx\n”),前者会憋住,直到遇到新行才会一起输出。而printf(stderr, “xxxxx”),不管有么有\n,都输出。

3)区别:
stdout – 标准输出设备 (printf("…")) 同 stdout。
stderr – 标准错误输出设备
两者默认向屏幕输出。
但如果用转向标准输出到磁盘文件,则可看出两者区别。stdout输出到磁盘文件,stderr在屏幕。

4.1.2 errno()

参考 http://c.biancheng.net/ref/errno.html.
1) 错误代码仅仅是一个数字,并没有额外的结构,要想获取具体的错误信息,一般有两种方案:

  • 使用 perror() 将错误信息(文本)打印到标准输出设备;
  • 使用 strerror() 将错误代码转换成对应的文本信息。

2)使用
默认是0,如果发生错误,就会被改变,成为一个新的int类型数字, strerror()函数中放入这个int类型数字,就会被打印出来。

总结:perro() 之所以实现在屏幕输出错误原因,是因为error找到了错误,stderr提供输出功能

4.2 waitpid()

(前面我们了解了wait(),这里区分一下waitpid(),算是对知识的优化。)
要存在子进程或者其他的信号时候使用

pid_t waitpid(pid_t pid,int *status,int options)
在这里插入图片描述

wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状态值, 则参数 status 可以设成NULL.


终于来到了上机实验部分

五、上机实验

5.1 实验报告

操作系统系统课程实验三:编写一个简单shell程序

5.1.1 实验目的

1、学习几个有关进程的命令:ps, pstree, top。
2、观察进程的镜像(结构)。
3、使用fork和execve等调用实现一个简单的shell程序。

5.1.2 实验步骤

实验环境:openEuler+gnome或Ubuntu。

(1)ps命令:观察系统中的进程信息。 $ ps -el

可以看到进程的有关信息,如图1所示。

图1 ps命令
在这里插入图片描述

(2)pstree命令:观察进程树的结构 $ pstree

展示进程树的结构,如图2所示

图2 pstree命令
在这里插入图片描述

(2)top命令:实时观察系统的资源使用状况。 $ top

运行结果为:

在这里插入图片描述

(4)观察进程的镜像。
首先在Helloworld.c程序中加入sleep(1000),或while(1){…}死循环,使程序不能马上运行结束。启动程序并转入后台执行后,通过ps命令获取Helloworld程序的PID,假定为3146。使用如下命令观察进程的镜像:

          $ cat /proc/3146/maps

结果为:
在这里插入图片描述

(5)在openEuler或Ubuntu环境下,编写并运行通过下述程序。仔细分析execve的输入参数。
ch3-exp-fork-v1.c

#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

int main(int argc, char* argv[]){
	pid_t pid;
	char* prog_argv[]={"ls", "-l", "/", NULL};
	if((pid=fork())<0){
			perror("Fork failed\n");
			exit(errno);
	}
	if(!pid){	/*Child*/
	         printf("argc=%d, argv[0]=%s.\n", argc, argv[0]);
			execve("/bin/ls", prog_argv, NULL);
			exit(0);
		}
		if(pid){	/*Parent*/
			waitpid(pid, NULL, 0);
			printf("Child is reaped.\n");
		}
	}//end else
	return 0;
}

经过前面的学习,大家对这个程序已经非常熟悉了,如果不熟悉说明前面的东西吸收的不够好哦,我都写这么详细了。如果不懂查看 【part 三 】

(6)在上述程序的基础上设计一个自己的shell程序。假定该程序名为myshell.c,编译后的可执行程序名为myshell,它带有一系列参数,比如myshell /bin/ls -el,其中/bin/ls是第一个参数,-el是第二个参数。在执行myshell /bin/ls -el时,myshell首先创建一个子进程,然后用该子进程去执行一个Linux固有的命令程序/bin/ls,选项为-el。对于myshell程序,验证以下几个命令是否能够正确执行:
(a)/bin/ls -l
(b)ps -el
(c)top
(d)bash myshell /bin/ls -l
(e)cd /

5.1.3 实验报告

1、实验环境要求:openEuler(Ubuntu)+C+gcc+make。
2、程序myshell运行通过,展示源程序,Makefile文件以及运行结果。
3、实验报告内容包括:
(1)通过top命令的执行,得到系统中当前的进程数,CPU使用率、内存占用率。
(2)通过ps命令和top命令都能获取进程的优先级PRI。查阅相关资料,说明通过这样两个命令得到的同一进程的优先级为什么是不同的?
(3)在shell下启动可执行文件helloworld(死循环版本),通过pstree命令的执行结果,说明helloworld所在的进程与shell进程是什么关系?
(4)展示myshell.c和Makefile程序的源代码。解释为什么myshell不能成功的执行“cd /”等命令?

5.2 操作实现

在这里插入图片描述



这篇关于编写一个Sheel程序(一)(全网最详细介绍------初学者上机实验总结)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程