技术心得:实例解析shell子进程(subshell)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 技术心得:实例解析shell子进程(subshell)

实例解析shell子进程(subshell )


通过实例,解析个人对shell子进程的一个了解,主要包括以下几个方面


1:什么是shell子进程


2:shell什么情况下会产生子进程


3:子进程的特点与注意事项


4:$变量$$在脚本里的意义,及如何得到子进程里的进程号

参考文档:apue,bash的man和info文档

1:什么是shell子进程

子进程,是从父子进程的概念出发的,unix操作系统的进程从init进程开始(init进程为1,而进程号0为系统原始进程,以下讨论的进程原则上不包括进程0)均有其对应的子进程,就算是由于父进程先行结束导致的孤儿进程,也会被init领养,使其父进程ID为1。

也因为所有的进程均有父进程,事实上,所有进程的创建,都可视为子进程创建过程。在apue一书里提及unix操作系统进程的创建,大抵上的模式都是进行fork+exec类系统调用。

理解子进程的创建执行,需要至少细分到二个步骤,包括

通过fork创建子进程环境,

通过exec加载并执行进程代码。

其间诸如继承的环境变量等细节,可以查看apue第八章相关章节。

而shell子进程(以下均称subshell),顾名思义,就是由“当前shell进程”创建的一个子进程

2:shell什么情况下会产生子进程

以下几个创建子进程的情况。(以下英文摘自info bash)

1:&,提交后台作业

If a command is terminated by the control operator `&', the shell executes the command asynchronously in a subshell.

2:管道

Each command in a pipeline is executed in its own subshell

3:括号命令列表

()操作符

Placing a list of commands between parentheses causes a subshell

environment to be created

4:执行外部脚本、程序:

When Bash finds such a file while searching the `$PATH' for a command, it spawns a subshell to execute it. In other words, executing

filename ARGUMENTS

is equivalent to executing

bash filename ARGUMENTS

说明:大致上子进程的创建包括以上四种情况了。需要说明的是只要是符合上边四种情况之一,便会创建(fork)子进程,不因是否是函数,命令,或程序,也不会因为是内置函数(buitin)或是外部程序。

此外,上边提到子进程创建与执行的二个步骤,shell子进程的创建在步骤之一并无多大差别,一般还是父进程调用fork产生进程环境,估在第二步exec的时候,是存在差别的。

shell做为解释语言程序,提供给第二步exec加载和执行的程序体并不是脚本本身,而是由第一行#!指定的,默认为shell程序,当然也可以是awk,sed等程序,在之前写过的一篇文章里:shell脚本的set id如何生效就有提及。这里不再展开讨论。

只不过子进程的执行会根据情况而有所差别,对于内置函数,exec程序体为shell程序,并在会在子shell直接调用内置函数,

而外部函数或程序,在创建了子进程环境后,大致会有二种执行情况:

1:直接exec外部程序,

比如下边例子中直接执行的sleep,pstree命令等

2:subshellexec程序体为shell程序,在此基础上会进一步创建一个子进程以执行函数。

比如下边例子中通过函数提交后台程序中的shell命令等

例:内置函数(直接在subshell里执行,不管是否通过函数)

【root@localhost shell】# mkfifo a

【root@localhost shell】# type echo

echo is a shell builtin

【root@localhost shell】# b(){ echo a>a; }

【root@localhost shell】# b &

【1】 15697

【root@localhost shell】# echo a>a &

【2】 15732

【root@localhost shell】# pstree -pa $$


bash,571


|-bash,15697


|-bash,15732


//代码效果参考:http://hnjlyzjd.com/hw/wz_24866.html

-pstree,15734 -pa 571</p> <p>例:定义函数并提交后台进行</p> <p>(函数调用中的sleep在subshell之下又创建一个子进程,</p> <p>而pstree,sleep命令的直接执行,则是直接在子进程上进行)</p> <p>【root@localhost shell】# a(){ sleep 30; } ;</p> <p>【root@localhost shell】# sleep 40 &</p> <p>【1】 15649</p> <p>【root@localhost shell】# a &</p> <p>【2】 15650</p> <p>【root@localhost shell】# pstree -pa $$

bash,571

|-bash,15650

| `-sleep,15651 30

|-pstree,15652 -pa 571

`-sleep,15649 40

