進入子shell的各種情況分析

来源:http://www.cnblogs.com/f-ck-need-u/archive/2017/08/28/7446194.html
-Advertisement-
Play Games

子shell的概念貫穿整個shell,寫shell腳本時更是不可不知。所謂子shell,即從當前shell環境新開一個shell環境,這個新開的shell環境就稱為子shell(subshell),而開啟子shell的環境稱為該子shell的父shell。子shell和父shell的關係其實就是子進 ...


子shell的概念貫穿整個shell,寫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,這不同於特殊變數"$$"值,因為"$$"會從父進程繼承。

何時產生子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命令在哪裡執行呢?執行cp命令敲入回車後,當前的bash進程fork出一個子bash,然後子bash通過exec載入cp程式替代子bash。這算是進入了子shell嗎?更通用的問題是:什麼情況下會進入子shell環境,什麼時候不進入子shel環境呢?

判斷是否進入了子shell的方式非常簡單,執行"echo $BASHPID",如果該值和父bash進程的pid值不同,則表示進入了子shell。在shell中是否進入子shell的情況可以分為幾種:

①.執行bash內置命令時。

bash內置命令是非常特殊的,父進程不會創建子進程來執行這些命令,而是直接在當前bash環境中執行。但如果將內置命令放在管道後,則此內置命令將和管道左邊的進程同屬於一個進程組,所以仍然會創建子shell。

[root@xuexi ~]# echo $BASHPID   # 當前BASHPID
65230
[root@xuexi ~]# let a=$BASHPID   # bash內置命令,不進入子shell
[root@xuexi ~]# echo $a
65230
[root@xuexi ~]# echo $BASHPID
65230
[root@xuexi ~]# cd | expr $BASHPID      # 管道使得任何命令都進入進程組,會進入子shell   
65603

 

②.執行bash命令本身時。

這是一個很巧合的命令。bash命令本身是bash內置命令,在當前shell環境下執行內置命令本不會創建子shell,也就是說不會有獨立的bash進程出現,而實際結果則表現為新的bash是一個子進程。其中一個原因是執行bash命令會載入各種環境配置項,為了父bash的環境得到保護而不被覆蓋,所以應該讓其以子shell的方式存在。雖然fork出來的bash子進程內容完全繼承父shell,但因重新載入了環境配置項,所以子shell沒有繼承普通變數,更準確的說是覆蓋了從父shell中繼承的變數。不妨試試在/etc/bashrc文件中定義一個變數,再在父shell中export名稱相同值卻不同的環境變數,然後到子shell中看看該變數的值為何?

[root@xuexi ~]# echo "var=55" >>/etc/bashrc
[root@xuexi ~]# export var=66
[root@xuexi ~]# bash
[root@xuexi ~]# echo $var
55

由結果55可知,執行bash時載入的/etc/bashrc中的變數覆蓋了父bash中的導出的環境變數值66。

其實執行bash命令,既可以認為進入了子shell,也可以認為沒有進入子shell。從bash是內置命令的角度來考慮,它不會進入子shell,這一點在執行bash命令後從變數$BASH_SUBSHELL的值為0可以驗證出來。但從執行bash命令後進入了新的shell環境來看,它有其父bash進程,且$BASHPID值和父shell不同,所以它算是進入了子shell。

[root@xuexi ~]# echo $BASHPID
65230
[root@xuexi ~]# bash
[root@xuexi ~]# echo $BASHPID
65534

 

③.執行shell腳本時。

腳本中第一行總是"#!/bin/bash"或者直接"bash xyz.sh",這和上面的執行bash進入子shell其實是一回事,都是使用bash命令進入子shell。只不過此時的bash命令和情況②中直接執行bash命令所隱含的選項不一樣,所以繼承和載入的shell環境也不一樣。事實也確實如此,它僅只繼承父shell的某些環境變數,其餘環境一概初始化

另外,執行shell腳本相比於直接執行bash命令,還多了一個動作:腳本執行完畢後自動退出子shell。

[root@xuexi ~]# cat b.sh 
#!/bin/bash
echo $BASHPID

[root@xuexi ~]# echo $BASHPID
65534
[root@xuexi ~]# ./b.sh 
65570

 

④.執行shell函數時。

其實shell函數就是命令,它和bash內置命令的情況一樣。直接執行時不會進入子shell,但放在管道後會進入子shell。

[root@xuexi ~]# fun_test (){ echo $BASHPID; }   # 定義一個函數,輸出BASHPID變數的值
[root@xuexi ~]# echo $BASHPID 
65230
[root@xuexi ~]# fun_test      # 說明執行函數不會進入子shell
65230
[root@xuexi ~]# cd | fun_test   # 但放在管道後會進入子shell
65605

 

⑤.執行非bash內置命令時。

例如執行cp命令、grep命令等,它們直接fork一份bash進程,然後使用exec載入程式替代該子bash。此類子進程會繼承所有父bash的環境。但嚴格地說,這已經不是子shell,因為exec載入的程式已經把子bash進程替換掉了,這意味著丟失了很多bash環境。在bash文檔中,直接稱呼這種環境為"單獨的環境",和子shell的概念類似。

