22 shell組命令與子進程


  • 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,()內部和外部的命令是在一個進程

(也就是當前進程)中執行的

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM