脚本混合编程说的是在bash的脚本里面可以加入python等其他编程脚本语言的,但是根据我们上一讲所讲的内容,#!这种shebang只在第一行才有效果,那也就说只能指定一个解释器,那么如何混合编程呢?其实c和汇编混合编程还是很常见的,不过我也没有实际去编过,我们前面学了python,我们就来看bash脚本里如何运行一段python。其实就是用了之前学过的一个输入重定向,没有那么难。
看到我们也就是用了一个输入重定向给/usr/bin/python去解释执行。 <<后面的符号是定义的结束标志,如果定义的是EOF,则EOF必须要顶头,这是必须的,前面的文章也有讲过,前面加一个-可以允许结束符前面有空格或者tab的缩进。要是python代码没有缩进还好,EOF前面可以不加-。
如果代码有缩进。
不加-,EOF顶头的话,就会显示缩进错误。如果加一个-,就没有问题了。
下面这样也是不行的。
加了-的话前面有缩进也是可以的。
这种写法就很方便了,因为你不需要写完python之后再在下一行顶头写了,看着也很整齐,对于有强迫症的人来说是很重要的,就像我。另外如果你看bash脚本的执行结果,就会发现,即使上一行有错误,下一行还是可以执行,这和python是不一样的。
python脚本里面是第一行错了第二行不可能执行的,在bash里面也是。
在python脚本里面是不能执行bash代码的,因为bash的命令不是python的关键字,在python脚本里面是不识别的。
执行bash脚本的时候还需要注意一个问题,就是哪个shell执行的问题。
看到脚本执行的cd并没有改变当前shell的路径。但是我们如果在前面加.或者source就是在当前shell里面执行。
source和.可以让脚本在当前shell里面执行。
那前面没有加.或者source的时候脚本去哪里执行了呢?其实是去子shell里面执行了。什么是子shell呢?参考了https://www.linuxidc.com/Linux/2017-08/146606.htm
子shell的概念贯穿整个shell,写shell脚本时更是不可不知。所谓子shell,即从当前shell环境新开一个shell环境,这个新开的shell环境就称为子shell(subshell),而开启子shell的环境称为该子shell的父shell。子shell和父shell的关系其实就是子进程和父进程的关系,只不过子shell和父shell是关联的进程是bash进程。子shell会从父shell中继承很多环境,如变量、命令全路径、文件描述符、当前工作目录、陷阱等等,但子shell有很多种类型,不同类型的子shell继承的环境不相同。可以使用$BASH_SUBSHELL变量来查看从当前进程开始的子shell层数,$BASHPID查看当前所处BASH的PID,这不同于特殊变量"$$"值,因为"$$"会从父进程继承。
何时产生子shell?
要解释清楚子shell以及产生何种类型的子shell,需要搞清楚Linux中如何产生子进程。Linux上创建子进程的方式有三种:一种是fork出来的进程,一种是exec出来的进程,一种是clone出来的进程。此处无需关心clone,因为它用来实现Linux中的线程。
(1).fork是复制进程,它会复制当前进程的副本(不考虑写时复制的模式),以适当的方式将这些资源交给子进程。所以子进程掌握的资源和父进程是一样的,包括内存中的内容,所以也包括环境变量和变量。但父子进程是完全独立的,它们是一个程序的两个实例。
(2).exec是加载另一个应用程序,替代当前运行的进程,也就是说在不创建新进程的情况下加载一个新程序。exec还有一个动作:在进程执行完毕后,退出exec所在的shell环境。
所以为了保证进程安全,若要形成新的且独立的子进程,都会先fork一份当前进程,然后在fork出来的子进程上调用exec来加载新程序替代该子进程。例如在bash下执行cp命令,会先fork出一个bash,然后再exec加载cp程序覆盖子bash进程变成cp进程。再来说明子shell的问题。一般fork出来的子进程,内容和父进程是一样的(包括变量),例如执行cp命令时也能获取到父进程的变量。但是cp命令在哪里执行呢?执行cp命令敲入回车后,当前的bash进程fork出一个子bash,然后子bash通过exec加载cp程序替代子bash。这算是进入了子shell吗?更通用的问题是:什么情况下会进入子shell环境,什么时候不进入子shel环境呢?判断是否进入了子shell的方式非常简单,执行"echo $BASHPID",如果该值和父bash进程的pid值不同,则表示进入了子shell。在shell中是否进入子shell的情况可以分为几种:
$$变量被继承了,expr是以标准输出格式打印表达式的值,表达式也有很多格式,里面还会有逻辑运算,比较运算,算术运算,这不是我们今天的重点,并且其实也不难,并且其实用echo也可以输出,这里稍微补充一下下面的内容,参考了https://www.cnblogs.com/hyc-blog/p/7880163.html
|的意思是如果arg1不是null或者0,就返回arg1,否则返回arg2。&的意思是如果参数都不是null或者0,换句话说就是有一个参数是0或者null,那么就返回arg1,否则返回0。
let上面是用来给变量赋值,但是其实let也可以完成一些运算。
我们下面先来体会一下echo和expr区别。
区别应该已经显而易见了,expr是可以进行运算的,不过格式是运算符前后都要有空格,否则也不认为是要运算,echo是根本就不能进行运算,只是照本宣科。其实let也是可以进行运算的,而且根据http://daizj.iteye.com/blog/2255906
let执行的效率要高于expr,但是let有一点缺点就是不能直接输出。
&这个符号在前面作业管理那里见过,在一个命令后面加这个,就会把进程放在后台运行。|是要进程管道的连接符,它们都需要转义或者说修饰。
好了好了,我们先扯回去,我们现在说的是子shell这个事情。上面看到的结果是进程管道后面的命令确实是在子shell里面执行的,而不是在当前shell下面执行的。
并且子shell进程很快就会结束了,而且子shell里面的变量是没有传到父shell里面的,这其实很好理解,一般都是子承父业。
给定的变量被自动标记导出到之后的执行的命令环境中,也就是之后的命令都会按照新赋给这个变量的值来打印。
可以看到这个2869的bash shell确实是2339的一个子进程,$$这个变量没有被继承,我们用exit可以退出这个子shell,然后这个对应的子进程就被处理掉了。
但是看到$BASH_SUBSHELL的值好像一直都是0,不知道出什么问题了。
上面第一句断句要断好,意思应该是这样的,脚本中第一行总是#!/bin/bash,这样会可以执行,或者我们直接在bash而不是脚本输入bash 脚本名,就像下面那样,放着不管会造成无限循环,最后out of memory。
用sleep来看一下。
这和函数里面打出来的$BASH_SUBSHELL是对的,但是真的不知道为什么其它的方式这个深度有问题,我们也不需要掌握那么深。
感觉这位博主应该是把内置和非内置写反了。参考了https://www.cnblogs.com/pingzhe/p/7077685.html和
https://www.cnblogs.com/11hwu2/p/3724986.html
上面的博主确实是把写反了,grep是个外部命令,按照上面博主的想法,应该是没有新的bash 的,从下图看,它就是当前BASHPID的进程的一个子进程而已。这和上面函数的情况是不一样的。
我猜想这应该也是我上面所说的只有子承父业,子进程被处理以后,a的值又被父进程的shell接管。其实我们没有必要去了解那么深。
这种也看不见新的bash,而是sleep进程的父进程直接就是3816。
整个顺下来其实有点懵,这篇文章写于2017-8-13,可能我们现在用的centos7,它用的可能不是,这篇已经算是比较新的了,不过还是有参考价值的,它已经把我们上面执行脚本cd的疑问解决了,那么这一讲就先到这里。