对于第四点,要注意,shell脚本的执行模式,在第四点的二种模式下,shell是会创建子进程的:

filename ARGUMENTS

bash filename ARGUMENTS

但shell同时提供二种不创建子程序的进程创建方式

1:source命令,使用方法

Source filename ARGUMENTS

. filename ARGUMENTS

此种方法,直接在当前shell进程中执行filename脚本,filename结束后继续返回当前shell进程

2:exec命令,使用方法

Exec filename ARGUMENTS

此种方法直接在当前shell进程中执行filname脚本,filename结束后退出当前shell进程

3:子进程的特点与注意事项

这方面不具体展开,只提一点写脚本容易出现的错误。

做为子进程,其进程环境与父进程的环境是独立的, 所以在变量传递过程中,需要注意子进程内部不能更改到父进程的变量。

比如如下通过管道求和并赋给外部变量sum例子,结果sum值并不会因此改变:

【root@localhost shell】# sum=0

【root@localhost shell】# echo '1 2 3 4' |sed 's/ //n/g'|while read line; do sum+=$line; done

【root@localhost shell】# echo $sum

0

【root@localhost shell】#

bai@bbox:~$ echo '1 2 3 4' |sed 's/ /\n/g'|while read line; do sum+=$line; done;echo $sum

bai@bbox:~$ echo '1 2 3 4' |sed 's/ /\n/g'|(while read line; do sum+=$line; done;echo $sum)

1234

bai@bbox:~$

4:变量$$在脚本里的意义</p> <p>变量$$代表的是当前shell进程的进和id,这里要特别留意“当前shell”,

看看info bash里的说明

`$'

Expands to the process ID of the shell. In a `()' subshell, it

expands to the process ID of the invoking shell, not the subshell.

再看看man bash里的说明

$

Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.

所以在实际环境中,$$并不一定“当前进程”的进程号,而是当前shell进程的进程号。</p> <p>从文档中,需要留意的便是 invoking shell (info) 或 current shell(man) 与 当前subshell进程的关系了</p> <p>这就引出了几个问题</p> <p>1:到底怎么样算是 current shell</p> <p>2:子进程里的$$对应的是哪个 current shell

3:如何猎取子进程的$$?</p> <p>做为调试和测试,下边的例子引用几个变量,</p> <p>BASH_SOURCE'</p> <p> An array variable whose members are the source filenames</p> <p> corresponding to the elements in theFUNCNAME' array variable.


BASH_LINENO'</p> <p> An array variable whose members are the line numbers in source</p> <p> files corresponding to each member of FUNCNAME.</p> <p>${BASH_LINENO【$i】}' is the line number in the source file where


${FUNCNAME【$i】}' was called. The corresponding source file name</p> <p> is${BASH_SOURCE【$i】}'. Use LINENO' to obtain the current line</p> <p> number.</p> <p>FUNCNAME'


An array variable containing the names of all shell functions


currently in the execution call stack. The element with index 0


is the name of any currently-executing shell function. The


bottom-most element is "main". This variable exists only when a


shell function is executing. Assignments to FUNCNAME' have no</p> <p> effect and return an error status. IfFUNCNAME' is unset, it


loses its special properties, even if it is subsequently reset.


脚本里set -x,并设置PS4跟踪程序执行过程


PS4='+【$SHELL】【$BASH_SUBSHELL】【$PPID-$$】【$LINENO】【"${BASH_SOURCE【*】}"】【${FUNCNAME【*】}】【${BASH_LINENO【*】}】/n +

PS4设置显示值如下:

【$SHELL】:当前shell路径

【$BASH_SUBSHELL】:子shell路径长度

【$PPID-$$】:父进程id,和变量$$值(current shell进程ID)

【$LINENO】:在当前shell的命令行号

【"${BASH_SOURCE【*】}"】:源脚本程序文件记录队列

【${FUNCNAME【*】}】:函数调用记录队列

【${BASH_LINENO【*】}】:执行行号记录队列

程序如下:

【root@localhost shell】# cat -n subshell.sh

+【/bin/bash】【0】【569-571】【1060】【""】【】【】

+cat -n subshell.sh

1 #!/bin/bash

2

3 set -x

4 sub2() {

5 # sh subshell2.sh

6 sleep 1

7 }

