• 1.组命令
  • 2.子进程
    • 2.1 什么是子进程
    • 2.2 创建子进程
    • 2.3 子进程总结
  • 3.如何检测子shell与子进程

1.组命令

组命令,就是将多个命令划分为一组,或者看成一个整体。

用法
区别

Shell 组命令的写法有两种:

{ command1; command2;. . .;  }
(command1; command2;. . . )

  1. 由花括号{}包围起来的组命名在当前 Shell 进程中执行,而由小括号()包围起来的组命令会创建一个子 Shell,所有命令都在子 Shell 中执行。
  2. 使用花括号{}时,花括号与命令之间必须要有一个空格,并且最后一个命令必须用一个分号或一个换行符结束
  3. 组命令可以将多条命令的输出结果合并在一起,在使用重定向和管道时会特别方便。

两种写法的重要不同:{}包围的组命令在当前 Shell 进程中执行,由()包围的组命令会创建一个子Shell,所有命令都会在这个子 Shell 中执行
在子 Shell 中执行意味着,运行环境被复制给了一个新的 shell 进程,当这个子 Shell 退出时,新的进程也会被销毁,环境副本也会消失,

所以在子 Shell 环境中的任何更改都会消失(包括给变量赋值)。因此,在大多数情况下,除非脚本要求一个子 Shell,

否则使用{}比使用()更受欢迎,并且{}的进行速度更快,占用的内存更少

举栗  
将多条命令的输出重定向到out.txt文件

1.普通模式

  1. ls -l > out.txt #>表示覆盖
  2. echo "test432" >> out.txt #>>表示追加
  3. cat test.txt >> out.txt

2.使用组命令

{ ls -l ;echo "test432";cat test.txt; }>out.txt

(ls -l ;echo "test432";cat test.txt)>out.txt

组命令与管道结合

(ls -l ;echo "test432";cat ../test.txt)|wc -l

2.子进程

2.1 什么是子进程

子进程的概念是由父进程的概念引申而来的。在 Linux 系统中,系统运行的应用程序几乎都是从 init(pid为 1 的进程)进程派生而来的,所有这些应用程序都可以视为 init 进程的子进程,而 init 则为它们的父进程。

Shell 脚本是从上至下、从左至右依次执行的,即执行完一个命令之后再执行下一个。如果在 Shell 脚本中遇到子脚本(即脚本嵌套,但是必须以新进程的方式运行)或者外部命令,就会向系统内核申请创建一个新的进程,以便在该进程中执行子脚本或者外部命令,这个新的进程就是子进程。子进程执行完毕后才能回到父进程,才能继续执行父脚本中后续的命令及语句。

使用pstree -p命令就可以看到 init 及系统中其他进程的进程树信息(包括 pid):

systemd(1)─┬─ModemManager(796)─┬─{ModemManager}(821)
│ └─{ModemManager}(882)
├─NetworkManager(975)─┬─{NetworkManager}(1061)
│ └─{NetworkManager}(1077)
├─abrt-watch-log(774)
├─abrt-watch-log(776)
├─abrtd(773)
├─accounts-daemon(806)─┬─{accounts-daemon}(839)
│ └─{accounts-daemon}(883)
├─alsactl(768)
├─at-spi-bus-laun(1954)─┬─dbus-daemon(1958)───{dbus-daemon}(1960)
│ ├─{at-spi-bus-laun}(1955)
│ ├─{at-spi-bus-laun}(1957)
│ └─{at-spi-bus-laun}(1959)
├─at-spi2-registr(1962)───{at-spi2-registr}(1965)
├─atd(842)
├─auditd(739)─┬─audispd(753)─┬─sedispatch(757)
│ │ └─{audispd}(759)
│ └─{auditd}(752)

2.2 创建子进程

创建子进程的方式
说明
 
  • 第一种只使用 fork() 函数,子进程和父进程几乎是一模一样的,父进程中的函数、变量(全局变量、局部变量)、文件描述符、别名等在子进程中仍然有效。我们将这种子进程称为子 Shell(sub shell)
  1. 使用 fork() 函数创建一个子进程相当于对父进程进行了克隆,除了 PID(进程ID)等极少的参数不同外,子进程的一切都来自父进程,包括代码、数据、堆栈、打开的文件等,就连代码的执行位置(状态)都是一样的。
  2. 但是,后期随着各自的发展轨迹不同,两者会变得不一样,但是在 fork() 出来的那一刻,两者都是一样的。
组命令、命令替换、管道
  • 第二种使用 fork() 和 exec() 函数,即使用 fork()创建子进程后立即调用 exec() 函数加载新的可执行文件,而不使用从父进程继承来的一切,子进程和父进程之间除了硬生生地维持一种“父子关系”外,再也没有任何联系了,它们就是两个完全不同的程序。