[root@xuexi ~]# let a=$BASHPID   # let是內置命令
[root@xuexi ~]# echo $a
65230
[root@xuexi ~]# echo $BASHPID    # echo是非內置命令,結果是不進入子shell
65230

 

⑥.命令替換。

當命令行中包含了命令替換部分時,將開啟一個子shell先執行這部分內容,再將執行結果返回給當前命令。因為這次的子shell不是通過bash命令進入的子shell,所以它會繼承父shell的所有變數內容。這也就解釋了"echo $(echo $$)"中"$$"的結果是當前bash的pid號,而不是子shell的pid號,但"echo $(echo $BASHPID)"卻和父bash進程的pid不同,因為它不是使用bash命令進入的子shell。

[root@xuexi ~]# echo $BASHPID
65230
[root@xuexi ~]# echo $(echo $BASHPID)      # 使用命令替換$()進入子shell
65612

 

⑦.使用括弧()組合一系列命令。

例如(ls;date;echo haha),獨立的括弧將會開啟一個子shell來執行括弧內的命令。這種情況等同於情況⑤。

[root@xuexi ~]# echo $BASHPID
65230
[root@xuexi ~]# (echo $BASHPID)  # 使用括弧()的命令組合進入子shell
65613

 

⑧.放入後臺運行的任務

它不僅是一個獨立的子進程,還是在子shell環境中運行的。例如"echo hahha &"。

[root@xuexi ~]# echo $BASHPID
65230
[root@xuexi ~]# echo $BASHPID &   # 放入後臺運行的任務進入子shell
[1] 65614
[root@xuexi ~]# 65614

[1]+  Done                    echo $BASHPID

 

⑨.進程替換

既然是新進程了,當然進入子shell執行。例如"cat <(echo haha)"。

[root@xuexi ~]# echo $BASHPID
65230

[root@xuexi ~]# cat <(echo $BASHPID)    # 進程替換"<()"進入子shell
65616

需要說明的是,子shell的環境設置不會粘滯到父shell環境,也就是說子shell的變數等不會影響父shell。

最後,建議同時閱讀另一篇文章:bash啟動時環境配置流程,此文中詳細解釋了bash啟動時載入哪些配置文件。

 

回到系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/7446194.html

註:若您覺得這篇文章還不錯請點擊下右下角的推薦,有了您的支持才能激發作者更大的寫作熱情,非常感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.安裝 安裝的時候會提示設置密碼 2.使用 (1)mysql操作 (2)資料庫操作 (3)表操作 (4)數據操作 其它sql詳細語法參考: 21分鐘 MySQL 入門教程 ...
  • 1.下載mysql 網址: https://dev.mysql.com/downloads/mysql/ 2.選擇源碼包,通用版點擊下載 直接下載就可以了,不用登錄 3.解壓編譯 tar -zxvf mysql-5.7.19.tar.gz cd mysql-5.7.19.tar.gz 創建數據目錄 ...
  • 常見的HTTP響應狀態碼解析 1XX Informational(信息性狀態碼) 2XX Success(成功狀態碼) 3XX Redirection(重定向狀態碼) 4XX Client Error(客戶端錯誤狀態碼) 5XX Server Error(伺服器錯誤狀態碼) 1xx(臨時響應) 表示 ...
  • 轉儲文件也就是我們常說的dump文件。可以把轉儲文件看成軟體的某個時刻的一個快照。轉儲文件一般都是在軟體出現問題時手動生成或者程式自動生成。下麵我們介紹幾種生成轉儲文件的方法。 1. 任務管理器 任務管理器可以說是最易獲取的系統工具,同時它具有生成轉儲文件的功能。但要註意的是在64位操作系統上面,默 ...
  • glibc-commons 安裝了兩個版本,導致依賴glibc-commons的很多軟體包 被安裝了兩個版本; 解決辦法就是 先清除這些重覆的已安裝的軟體,然後執行 yum update 將 glibc-commons 更新一下 1.清除重覆的已安裝的包sudo package-cleanup -- ...
  • Linux 下沒有百度網盤客戶端,用瀏覽器下載速度慢得急死人 滑鼠移到鏈接處, 右鍵, 然後複製鏈接 接著在終端里輸入 axel 是下載程式名, -n 後面數字是線程數,多少自己決定, -o 後面下載到本機上 保持的文件名, 最後面 英文引號裡面放下載鏈接 使用這種方式 我的下載速度可以達到1.5M ...
  • SecureCRT預設不顯示語法高亮,整個界面顏色單一,用起來很不舒服,也沒有效率,所有通過設置一下語法高亮還是很有必要的, 預設字體也看著不是很清晰。所以還是修改一下預告高亮比較好 設置語法高亮,多色顯示 Options Session Options Emulation (Terminal) 其 ...
  • 要執行周期性任務,要保證服務運行。服務名為crond;service crond start ; systemctl start crond; 配置文件 /etc/crontab cron的日誌文件 cat /var/log/cron 一、簡介 crond是linux下用來周期性的執行某種任務或等待 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...