8 sub() {

9 sub2 &

10 sleep 20

11 }

12 sub &

13 pstree -p $PPID

执行结果如下:

【root@localhost shell】# bash subshell.sh

+【/bin/bash】【0】【569-571】【1059】【""】【】【】

+bash subshell.sh

+【/bin/bash】【0】【571-17858】【12】【"subshell.sh"】【】【0】

+sub

+【/bin/bash】【0】【571-17858】【13】【"subshell.sh"】【】【0】

+pstree -p 571

+【/bin/bash】【1】【571-17858】【10】【"subshell.sh subshell.sh"】【sub main】【12 0】

+sleep 20

+【/bin/bash】【1】【571-17858】【9】【"subshell.sh subshell.sh"】【sub main】【12 0】

+sub2

+【/bin/bash】【2】【571-17858】【6】【"subshell.sh subshell.sh subshell.sh"】【sub2 sub main】【9 12 0】

+sleep 1

bash(571)---bash(17858)-+-bash(17859)-+-bash(17860)---sleep(17863)

| `-sleep(17862)

`-pstree(17861)

说明:

1:

首先在当前shell(进程id 571)下执行subshell.sh ,产生子进程,

【$PPID-$$】=【571-17858】显示此时执行subshell.sh脚本的进程号为17858,


【】【0】显示未进行函数调用,未产生函数记录记录


说明在subshell.sh执行进程里,$$值保存的“current shell”即为本身,17858

【$LINENO】=【12】显示在subshell.sh第12行调用sub函数

sub函数在程序里通过&提交后台方式调用,

进程树显示,sub函数的调用在17858进程后创建子进程17859,执行体为bash

此时,ppid指示父进程为571,$$变量值为17858,


说明对于sub调用产生的进程,其“current shell”仍然为subshell.sh脚本执行进程17858


【$LINENO】=【13】显示在subshell.sh第13行执行pstree命令


pstree命令调用方式是在脚本里直接调用


进程树显示,pstree命令直接在17858进程后创建子进程17861并执行


此时,ppid指示父进程为571,$$变量值为17858,

说明对于这里运行的pstree命令的子进程,其“current shell”仍然为subshell.sh脚本执行进程17858

2:

【sub main】【12 0】显示进入sub函数内部

【$LINENO】=【9】显示执行(在sub函数内)脚本第9行,调用sub2函数

进程树显示,sub2函数的调用在17859进程后创建子进程17860,执行体为bash

此时,ppid仍然指示父进程为571,$$变量值为17858,


说明对于sub2调用产生的进程,其“current shell”仍然为subshell.sh脚本执行进程17858


【$LINENO】=【10】显示执行(在sub函数内)脚本第10行,sleep命令


此处sleep命令调用方式是在脚本里的sub函数内直接调用


进程树显示,sleep命令是sub函数调用时创建的进程17859后创建子进程17862并执行


此时,ppid指示父进程为571,$$变量值为17858,

说明对于这里运行的pstree命令的子进程,其“current shell”仍然为subshell.sh脚本执行进程17858

3:

【sub2 sub main】【9 12 0】显示进入sub2函数内部

【6】显示执行(在sub2函数内)脚本第6行,sleep 1

此处sleep命令调用方式是在脚本里的sub2函数内直接调用

进程树显示,sleep命令是sub2函数调用时创建的进程17860后创建子进程17863并执行

此时,ppid指示父进程为571,$$变量值为17858,


说明对于这里运行的sleep命令的子进程,其“current shell”仍然为subshell.sh脚本执行进程17858


终上


这里的$$只有二个值,

一个是最初的bash shell: 571,

一个是subshell.sh脚本调用时产生的进程:17858

其他由subshell.sh产生的子进程,无论是函数还是命令运行,$$变量值保存的“current shell”均为subshell.sh调用时产生的进程:17858


由此推论出上边提到的四种子shell的创建方法:提交后台,管道,括号命令列表,脚本调用。似乎只有第四种方法--脚本调用--产生的subshell可以做o为“current shell”


可以通过以下二个例子再次论证这个推论


例子一:


更改脚本调用方式,


此种方式采用当前shell进程执行subshell.sh,不再创建一个子进程


结果$$变量值保存的“current shell”均为当前进程

