Bourne Shell 指令碼/環境
沒有哪個程式是孤立存在的。即使是Bourne Shell也不例外。每個程式都在一個環境中執行,這是一個控制程式執行方式以及程式擁有和可以建立的外部連線的資源系統。並且程式本身可以在其中進行更改。
在本模組中,我們將討論環境,即每個程式和命令生存和執行的棲息地。我們將瞭解環境由什麼組成,它來自哪裡以及它將去往哪裡……並且我們將討論Shell用於傳遞資料的最重要的機制:環境變數。
在討論Unix Shell時,你經常會遇到“環境”這個術語。這個術語用於描述程式執行的上下文,通常是指一組“環境變數”(我們很快就會講到這些)。但實際上,有兩個不同的術語在某種程度上是程式的環境,並且在“環境”中經常被混淆在一起。其中較簡單的一個實際上是環境變數的集合,實際上被稱為“環境”。第二個是影響程式執行的一組更廣泛的資源,稱為命令執行環境。
每個正在執行的程式,無論是使用者從Shell中直接啟動的,還是由另一個程序間接啟動的,都在其命令執行環境(CEE)中的一組全域性資源中執行。
程式的CEE包含重要資訊,例如程式可以操作的資料的源和目標(也稱為標準輸入、標準輸出和標準錯誤控制代碼)。此外,還定義了變數,這些變數列出了啟動程式的使用者或程序的身份和主目錄、機器的主機名以及用於啟動程式的終端型別。還有其他變數,但這只是一些主要的變數。環境還為程式提供了工作空間,以及一種與將在同一環境中執行的其他未來程式進行通訊的簡單方法。
Shell的CEE中包含的完整資源列表為
- 在啟動Shell的父程序中持有的開啟的檔案。這些檔案是繼承的。此檔案列表包括透過重定向訪問的檔案(例如標準輸入、輸出和錯誤檔案)。
- 當前工作目錄:Shell的“當前”目錄。
- 檔案建立模式:建立新檔案時設定的檔案許可權的預設集。
- 活動的陷阱。
- 在呼叫Shell期間設定或從父程序繼承的Shell引數和變數。
- 從父程序繼承的Shell函式。
- 由set或shopts設定的Shell選項,或作為Shell可執行檔案的命令列選項。
- Shell別名(如果你的Shell中可用)。
- Shell的程序ID以及父程序啟動的一些程序的程序ID。
每當Shell執行啟動子程序的命令時,該命令將在其自己的CEE中執行。此CEE繼承其父級的CEE的一部分副本,但不是整個父級CEE。繼承的副本包括
- 開啟的檔案。
- 工作目錄。
- 檔案建立模式掩碼。
- 標記為匯出到子程序的任何Shell變數和函式。
- Shell設定的陷阱。
“set”命令允許你設定或停用CEE的一部分並影響Shell行為的許多選項。要設定選項,請使用“-”後跟一個或多個標誌作為命令列引數呼叫set。要停用該選項,請使用“+”然後是相同的標誌呼叫set。你可能不會經常使用這些選項;“set”最常見的用法是在沒有任何引數的情況下呼叫它,這將生成環境中所有已定義名稱(變數和函式)的列表。以下是一些你可能會用到的選項
- +/-a
- 設定後,自動將所有新建立或重新定義的變數標記為匯出。
- +/-f
- 設定後,忽略檔名元字元。
- +/-n
- 設定後,僅讀取命令但不執行它們。
- +/-v
- 設定後,導致Shell在從輸入讀取時列印命令(詳細除錯標誌)。
- +/-x
- 設定後,導致Shell在執行命令時列印命令(除錯標誌)。
同樣,你可能主要是在沒有引數的情況下使用set,以檢查已定義變數的列表。
CEE的一部分是簡單稱為環境的東西。環境是名為環境變數的名稱/值對的集合。從技術上講,這些變數還包含Shell函式,但我們將在單獨的模組中討論這些函式。
環境變數是環境中的一塊帶標籤的儲存空間,你可以在其中儲存任何你想要的東西,只要它適合即可。這些空間被稱為變數,因為你可以改變你放入其中的內容。你只需要知道用於儲存內容的名稱(標籤)。Bourne Shell也使用這些“環境變數”。你可以編寫檢查這些變數的指令碼,並且這些指令碼可以根據儲存在變數中的值做出決策。
環境變數是以下形式的名稱/值對
這也是建立變數的方式。有多種使用變數的方法,我們將在關於替換的模組中討論,但現在我們將限制自己使用簡單的方法:如果你在變數名前面加上一個$-字元,Shell將用變數的值替換該變數。例如
正如您從上面的示例中看到的,環境變數有點像公告板:任何人都可以在那裡釋出任何型別的數值,供所有人讀取(只要他們有權訪問該公告板)。並且任何釋出的值都可以被任何讀者以他們喜歡的任何方式解釋。這使得環境變數成為在不同位置之間傳遞資料的一種非常通用的機制。因此,環境變數用於各種用途。例如,用於設定程式在執行過程中可以使用的全域性引數。或者從一個 shell 指令碼中設定一個值,以便另一個指令碼獲取。Shell 本身在其配置中也使用許多環境變數。一些典型的示例
- IFS
- 此變數列出了 shell 認為是空白字元的字元。
- PATH
- 此變數被解釋為目錄列表(在 Unix 系統上由冒號分隔)。每當您鍵入可執行檔案的名稱以供 shell 執行但未包含該可執行檔案的完整路徑時,shell 將按順序檢視所有這些目錄以查詢可執行檔案。
- PS1
- 此變數列出了一些程式碼。這些程式碼指示您的 shell 互動式 shell 中的命令列提示符應該是什麼樣子。
- PWD
- 此變數的值始終是工作目錄的路徑。
如上所述,環境變數的絕對優點在於它們只包含隨機的字元字串,沒有直接的含義。任何變數的含義都由讀取該變數的任何程式或程序來解釋。因此,一個變數可以儲存任何型別的資訊,並且可以在任何地方使用。例如,考慮以下示例
$ echo $CMD
$ CMD=ls
$ echo $CMD
ls
$ $CMD
bin booktemp Documents Mail mbox public_html sent
將變數設定為可執行檔案的名稱,然後透過將變數作為命令來執行該可執行檔案,這沒有任何問題。
儘管您以相同的方式使用所有環境變數,但有幾種不同的變數型別。在本節中,我們將討論它們之間的區別及其用途。
最簡單和最直接的環境變數是命名變數。我們之前見過它:它只是一個帶有值的名稱,可以透過在名稱前加上“$”來檢索。您可以透過鍵入名稱、等號,然後鍵入生成字元字串的內容來一次建立和定義命名變數。
之前我們看到了以下簡單的示例
$ VAR=Hello
這只是分配一個簡單值。定義變數後,我們也可以重新定義它
$ VAR=Goodbye
我們也不限於簡單的字串。我們可以很容易地將一個變數的值賦給另一個變數
$ VAR=$PATH
我們甚至可以全力以赴,組合多個命令來生成一個值
$ PS1= "`whoami`@`hostname -s` `pwd` \$ "
在這種情況下,我們獲取三個命令“whoami”、“hostname”和“pwd”的輸出,然後新增“$”符號,以及一些空格和其他格式,只是為了稍微填充一下。哇。所有這些,都放在一個帶標籤的空間裡。正如您所看到的,環境變數可以儲存相當多的內容,包括整個命令的輸出。
即使您沒有意識到,您的環境中通常也定義了許多命名變數。嘗試“set”命令並檢視。
shell 中的大多數環境變數都是命名變數,但也有一些“特殊”變數。您不會設定這些變數,但它們的數值由 shell 自動安排和維護。這些變數的存在是為了幫助您發現有關 shell 或環境的資訊。
最常見的是位置變數或引數變數。您在 shell 中執行的任何命令(在互動模式或指令碼中)都可以有命令列引數。即使命令實際上沒有使用它們,它們仍然可以存在。您可以透過在命令之後簡單地鍵入它們來將命令列引數傳遞給命令,如下所示
這適用於任何命令。甚至您自己的 shell 指令碼。但是假設您這樣做(建立一個 shell 指令碼,然後使用引數執行它);您如何從指令碼中訪問命令列引數?這就是位置變數的作用。當 shell 執行命令時,它會自動將任何命令列引數按順序分配給一組位置變數。這些變數的名稱是數字:1 到 9,透過 $1 到 $9 訪問。實際上是 0 到 9;$0 是執行的命令的名稱。例如,考慮這樣的指令碼
#!/bin/sh
echo $0
echo $1
echo $2
以及對該指令碼的以下呼叫
如您所見,shell 自動將值“Hello”和“World”分配給 $1 和 $2(好吧,從技術上講是分配給名為 1 和 2 的變數,但在書面文字中稱它們為 $1 和 $2 不會那麼令人困惑)。如果我們使用兩個以上的引數呼叫此指令碼會發生什麼?
這根本不是問題——額外的引數將分配給 $3 和 $4。但是我們沒有在指令碼中使用這些變數,因此這些命令列引數會被忽略。相反的情況(引數太少)呢?
同樣,沒問題。當指令碼訪問 $2 時,shell 只會用 $2 的值替換 $2。在這種情況下,該值為空,因此我們列印完全相同的內容。在這種情況下,這不是問題,但如果您的指令碼有強制性引數,則應檢查它們是否確實存在。
如果我們希望“Hello”和“World”被視為要傳遞給指令碼的一個命令列引數怎麼辦?即“Hello World”而不是“Hello”和“World”?當我們開始討論引用時,我們將深入探討這一點,但現在只需用單引號將這些單詞括起來
$ WithArgs.sh 'Hello World' 'Mouse Cheese'
WithArgs.sh
Hello World
Mouse Cheese
那麼,如果您有超過九個命令列引數會發生什麼?然後您的指令碼過於複雜。不,但說真的:然後您遇到了一點小問題。允許傳遞超過九個引數,但只有九個位置變數(至少在 Bourne Shell 中是這樣)。為了處理這種情況,shell 包含了shift命令
Shift 導致位置引數向左移動。也就是說,$1 的值變成 $2 的舊值,$2 的值變成 $3 的舊值,依此類推。使用shift,您可以訪問所有命令列引數(即使超過九個)。shift 的可選整數引數是要移動的位置數(因此您可以一次移動任意多個位置)。不過,需要注意以下幾點
- 無論您移位多少次,$0 始終保持原始命令。
- 如果您移位 n 個位置,則 n 必須小於引數的數量。如果 n 大於引數的數量,則不會發生移位。
- 如果您移位 n 個位置,則前 n 個引數將丟失。因此,請確保您已將它們儲存在其他位置,或者您不再需要它們!
- 您無法向右移位。
在關於控制流的模組中,我們將瞭解如何在不知道引數的確切數量的情況下遍歷所有引數。
除了位置變數之外,Bourne Shell 還包含許多其他特殊變數,其中包含有關 shell 的特殊資訊。您可能不會經常使用這些變數,但瞭解它們的存在總是一件好事。這些變數是
- $#
- 當前命令的命令列引數數量(在使用shift命令後會發生變化!)。
- $-
- 當前生效的 shell 選項(參見“set”命令)。
- $?
- 最後執行命令的退出狀態(如果成功則為 0,如果出錯則為非零)。
- $$
- 當前程序的程序 ID。
- $!
- 最後一個後臺命令的程序 ID。
- $*
- 所有命令列引數。帶引號時,擴充套件為所有命令列引數作為一個單詞(即“$*" = "$1 $2 $3 ...")。
- $@
- 所有命令列引數。帶引號時,擴充套件為所有命令列引數分別帶引號(即“$@" = "$1" "$2" "$3" ...)。
我們之前已經提到過幾次:Unix 是一個多使用者、多處理的作業系統。Bourne Shell 完全支援這一事實,它允許您直接在正在執行的 shell 中啟動新程序。事實上,您甚至可以在彼此旁邊同時執行多個程序(但我們稍後再討論這一點)。這是一個啟動子程序的簡單示例
$ sh
我們還討論了命令執行環境和環境(後者是變數的集合)。這些環境會影響程式的執行方式,因此它們不能無意中相互影響這一點非常重要。畢竟,您不希望僅僅因為有人在另一個程序中啟動了 Midnight Commander,您的 shell 中的螢幕就變成藍色帶黃色字母,對吧?
shell 為避免程序無意中相互影響所做的事情之一是環境分離。基本上,這意味著每當啟動一個新的(子)程序時,它都會擁有自己的 CEE 和環境。當然,如果您的 shell 的子程序的環境完全為空,那將非常不方便;您的子程序將沒有 PATH 變數或您為提示符格式選擇的設定。另一方面,通常有充分的理由不在子程序的環境中包含某些變數,並且通常與在程序不需要這些資料時不向其傳遞過多的環境資料有關。在執行 MS-DOS 的副本和 Windows 下的 DOS 版本時,尤其如此。您只有有限的環境空間,因此您必須小心使用它,或者在啟動時請求更多空間。如今在 UNIX 環境中,空間問題不再相同,但是如果所有現有變數最終都進入子程序的環境,您仍然可能會對在該子程序中啟動的程式的執行產生不利影響(在子程序的情況下,保持環境精簡和乾淨確實有其道理)。
Stephen Bourne 和其他人提出的兩種極端情況之間的折衷方案是:子程序有一個環境,其中包含其父程序環境中變數的副本——但僅限於標記為要匯出(即複製到子程序)的變數。換句話說,您可以將任何變數複製到子程序的環境中,但您必須首先讓 shell 知道您想要這樣做。這是一個區分的示例
$ echo $PATH
/usr/local/bin:/usr/bin:/bin
$ VAR=value
$ echo $VAR
value
$ sh
$ echo $PATH
/usr/local/bin:/usr/bin:/bin
$ echo $VAR
$
在上面的示例中,PATH 變數(預設情況下標記為匯出)被複制到在 shell 內啟動的 shell 的環境中。但是 VAR 變數未標記為匯出,因此第二個 shell 的環境不會獲得副本。
要將變數標記為匯出,您可以使用export命令,如下所示
如您所見,您可以在一次操作中匯出任意數量的變數。您也可以在沒有引數的情況下發出export命令,這將列印環境中標記為匯出的變數列表。這是一個匯出變數的示例
$ VAR=value
$ echo $VAR
value
$ sh
$ echo $VAR
$ exit #Quitting the inner shell
$ export VAR #This is back in the outer shell
$ sh
$ echo $VAR
value
Korn Shell 和 Bash 等更現代的 shell 具有更擴充套件形式的export。一個常見的擴充套件是允許在一個命令中定義和匯出變數。另一個是允許您刪除變數的匯出標記。但是,Bourne Shell 只支援如上所述的匯出。
在前面的部分中,我們討論了使用 shell 執行的每個程式和命令的執行時環境。我們討論了命令執行環境,並詳細討論了其中一個名為“環境”的部分,其中包含環境變數。我們已經看到,您可以定義自己的變數,並且系統通常已經有很多變數可以開始使用。
這裡有一個關於系統開始時設定的那些變數的問題:它們從哪裡來?它們像甘露一樣從天而降嗎?相關問題是:如果每次 shell 啟動時您都想要自動建立一些變數該怎麼辦?或者每次登入時都執行一個程式?
那些在其他作業系統上進行過一些挖掘的讀者會知道我的意思:通常有一些方法可以在每次登入時(或至少在每次系統啟動時)執行一組命令。例如,在 MS-DOS 中,有一個名為 autoexec.bat 的檔案,該檔案在每次系統啟動時都會執行。在舊版本的 MS-Windows 中,有 system.ini。Bourne Shell 有類似的東西:每個使用者主目錄中名為.profile的檔案。$HOME/.profile(HOME 是一個預設變數,其值為您的主目錄)檔案與任何其他 shell 指令碼一樣,在您登入到新的 shell 會話後會自動執行。您可以編輯指令碼以使其執行您喜歡的任何登入命令。
每個特定的 Unix 系統都有自己預設的 .profile 指令碼實現(包括無——允許不具有 .profile 指令碼)。但它們都以某種形式的以下內容開頭
#!/bin/sh
if [ -f /etc/profile ]; then
. /etc/profile
fi
PS1= "`whoami`@`hostname -s` `pwd` \$ "
export PS1
這個 .profile 可能會讓您有點驚訝:設定的所有這些變數在哪裡?在典型的 Unix 系統上為您設定的大多數變數也為所有其他使用者設定。為了使這成為可能並易於維護,常見的解決方案是讓每個 $HOME/.profile 指令碼首先執行另一個 shell 指令碼:/etc/profile。此指令碼是一個系統範圍的指令碼,其內容由系統管理員(以使用者名稱root登入的使用者)維護。此指令碼設定各種變數並呼叫設定更多變數的指令碼,並通常執行提供每個使用者舒適工作環境所需的一切操作。
從上面的示例中可以看到,您可以將任何您想要或需要的個人配置新增到您目錄中的 .profile 指令碼中。執行系統配置檔案指令碼的呼叫不必是第一個,但您可能不想完全刪除它。
隨著快速計算機、能夠在極短時間內在多個任務之間切換的 CPU、能夠同時執行多項任務的 CPU 以及多個 CPU 的網路的出現,讓計算機同時執行多個任務已變得司空見慣。快速任務切換提供了計算機確實能夠同時執行多個任務的錯覺,從而能夠有效地同時為多個使用者提供服務。並且能夠在舊任務等待外設裝置時切換到新的 CPU 任務,這使得 CPU 使用效率大大提高。
為了利用多工處理功能作為使用者,您需要一個支援多工處理的命令環境。例如,能夠將一個程式設定為一個任務,然後繼續並啟動一個新程式而舊程式仍在執行。這種能力允許您作為使用者在同一臺機器上同時執行多項操作,只要這些程式不相互干擾即可。當然,您不能總是將每個程式視為“啟動並忘記”的事情;您可能需要輸入密碼,或者程式可能已完成並希望告訴您其結果。多工處理環境必須允許您在正在執行的多個程式之間切換,並允許這些程式在需要您的注意時向您傳送某種訊息。
為了使事情更易於理解,請考慮像下載檔案這樣的事情。通常,在下載檔案時,您也希望做其他事情——否則,當您想要下載整張 CD 的資料時,您將不得不長時間坐在鍵盤前空轉。因此,您啟動檔案下載器併為其提供要獲取的檔案列表。輸入完檔案後,您可以告訴它“開始!”,它將首先開始下載第一個檔案,並繼續直到完成最後一個檔案,或者直到出現問題。更智慧的檔案下載器甚至會嘗試自行解決常見問題,例如檔案不可用。一旦啟動,您將獲得標準的 shell 提示符,讓您知道可以啟動另一個程式。
如果您想檢視檔案下載器的下載進度,只需檢查系統中的檔案與列表中的檔案即可。但另一種通知您的方法是透過環境。環境可以包含您使用的檔案,這可以幫助提供有關正在執行的程式(如檔案下載器)的進度資訊。它是否下載了所有檔案?如果檢查狀態檔案,您將看到它已下載了 65% 的檔案,並且現在正在處理最後三個檔案。
其他一些不需要手動干預的程式示例包括播放音樂的程式。通常情況下,一旦你啟動了一個播放音樂軌道的程式,你**不希望**告訴程式“好的,現在播放下一首曲目”。它應該能夠自行完成此操作,前提是給定了一個要播放的歌曲列表。事實上,它甚至不需要佔用監視器;你可以在按下“播放”按鈕後立即開始執行其他軟體。
在本節中,我們將探討 Unix shell 中的多工支援。我們將瞭解如何啟用支援、如何處理多個任務以及 shell 提供的可用實用程式。
在我們討論 shell 中多工處理的機制之前,讓我們先了解一些術語。這將有助於我們清楚地討論主題,你也會知道在其他地方遇到這些術語時它們指的是什麼。
首先,當我們在系統上以其自身程序的方式啟動一個程式執行時,該程序與該程式的一個正在執行的例項稱為一個作業。你還會遇到諸如程序、任務、例項或類似的術語。但 Unix shell 中使用的術語是作業。其次,shell 影響和使用多工處理(啟動作業等)的能力稱為作業控制。
- 作業
- 正在執行計算機程式例項的程序。
- 作業控制
- 能夠選擇性地停止(掛起)作業的執行並在稍後繼續(恢復)其執行。
請注意,這些術語在 Unix shell 中以這種方式使用。其他情況和其他上下文可能允許不同的定義。以下是一些你還會遇到的其他術語
- 作業 ID
- 唯一標識作業的 ID(通常為整數)。可用於引用不同工具和命令的作業。
- 程序 ID(或 PID)
- 唯一標識程序的 ID(通常為整數)。可用於引用不同工具和命令的程序。與作業 ID 不同。
- 前臺作業(或前臺程序)
- 可以訪問終端的作業(即可以從鍵盤讀取並寫入監視器)。
- 後臺作業(或後臺程序)
- 無法訪問終端的作業(即無法從鍵盤讀取或寫入監視器)。
- 停止(或掛起)
- 停止作業的執行並將終端控制權返回給 shell。停止的作業不是終止的作業。
- 終止
- 從記憶體中解除安裝程式並銷燬執行該程式的作業。
作業是在 shell 中啟動的程式。預設情況下,新作業將掛起 shell 並控制輸入和輸出:你在鍵盤上輸入的每個按鍵都將傳送到作業,滑鼠的每個移動也是如此。除了作業之外,沒有任何其他內容可以寫入監視器。這就是我們所說的前臺作業:它位於前臺,作為使用者對你來說清晰可見,並且遮擋了系統中所有其他作業的檢視。
但有時這種工作方式非常笨拙且令人惱火。如果你啟動了一個不需要你輸入的長時間執行的作業(例如硬碟備份)會怎樣?如果這是一個前臺程序,你必須等到它完成才能執行任何其他操作。在這種情況下,你更希望將程式作為後臺程序啟動:一個正在執行但不會監聽輸入裝置也不會寫入監視器的程序。Unix 支援它們,shell(使用作業控制)允許你將任何作業作為後臺作業啟動。
但是中間地帶呢?比如那個檔案下載器?你必須啟動它,登入到遠端伺服器,選擇你的檔案並開始下載。只有在完成所有這些操作後,該作業才有意義在後臺執行。但是,如果你已經將程式作為前臺作業啟動,該如何實現呢?或者考慮這種情況:你正在你喜歡的編輯器中忙著編寫文件,並且只想暫時退出檢視郵件。你是否必須為此關閉編輯器?然後,在你檢視完郵件後,重新啟動它,重新開啟你的檔案並找到你離開的位置?這很不方便。不,在這兩種情況下,更好的方法是簡單地掛起程式:只需停止它進一步執行並將控制權返回給 shell。一旦你回到 shell 中,你就可以啟動另一個程式(郵件),然後在你完成該程式後恢復掛起的程式(編輯器)——並準確地返回到你離開程式的位置。相反,你也可以決定讓掛起的程序(下載器)繼續執行,但現在在後臺執行。
當我們談論 shell 中的作業控制時,我們指的是上面描述的功能:在後臺啟動程式、掛起正在執行的程式以及恢復掛起的程式(在前臺或後臺)。
為了完成我們在上一節中討論的所有操作,你需要兩樣東西
- 支援作業控制的作業系統。
- 支援作業控制並已啟用作業控制的 shell。
Unix 系統支援多工處理和作業控制。Unix 從一開始就被設計為支援多工處理。如果你遇到一個聲稱是 Unix 供應商但其軟體不支援作業控制的人,稱他為騙子。然後扔掉他的安裝 CD。然後扔掉他本人。
當然,你已經猜到了接下來會發生什麼,對吧?我要告訴你 Bourne Shell 支援作業控制。並且你可以依靠相同的機制在所有相容的 shell 中工作。猜猜看:你錯了。原始的 Bourne Shell 沒有作業控制支援;它是一個單任務 shell。但是,Bourne Shell 有一個擴充套件版本,稱為jsh(猜猜“j”代表什麼……),它具有作業控制支援。要在原始 Bourne Shell 中使用作業控制,你必須以互動模式啟動此擴充套件 shell,如下所示
在該 shell 中,你擁有我們將在以下部分討論的作業控制工具。
從那時起編寫的幾乎所有其他 shell 都直接將作業控制整合到基本 shell 中,並且 POSIX 1003 標準已經標準化了作業控制實用程式。因此,你幾乎可以依賴現在可用的作業控制,並且通常在互動模式下預設啟用(一些較舊的 shell,如 Korn shell,具有支援,但要求你專門啟用該支援)。但以防萬一,請記住你可能需要在你的系統上執行一些額外操作才能使用作業控制。不過,有一個需要注意的地方:在 shell 指令碼中,你通常會包含一個呼叫 Bourne Shell 的直譯器提示(即#!/bin/sh)。由於原始的 Bourne Shell 沒有作業控制,因此許多現代 shell 在非互動模式下預設關閉作業控制作為相容性功能。
我們已經詳細討論瞭如何建立前臺作業:在提示符處鍵入命令或可執行檔名,按 Enter 鍵,這就是你的作業。已經做過,完成了,買了 T 恤。
我們也已經提到如何啟動後臺作業:在命令末尾新增一個&符號。
$ ls * > /dev/null &
[1] 4808
$
但現在看起來與我們之前發出命令時有所不同;那裡有一個“[1]”和一些數字。“[1]”是作業 ID,數字是程序 ID。我們可以使用這些數字來引用我們剛剛建立的程序和作業,這對於使用處理作業的工具很有用。當任務完成後,你會收到類似以下內容的通知
[1]+ Done ls * > /dev/null &
你用來管理作業的工具之一是“fg”命令。此命令獲取一個後臺作業並將其置於前臺。例如,考慮一個實際需要一些時間才能完成的後臺作業
while [ $CNT -lt 200000 ]; do echo $CNT >> outp.txt; CNT=$(expr $CNT + 1); done &
我們還沒有深入探討流程控制,但這會將 200,000 個整數寫入檔案,需要一些時間。它還在後臺執行。假設我們啟動了此作業
$ CNT=0
$ while [ $CNT -lt 200000 ]; do echo $CNT >> outp.txt; CNT=$(expr $CNT + 1); done &
[1] 11246
作業被賦予作業 ID 1 和程序 ID 11246。讓我們將程序移到前臺
$ fg %1
while [ $CNT -lt 200000 ]; do
echo $CNT >> outp.txt; CNT=$(expr $CNT + 1);
done
作業現在在前臺執行,你可以從我們沒有返回提示符這一事實中看出。現在鍵入CTRL+Z鍵盤組合
'CTRL+Z'
[1]+ Stopped while [ $CNT -lt 200000 ]; do
echo $CNT >> outp.txt; CNT=$(expr $CNT + 1);
done
$
你注意到 shell 報告作業已停止了嗎?嘗試使用“cat”命令檢查 outp.txt 檔案。嘗試幾次;內容不會改變。該作業不是後臺作業;它根本沒有執行!作業已掛起。許多程式識別CTRL+Z組合以進行掛起。即使那些沒有識別該組合的程式通常也有一些方法可以自行掛起。
作業掛起後,你可以將其恢復到前臺或後臺。要恢復到前臺,請使用前面討論的“fg”命令。你使用“bg”恢復到後臺
為了恢復我們之前編寫的生成數字的長期作業,我們需要執行以下操作
$ bg %1
[1]+ while [ $CNT -lt 200000 ] do
echo $CNT >> outp.txt; CNT=`expr $CNT + 1`;
done &
$
輸出表明作業已重新執行。這次是在後臺,因為我們也收到了一個提示。
我們能否也停止後臺的程序?當然可以,我們可以將其移動到前臺並按下“CTRL+Z”。但是我們能否直接做到這一點呢?嗯,沒有實用程式或命令可以做到這一點。大多數情況下,你不會想要這樣做——將其置於後臺的全部意義在於讓它執行而不會打擾任何人或需要關注。但如果你真的想這樣做,可以按照以下步驟操作
或者
我們將在稍後討論訊號時詳細瞭解其具體作用。
我們之前提到過,POSIX 1003.1 標準已經標準化了許多作業控制工具,這些工具包含在 jsh shell 及其後續版本中用於作業控制。我們已經瞭解了一些這些工具;在本節中,我們將介紹完整的列表。
標準作業控制工具列表如下:
- bg
- 將作業移至後臺。
- fg
- 將作業移至前臺。
- jobs
- 列出活動作業。
- kill
- 終止作業或向程序傳送訊號。
- CTRL+C
- 終止作業(與使用 SIGTERM 訊號的“kill”相同)。
- CRTL+Z
- 掛起前臺作業。
- wait
- 等待後臺作業終止。
所有這些命令都可以接受作業規範作為引數。作業規範以百分號開頭,可以是以下任何一項:
- %n
- 作業 ID(n 為數字)。
- %s
- 命令列以字串 s 開頭的作業。
- %?s
- 命令列包含字串 s 的作業。
- %%
- 當前作業(即您使用作業控制管理的最新作業)。
- %+
- 當前作業(即您使用作業控制管理的最新作業)。
- %-
- 上一個作業。
我們已經瞭解了“bg”、“fg”和 CTRL+Z,我們將在後面的部分中介紹“kill”。剩下的就是“jobs”和“wait”。讓我們從最簡單的開始
“Wait”是所謂的同步機制:它導致呼叫程序暫停,直到所有後臺作業終止。或者,如果您包含一個或多個作業規範,則直到您列出的作業終止。如果您啟動了多個作業(僅僅是為了利用系統的並行處理能力),並且在所有作業完成之前無法安全地繼續,則可以使用“wait”。
“wait”命令在相當高階的指令碼中使用。換句話說,你可能不會經常使用它。不過,這裡有一個你可能會經常使用的命令:
- -l 除了正常輸出外,還列出程序 ID
- -n 將輸出限制為自上次狀態報告以來狀態已更改的作業的資訊
- -p 僅列出作業的程序組組長的程序 ID
- -r 將輸出限制為正在執行的作業的資料
- -s 將輸出限制為已停止的作業的資料
- 作業規範如上所述
jobs 命令報告有關活動作業的資訊和狀態(不要將活動與執行混淆!)。但必須記住,此命令報告的是作業而不是程序。由於作業對 shell 是本地的,因此“jobs”命令無法跨 shell 檢視。“jobs”命令是您可以對其應用作業控制的作業的主要資訊來源;首先,如果您不記得作業 ID,則可以使用此命令檢索作業 ID。例如,請考慮以下情況:
$ CNT0=0
$ while [ $CNT0 -lt 200000 ]; do echo $CNT0 >> outtemp0.txt; CNT0=`expr $CNT0 + 1`; done&
[1] 26859
$ CNT1=0
$ while [ $CNT1 -lt 200000 ]; do echo $CNT1 >> outtemp1.txt; CNT1=`expr $CNT1 + 1`; done&
[2] 31331
$ jobs
[1]- Running while [ $CNT0 -lt 200000 ]; do
echo $CNT0 >> outtemp0.txt; CNT0=`expr $CNT0 + 1`;
done &
[2]+ Running while [ $CNT1 -lt 200000 ]; do
echo $CNT1 >> outtemp1.txt; CNT1=`expr $CNT1 + 1`;
done &
說到狀態(由“jobs”命令報告),現在是時候討論一下我們擁有的不同狀態了。作業可以處於多種狀態,有時甚至可以同時處於多種狀態。“jobs”命令在作業 ID 和順序之後直接報告狀態。我們識別以下狀態:
- 執行
- 作業正在執行其應執行的操作。除非您真的想親自關注程式(例如,停止程式或瞭解檔案下載的進度),否則您可能不需要中斷它。您通常會發現,前臺任何不等待您關注的內容都處於此狀態,除非它已被置於睡眠狀態。
- 睡眠
- 當程式需要檢索尚不可用的輸入時,它們無需繼續使用 CPU 資源。因此,它們將進入睡眠模式,直到另一批輸入到達。您將看到更多睡眠程序,因為它們不太可能在確切的時間點處理資料。
- 停止
- 停止狀態表示程式已被作業系統停止。這通常發生在使用者掛起前臺作業(例如,按下 CTRL-Z)或收到 SIGSTOP 時。此時,作業無法主動消耗 CPU 資源,並且除了仍載入在記憶體中之外,不會影響系統的其餘部分。一旦收到 SIGCONT 訊號或以其他方式從 shell 恢復,它將從中斷的地方繼續。睡眠和停止的區別在於,“睡眠”是一種等待計劃事件發生的形式,而“停止”可以由使用者發起並且是不確定的。
- 殭屍
- 如果父程式在子程式能夠將其返回值提供給父程式之前終止,則會出現殭屍程序。這些程序將由init程序清理,但有時需要重新引導才能將其刪除。
在上一節中,我們討論了 Unix shell 中可用於作業控制的標準工具。但是,您也可能會遇到許多非標準工具。即使本書的重點是 Bourne Shell 指令碼(尤其是作為 Unix shell 指令碼的通用語言),但這些工具非常普遍,如果我們至少不提及它們,那將是不負責任的。
除了前面討論的工具之外,還有兩個非常常見的 shell 命令:“stop”和“suspend”。
“stop”命令是許多與 System V 相容的 Unix 系統的 shell 中出現的命令。它用於掛起後臺程序——換句話說,它等效於後臺程序的“CTRL+Z”。它通常接受作業 ID,就像大多數這些命令一樣。在沒有“stop”命令的系統上,您應該能夠透過使用“kill”命令向後臺程序傳送 SIGSTOP 訊號來停止後臺程序。
suspend [-f]
您可能會遇到的另一個命令是“suspend”命令。“suspend”命令有點棘手,因為它並不總是對所有系統和所有 shell 都有相同的含義。目前作者知道有兩種變體,上面都顯示了。第一個明顯的變體接受作業 ID 引數並掛起指定的作業;實際上,它與“CTRL+Z”相同。
“suspend”的第二個變體根本不接受作業 ID,這是因為它不會掛起任何隨機作業。相反,它會掛起發出該命令的 shell 的執行。在此變體中,-f 引數指示即使 shell 是登入 shell 也應將其掛起。要恢復 shell 執行,請使用“kill”命令向其傳送 SIGCONT 訊號。
我們將討論的最後一個工具是程序快照實用程式“ps”。此實用程式根本不是 shell 工具,但它以某種變體存在於幾乎每個系統上,並且您會經常想要使用它。可能比“jobs”工具更頻繁。
“ps”實用程式旨在報告系統中正在執行的程序。程序,而不是作業——這意味著它可以跨 shell 例項檢視。以下是一個“ps”實用程式的示例:
$ ps x
PID TTY STAT TIME COMMAND 32094 tty5 R 3:37:21 /bin/sh 37759 tty5 S 0:00:00 /bin/ps
典型的程序輸出包括程序 ID、程序連線到的(或在其上執行的)終端的 ID、程序已使用的 CPU 時間以及用於啟動程序的命令。您可能還會獲得程序狀態。程序狀態由字母程式碼指示,但總的來說,報告的狀態與作業報告相同:R執行、S睡眠、sT停止和Z殭屍。但是,不同的“ps”實現可能使用不同的或更多的程式碼。
編寫有關“ps”的主要問題在於它並非完全標準化,因此可以使用不同的命令列選項集。您需要檢視系統上的文件以獲取具體詳細資訊。不過,某些選項非常常見,因此我們將在此處列出:
- -a
- 列出除組領導者程序之外的所有程序。
- -d
- 列出除會話領導者之外的所有程序。
- -e
- 列出所有程序,而不考慮使用者 ID 和其他訪問限制。
- -f
- 生成完整列表作為輸出(即所有報告選項)。
- -g 列表
- 將輸出限制為組領導者程序 ID 在列表中提到的程序。
- -l
- 生成長列表。
- -p 列表
- 將輸出限制為程序 ID 在列表中提到的程序。
- -s 列表
- 將輸出限制為會話領導者程序 ID 在列表中提到的程序。
- -t 列表
- 將輸出限制為在列表中提到的終端上執行的程序。
- -u 列表
- 將輸出限制為由列表中提到的使用者帳戶擁有的程序。
“ps”工具可用於監控跨 shell 例項的作業,並發現用於訊號傳輸的程序 ID。