举栗:
在 ~/bin 目录下有两个可执行文件分别叫 a.out 和 b.out。现在运行 a.out,就会产生一个进程,比如叫做 A。在进程 A 中我又调用 fork() 函数创建了一个进程 B,那么 B 就是 A 的子进程,此时它们是一模一样的。但是,我调用 fork() 后立即又调用 exec() 去加载 b.out,这可就坏事了,B 进程中的一切(包括代码、数据、堆栈等)都会被销毁,然后再根据 b.out 重建建立一切。这样一折腾,B 进程除了 ID 没有变,其它的都变了,再也没有属于 A 的东西了。

1.以新进程的方式运行脚本文件,比如bash ./test.sh; chmod +x ./test.sh; ./test.sh

2.在当前 Shell 中使用 bash 命令启动新的 Shell

2.3 子进程总结

子 Shell 虽然能使用父 Shell 的的一切,但是如果子 Shell 对数据做了修改,比如修改了全局变量,这种修改也只能停留在子 Shell,无法传递给父 Shell。不管是子进程还是子 Shell,都是“传子不传父”。

子 Shell 才是真正继承了父进程的一切,这才像“一个模子刻出来的”;普通子进程和父进程是完全不同的两个程序,只是维持着父子关系而已。

3.如何检测子shell与子进程

echo $$输出当前进程ID,echo $PPID输出父shell ID

 

命令

结果

结论

输出当前进程与父进程ID

echo $$;echo $PPID

34451

34450

 

子进程形式输出进程ID

子进程

bash

echo $$;echo $PPID

exit

52886

34451

在普通的子进程中,$ 被展开为子进程的 ID

组命令形式输出进程ID

子shell

(echo $$;echo $PPID)

34451

34450

子shell和父shell中的ID是一样的

这是因为$ 变量在子 Shell 中无效!Base 官方文档说,在普通的子进程中,$ 确实被展开为子进程的 ID;

但是在子 Shell 中,$ 却被展开成父进程的 ID

管道形式输出进程ID

子shell

echo "test" | { echo $$;echo $PPID; }

34451

34450

进程替换形式输出进程ID

read < <(echo $$ $PPID)

$ echo $REPLY

34451 34450

除了 $,Bash 还提供了另外两个环境变量——SHLVL 和 BASH_SUBSHELL,用它们来检测子 Shell 非常方便。

SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,每次进入一层普通的子进程,SHLVL 的值就加 1。而 BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子 Shell(sub shell)嵌套深度的累加器,每次进入一层子 Shell,BASH_SUBSHELL 的值就加 1。

  命令 结果 知识点
输出变量

echo "$SHLVL $BASH_SUBSHELL"

1  0

 

子进程形式输出变量

子进程

创建一个脚本文件,命名为 test.sh,内容如下:

#!/bin/bash echo "$SHLVL $BASH_SUBSHELL"

*****************************************

bash

echo "$SHLVL $BASH_SUBSHELL"#2 0

bash ./test.sh #3 0

echo "$SHLVL $BASH_SUBSHELL"#2 0

chmod +x ./test.sh;./test.sh #3 0

echo "$SHLVL $BASH_SUBSHELL"#2 0

exit #退出内层Shell

echo "$SHLVL $BASH_SUBSHELL"#1 0

 

bash ./test.sh./test.sh

这两种运行脚本的方式,在脚本运行期间会开启一个子进程,

运行结束后立即退出子进程

产生新进程时,SHLVL的值加1

组命令形式输出变量

子shell

(echo "$SHLVL  $BASH_SUBSHELL")

1  1 组命令、管道、命令替换这几种方式都会产生子 Shell

管道形式输出变量

子shell

echo "test" | { echo "$SHLVL  $BASH_SUBSHELL"; }

1  1

命令替换形式输出变量

子shell

var=$(echo "$SHLVL  $BASH_SUBSHELL")

echo $var

1 1

四层组命令形式输出变量

子shell

( ( ( (echo "$SHLVL $BASH_SUBSHELL") ) ) )

1 4

进程替换形式输出变量

read < <(echo "$SHLVL  $BASH_SUBSHELL")

echo $REPLY

echo "hello" > >(echo "$SHLVL $BASH_SUBSHELL")

1 0

1 0

进程替换只是借助文件在()内部和外部命令之间传递数据,

并没有创建子shell,()内部和外部的命令是在一个进程

(也就是当前进程)中执行的