【root@localhost shell】# source subshell.sh

+【/bin/bash】【0】【569-571】【1062】【""】【】【】

+source subshell.sh

++【/bin/bash】【0】【569-571】【3】【"subshell.sh"】【】【1062】

+set -x

++【/bin/bash】【0】【569-571】【13】【"subshell.sh"】【】【1062】

+pstree -p 569

++【/bin/bash】【0】【569-571】【12】【"subshell.sh"】【】【1062】

+sub

++【/bin/bash】【1】【569-571】【1】【"subshell.sh subshell.sh"】【sub source】【12 1062】

+sub2

++【/bin/bash】【2】【569-571】【2】【"subshell.sh subshell.sh subshell.sh"】【sub2 sub source】【1 12 1062】

sleep 1

++【/bin/bash】【1】【569-571】【2】【"subshell.sh subshell.sh"】【sub source】【12 1062】

+sleep 20

sshd(569)---bash(571)-+-bash(18801)---sleep(18804)

|-bash(18806)---bash(18808)---sleep(18809)

`-pstree(18807)

例子二:

在子进程里边,采用脚本调用方式,或取子进程进程号

为此增加一个脚本subshell2.sh,并在subshell.sh进行调用

+cat -n subshell2.sh

1 #!/bin/bash

2 set -x

3 echo $PPID

4 sleep 10

+cat -n subshell.sh

1 #!/bin/bash

2

3 set -x

4 sub2() {

5 ./subshell2.sh &

6 sleep 1

7 }

8 sub() {

9 sub2 &

10 sleep 20

11 }

12 sub &

13 pstree -pa $PPID

【root@localhost shell】# ./subshell.sh

+【/bin/bash】【0】【569-571】【1138】【""】【】【】

+./subshell.sh

+【/bin/bash】【0】【571-19715】【13】【"./subshell.sh"】【】【0】

+pstree -pa 571

+【/bin/bash】【0】【571-19715】【12】【"./subshell.sh"】【】【0】

+sub

+【/bin/bash】【1】【571-19715】【9】【"./subshell.sh ./subshell.sh"】【sub main】【12 0】

+sub2

+【/bin/bash】【2】【571-19715】【5】【"./subshell.sh ./subshell.sh ./subshell.sh"】【sub2 sub main】【9 12 0】

./subshell2.sh

+【/bin/bash】【2】【571-19715】【6】【"./subshell.sh ./subshell.sh ./subshell.sh"】【sub2 sub main】【9 12 0】

sleep 1

+【/bin/bash】【1】【571-19715】【10】【"./subshell.sh ./subshell.sh"】【sub main】【12 0】

+sleep 20

+【/bin/bash】【0】【19718-19719】【3】【"./subshell2.sh"】【】【0】

+echo 19718

19718

+【/bin/bash】【0】【19718-19719】【4】【"./subshell2.sh"】【】【0】

+sleep 10

bash,571

`-subshell.sh,19715 ./subshell.sh

|-pstree,19717 -pa 571

`-subshell.sh,19716 ./subshell.sh

|-sleep,19721 20

`-subshell.sh,19718 ./subshell.sh

|-sleep,19720 1

`-subshell2.sh,19719 ./subshell2.sh

`-sleep,19722 10

从上边的执行结果可以看出,当程序执行进入subshell2.sh时,创建了进程号19294的进程境,$$变量值保存的“current shell”均为更新为subshell2.sh调用时创建的进程:


这样,打出来的subshell2.sh的父进程id,实际上就是

