理解 exec &> >(tee -a -i test.log) 之 subshell

理解 exec &> >(tee -a -i test.log) 之 subshell

子进程,是从父子进程的概念出发的,unix操作系统的进程从init(有些系统如CentOS 7.x 是从systemd开始的)进程开始均有其对应的子进程,就算是由于父进程先行结束导致的孤儿进程,也会被init领养,使其父进程ID为1。我们这里只讨论bash下什么时候创建子进程。先实验再总结。

准备环境

  • 系统 CentOS 7.5

  • 用到的分析工具

    • htop 据说是比 top 更漂亮的系统监控工具这里我们用来看进程间的关系

      1
       1023 root         0:04.59 ├─ /usr/sbin/sshd -D
      2
      48371 root         0:00.06 │  ├─ sshd: root@pts/2
      3
      48374 root         0:00.02 │  │  └─ -bash
      4
      51925 root         0:00.71 │  │     └─ htop
      5
      47866 root         0:00.01 │  └─ sshd: root@pts/0
      6
      47868 root         0:00.00 │     └─ -bash

      (删减部分无关内容)

    • strace 功能强大的Linux调试分析诊断工具

      举个例子

      1
      [root@master ~]# echo "aaa"
      2
      aaa

      输入到底发生了什么?执行一下

      1
      [root@master ~]# strace echo "aaa"
      2
      execve("/usr/bin/echo", ["echo", "aaa"], [/* 22 vars */]) = 0
      3
      write(1, "aaa\n", 4aaa)                    = 4
      4
      +++ exited with 0 +++

    ​ 删减部分内容。

    • pstree 以树状结构显示进程间关系的工具

      1
      ├─sshd,1023 -D
      2
      │   ├─sshd,35522
      3
      │   │   └─bash,35524
      4
      │   │       └─pstree,35589 -ap
      5
      │   └─sshd,47866
      6
      │       └─bash,47868

命令执行的几种方式

1. shell 中直接执行

1
[root@master ~]# sleep 100

2. 以文件的形式执行

以文件执行有两种方式

./test.sh 方式执行

举个例子 shell 脚本为

1
#!/bin/bash
2
sleep 100

执行方式

1
[root@master ~]# ./test.sh

进程关系(bash 在执行外部命令会单独分配子进程)

1
│   ├─sshd,35522
2
│   │   └─bash,35524
3
│   │       └─test.sh,42281 ./test.sh
4
│   │           └─sleep,42282 100

可以看到 bash 为 test.sh 分配一个子进程用于执行这个脚本。

总结: ./test.sh这种方式由shell分配一个子进程来执行此脚本

source test.sh 执行

使用 source 命令并不会为单独创建进程用于执行这个脚本。而是在当前 shell 下执行。因此脚本中的环境变量等值会被带到当前 shell 环境中这也是我们在修改/etc/profile之后 source /etc/profile 一下

. test.shsource test.sh 执行方式一样直接在当前 shell 下执行可以参考下面的 man source

1
.  filename [arguments]
2
source filename [arguments]
3
          Read and execute commands from filename  in  the  current  shell
4
          environment  and return the exit status of the last command exe‐

还是举出上个例子

1
#!/bin/bash
2
sleep 100

执行方式

1
[root@master ~]# . test.sh

进程间的关系

1
├─sshd,1023 -D
2
│   ├─sshd,35522
3
│   │   └─bash,35524
4
│   │       └─sleep,42402 100

总结: 在使用 source test.sh 或者 . test.sh 方式执行脚本的时候 bash 并不会单独为此脚本分配一个子进程

那些情况下会创建子进程

1. 执行外部脚本,和程序

参见上述例子

2. 使用 $() 命令替换的时候

Linux 使用反引号”`”或者$()来执行命令替换。使用括号来组合一系列命令。

例如

1
[root@master test]# echo what date? `date +%F`
2
what date? 2018-12-30

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

Bash 手册 命令替换中有详细介绍

1
Bash performs the expansion by executing command in a subshell environment and replacing the command substitution with the standard output of the command, with any trailing newlines deleted. Embedded newlines are not deleted, but they may be removed during word splitting. The command substitution $(cat file) can be replaced by the equivalent but faster

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

3. & 提交后台作业的时候

& 放在启动参数后面表示设置此进程为后台进程

4. 使用管道

管道中的每个命令都作为单独的进程来执行(即,在一个子 shell 中启动)。

6. 使用 () 命令组合的时候

命令组()括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。

1
Placing a list of commands between parentheses causes a subshell environment to be created

命令使用{} 包围或者bash 内置的命令都不会单独创建子进程

(待修改-)