星空网 > 软件开发 > 操作系统

子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,这不同于特殊变量"$$"值,因为"$$"是从父进程继承的。

1.1.1 何时产生子shell

严格地说,除了直接执行bash内置命令,在bash环境下执行的所有命令以及脚本都会产生子shell(意味着这些命令都是在子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命令是在哪里执行的呢?在子shell中。执行cp命令敲入回车后,当前的bash进程fork出一个子bash,然后子bash通过exec加载cp程序替代子bash。

那是否可以理解为所有命令、脚本其运行环境都是在子shell中呢?显然,上面所说的bash内置命令不是在子shell中运行的。其他的所有方式,都是在子shell中完成,只不过方式不尽相同。

分为几种情况:

  • ①.执行bash内置命令:bash内置命令是非常特殊的,父进程不会创建子进程来执行这些命令,而是直接在当前bash进程中执行。但如果将内置命令放在管道后,则此内置命令将和管道左边的进程同属于一个进程组,所以仍然会创建子进程,但却不一定是子shell。请先阅读完下面的几种情况再来考虑此项。
  • ②.执行bash命令本身:这是一个很巧合的命令。bash命令本身是bash内置命令,在当前shell环境下执行内置命令本不会创建子shell,也就是说不会有独立的bash进程出现,而实际结果则表现为新的bash是一个子进程。其中一个原因是执行bash命令会加载各种环境配置项,为了父bash的环境得到保护而不被覆盖,所以应该让其以子shell的方式存在。虽然fork出来的bash子进程内容完全继承父shell,但因重新加载了环境配置项,所以子shell没有继承普通变量,更准确的说是覆盖了从父shell中继承的变量。不妨试试在/etc/bashrc文件中定义一个变量,再在父shell中export名称相同值却不同的环境变量,然后到子shell中看看该变量的值为何?
    • 其实执行bash命令,即可以认为是进入了子shell,也可以认为没有进入子shell。从bash是内置命令的角度来考虑,它不会进入子shell,这一点在执行bash命令后从变量$BASH_SUBSHELL的值为0可以验证出来。但从执行bash命令后进入了新的shell环境来看,它有其父bash进程,所以它算是进入了子shell。
  • ③.执行shell脚本:因为脚本中第一行总是"#!/bin/bash"或者直接"bash xyz.sh",所以这和上面的执行bash进入子shell其实是一回事,都是使用bash命令进入子shell。只不过此时的bash命令和情况②中直接执行bash命令所隐含的选项不一样,所以继承和加载的shell环境也不一样。事实也确实如此,它仅只继承父shell的某些环境变量,其余环境一概初始化。
    • 另外,执行shell脚本有一个动作:命令执行完毕后自动退出子shell。
  • ④.执行非bash内置命令:例如执行cp命令、grep命令等,它们直接fork一份bash进程,然后使用exec加载程序替代该子bash。此类子进程会继承所有父bash的环境。但严格地说,这已经不是子shell,因为exec加载的程序已经把子bash进程替换掉了,这以为着丢失了很多bash环境。
  • ⑤.非内置命令的命令替换:当命令行中包含了命令替换部分时,将开启一个子shell先执行这部分内容,再将执行结果返回给当前命令。因为这次的子shell不是通过bash命令进入的子shell,所以它会继承父shell的所有变量内容。这也就解释了"echo $(echo $$)"中"$$"的结果是当前bash的pid号,而不是子shell的pid号,但"echo $(echo $BASHPID)"却和父bash进程的pid不同,因为它不是使用bash命令进入的子shell。
  • ⑥.使用括号()组合一系列命令:例如(ls;date;echo haha),独立的括号将会开启一个子shell来执行括号内的命令。这种情况等同于情况⑤。

最后需要说明的是,子shell的环境设置不会粘滞到父shell环境,也就是说子shell的变量等不会影响父shell。

其实,如何判断是否进入了子shell,方式非常简单,执行"echo $BASHPID",如果该值和父bash进程的pid值不同,则表示进入了子shell。

例如:

[root@xuexi ~]# echo $BASHPID    # 当前bash进程的pid12904[root@xuexi ~]# echo haha | echo $BASHPID # 非内置命令开启的子进程组,进入了子shell13412[root@xuexi ~]# (echo $BASHPID)    # 使用括号,进入了子shell13413[root@xuexi ~]# echo $(echo $BASHPID)  # 使用命令替换$()进入了子shell13433

1.1.2 命令替换和命令组合

Linux中使用反引号"``"(脱字符,在波浪线的按键上)或者$()来执行命令替换。使用括号()来组合一系列命令。

[root@xuexi ~]# echo what date it is? $(date +%F)what date it is? 2016-09-25[root@xuexi tmp]# echo what date it is? `date +%F` # 或者使用反引号

使用$()可以让括号里的命令提前于整个命令运行,然后将执行结果插入在命令替换符号处。由于命令替换的结果经常交给外部命令,不应该让结果有换行的行为,所以默认将所有的换行符替换为了空格(实际上所有的空白符都被压缩成了单个空格)。

例如:

[root@xuexi ~]# echo -e "a\nb"ab
[root@xuexi ~]# echo `echo -e "a\nb\t \tc"`a b c

使用双引号引用可以保留空白符。

[root@xuexi ~]# echo "`echo -e "a\nb\t \tc"`"ab    c

从上面大概可以知道,命令替换分为两个过程:(1)开启子shell执行其中的命令(2)将子shell中的输出结果打包插入在命令行中。但打包输出结果的过程是可以控制的(例如上面使用双引号)。

很多时候,在命令行中需要使用"cat a.txt|command"或者执行$(cat a.txt)来传递文件a.txt中的内容,但这不是最好的方法。它们等价的效率更高的方法分别是"<a.txt"和"$(<a.txt)"。

如果使用括号将一系列命令包围,可以使得这些命令独立于当前bash环境运行。这其实是一个命令组。

例如:

[root@xuexi ~]# (umask 077;touch new.txt;ls -l new.txt)-rw------- 1 root root 0 Aug 13 22:46 new.txt

回到系列文章大纲:转载请注明出处:注:若您觉得这篇文章还不错请点击下右下角的推荐,有了您的支持才能激发作者更大的写作热情,非常感谢!

原标题:子shell、命令替换和命令组合

关键词:

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。
相关文章
我的浏览记录
最新相关资讯
海外公司注册 | 跨境电商服务平台 | 深圳旅行社 | 东南亚物流