相关文章
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
AI技术深度解析:从基础到应用的全面介绍
人工智能(AI)技术的迅猛发展,正在深刻改变着我们的生活和工作方式。从自然语言处理(NLP)到机器学习,从神经网络到大型语言模型(LLM),AI技术的每一次进步都带来了前所未有的机遇和挑战。本文将从背景、历史、业务场景、Python代码示例、流程图以及如何上手等多个方面,对AI技术中的关键组件进行深度解析,为读者呈现一个全面而深入的AI技术世界。
197 10
|
2天前
|
机器学习/深度学习 人工智能 算法
DeepSeek技术报告解析:为什么DeepSeek-R1 可以用低成本训练出高效的模型
DeepSeek-R1 通过创新的训练策略实现了显著的成本降低,同时保持了卓越的模型性能。本文将详细分析其核心训练方法。
110 10
DeepSeek技术报告解析:为什么DeepSeek-R1 可以用低成本训练出高效的模型
|
21天前
|
缓存 算法 Oracle
深度干货 如何兼顾性能与可靠性?一文解析YashanDB主备高可用技术
数据库高可用(High Availability,HA)是指在系统遇到故障或异常情况时,能够自动快速地恢复并保持服务可用性的能力。如果数据库只有一个实例,该实例所在的服务器一旦发生故障,那就很难在短时间内恢复服务。长时间的服务中断会造成很大的损失,因此数据库高可用一般通过多实例副本冗余实现,如果一个实例发生故障,则可以将业务转移到另一个实例,快速恢复服务。
深度干货  如何兼顾性能与可靠性?一文解析YashanDB主备高可用技术
|
17天前
|
运维 Shell 数据库
Python执行Shell命令并获取结果:深入解析与实战
通过以上内容,开发者可以在实际项目中灵活应用Python执行Shell命令,实现各种自动化任务,提高开发和运维效率。
46 20
|
1月前
|
Serverless 对象存储 人工智能
智能文件解析:体验阿里云多模态信息提取解决方案
在当今数据驱动的时代,信息的获取和处理效率直接影响着企业决策的速度和质量。然而,面对日益多样化的文件格式(文本、图像、音频、视频),传统的处理方法显然已经无法满足需求。
83 4
智能文件解析:体验阿里云多模态信息提取解决方案
|
30天前
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
121 11
|
2月前
|
域名解析 负载均衡 安全
DNS技术标准趋势和安全研究
本文探讨了互联网域名基础设施的结构性安全风险,由清华大学段教授团队多年研究总结。文章指出,DNS系统的安全性不仅受代码实现影响,更源于其设计、实现、运营及治理中的固有缺陷。主要风险包括协议设计缺陷(如明文传输)、生态演进隐患(如单点故障增加)和薄弱的信任关系(如威胁情报被操纵)。团队通过多项研究揭示了这些深层次问题,并呼吁构建更加可信的DNS基础设施,以保障全球互联网的安全稳定运行。
|
2月前
|
缓存 网络协议 安全
融合DNS技术产品和生态
本文介绍了阿里云在互联网基础资源领域的最新进展和解决方案,重点围绕共筑韧性寻址、赋能新质生产展开。随着应用规模的增长,基础服务的韧性变得尤为重要。阿里云作为互联网资源的践行者,致力于推动互联网基础资源技术研究和自主创新,打造更韧性的寻址基础服务。文章还详细介绍了浙江省IPv6创新实验室的成立背景与工作进展,以及阿里云在IPv6规模化部署、DNS产品能力升级等方面的成果。此外,阿里云通过端云融合场景下的企业级DNS服务,帮助企业构建稳定安全的DNS系统,确保企业在数字世界中的稳定运行。最后,文章强调了全链路极致高可用的企业DNS解决方案,为全球互联网基础资源的创新提供了中国标准和数字化解决方案。
|
2月前
|
缓存 边缘计算 网络协议
深入解析CDN技术:加速互联网内容分发的幕后英雄
内容分发网络(CDN)是现代互联网架构的重要组成部分,通过全球分布的服务器节点,加速网站、应用和多媒体内容的传递。它不仅提升了访问速度和用户体验,还减轻了源站服务器的负担。CDN的核心技术包括缓存机制、动态加速、流媒体加速和安全防护,广泛应用于静态资源、动态内容、视频直播及大文件下载等场景,具有低延迟、高带宽、稳定性强等优势,有效降低成本并保障安全。
100 4
|
2月前
|
数据采集 存储 JavaScript
网页爬虫技术全解析:从基础到实战
在信息爆炸的时代,网页爬虫作为数据采集的重要工具,已成为数据科学家、研究人员和开发者不可或缺的技术。本文全面解析网页爬虫的基础概念、工作原理、技术栈与工具,以及实战案例,探讨其合法性与道德问题,分享爬虫设计与实现的详细步骤,介绍优化与维护的方法,应对反爬虫机制、动态内容加载等挑战,旨在帮助读者深入理解并合理运用网页爬虫技术。

推荐镜像

更多