22 shell组命令与子进程的更多相关文章

  1. dirname和shell常用命令

    $ cd `dirname $0` 和PWD%}  显示当前目录名称${#var}             替换为变量字符个数特殊变量$ 当前SHELL的PID? 前一个命令的退出状态! 后台执行的上 ...

  2. 常用shell脚本命令

    常用shell脚本命令 1.显示包含文字aaa的下一行的内容: sed -n '/aaa/{n;p;}' filename 2.删除当前行与下一行的内容: sed -i '/aaa/{N;d;}' f ...

  3. 4.Shell内部命令

    4.Shell内部命令内部命令是由shell自身提供的.如果某个内部命令的名称是一个简单命令的第一个单词,shell会直接执行这个命令,而不会启动其它程序.对于一些不可能或者不方便通过外部程序实现的功 ...

  4. 运维 04 Shell基础命令(二)

    Shell基础命令(二)   查看Linux的发行版 cat /etc/redhat-release cat /etc/os-release 查看系统用户的id信息 id 用户名 id root id ...

  5. shell 一些命令(转)

    shell 一些命令(转) https://www.cnblogs.com/amei0/p/8041989.html 参考文档 http://man.linuxde.net/ 一.awk 求和 awk ...

  6. linux 基础 shell脚本命令

    #########shell脚本命令#### 1.diff diff file file1 ####比较两个文件的不同 -c ####显示周围的行 -u ####按照一格式统一输出生成补丁 -r ## ...

  7. Linux Shell : Test命令参数解析

    格式: test conditions test -n string : string 不为空 test -z string : string 为空 test int1 -eq int2  : int ...

  8. Linux shell 内部命令与外部命令有什么区别以及怎么辨别

    内部命令实际上是shell程序的一部分,其中包含的是一些比较简单的linux系统命令,这些命令由shell程序识别并在shell程序内部完成运行,通常在linux系统加载运行时shell就被加载并驻留 ...

  9. mac os 添加用户到组 命令

    mac os 添加用户到组 命令 dscl localhost 进入组目录 cd /Local/Default/Groups 使用ls 你就可以看到所有的group,? /Local/Default/ ...

随机推荐

  1. 嵌入式Boa服务器上CGI开发-(转自Bryce.Xiao)

    嵌入式WEB服务器常见的有lighttpd shttpd thttpdboa mathopd minihttpdappwebgoahead=============================== ...

  2. Apple Xcode 12.5 (12E262) 正式版发布 - 构建 Universal App

    请访问原文链接:https://sysin.org/article/apple-xcode-12/,查看最新版.转载请保留出处. Xcode 12 简介 Xcode 12 采用全新设计,在 macOS ...

  3. GO语言的JSON01---序列化

    package main import ( "encoding/json" "fmt" ) /* 定义待序列化结构体 属性一定要可见,否则json包无法访问 * ...

  4. Tomcat配置probe详细过程(步骤加截图)

    1.先下载probe.war包,点击probe.war进行下载:附上下载地址:https://github.com/psi-probe/psi-probe/releases 2.将probe.war包 ...

  5. QT Dialog模态与非模态

    模态 // 创建对话框窗口 TestDialog* dlg = new TestDialog(this); // 阻塞程序的运行 dlg->exec(); 这样的话,当运行对话窗口的时候,会阻塞 ...

  6. QT环境变量设置

    首先找到自己对应的目录 我的如图 还有一个路径 将这两个路径添加到系统变量的Path中

  7. <题解>幻想乡战略游戏

    洛谷题目 看到题面,很容易就想到,这是要你找树上的重心,只不过这个重心是在带边权的树上 所以对于这个我们在树上找这个重心 一开始我想的是,我要更新权值,然后把每个点的答案更新一下 就取最大值,这好像是 ...

  8. 面试总被问到HTTP缓存机制及原理?看完你就彻底明白了

    前言 Http 缓存机制作为 web 性能优化的重要手段,对于从事 Web 开发的同学们来说,应该是知识体系库中的一个基础环节,同时对于有志成为前端架构师的同学来说是必备的知识技能. 但是对于很多前端 ...

  9. Optim High Performance Unload [HPU]

    目录 一.简介 二.安装 三.基本配置 四.卸数 4.1. 命令行导出 4.2. 控制文件 4.2.1. 从表空间容器卸数 4.2.2. 从备份镜像中卸数 4.2.3. 控制文件语法参考 4.3. 存 ...

  10. 不下软件,照样可以完美正确格式化树莓派SD卡!(恢复U盘/SD卡到满容量)

    树莓派作用千千万,系统崩溃的理由也数不胜数(不要问我为啥知道),所以系统的重装和sd卡的格式化也在所难免.顺便给大家看一下我今天的成果,我不就是不小心摔了一下我的树莓派...我和sd卡一定是冤家! 捡 ...