轉自:http://blog.csdn.net/taiyang1987912/article/details/39529291
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
一、簡介
Linux是一種用戶控制的多作業操作系統,系統允許多個系統用戶同時提交作業,而一個系統用戶又可能用多個shell登錄,每個系統用戶可以用一個shell提交多個作業。了解Bash Shell在多作業管理和進程處理方面的命名和機制有助於理解多用戶、多作業的系統。
二、詳解
1、子Shell
(1)父子Shell是相對的,它描述了兩個Shell進程的fork關系,父Shell指在控制終端或xterm窗口給出提示符的進程,子Shell是由父Shell創建的進程。父Shell創建子Shell調用的是fork函數。
Shell命令可以分為內建命令(Shell本身執行的命令)和外部命令(fork創建出來的子shell執行的命名),內建命令不創建子Shell而外部命令創建子Shell。
(2) 內建命令是包含在Shell工具包中的命令,其中保留字對Shell有特殊含義,保留字本身不是一個命令而是命令結構的一部分。
冒號是Shell中一個特殊的符號,首先冒號可以表示永真(相當於TRUE關鍵字)如while :;do...done(while循環的條件始終為真);其次冒號可以清空一個文件,:>log將冒號重定向到文件,log文件內容被清空,所 以:>命名是常用的清空文件的命令;接着冒號最重要的用法是:不做任何事,只做參數展開。
(3)圓括號結構,能強制將其中的命令運行在子shell中,bash3后定義了內部變量BASH_SUBSHELL記錄子shell的層次。
- #圓括號結構用法
- #!/bin/bash
- echo "Father Shell is: $BASH_SUBSHELL" #打印父shell的層次,為0
- outervar=OUTER #父shell的變量outervar
- ( #利用圓括號結構創建子shell
- echo "SubShell is: $BASH_SUBSHELL" #子shell的層次為1
- (
- echo "GrandSubShell is: $BASH_SUBSHELL" #孫shell的層次為2
- )
- innervar=INNER #子shell的變量
- echo "innervar=$innervar"
- echo "outervar=$outervar" #outervar繼承了符shell所賦給它的值
- ) #回到父shell
- echo "Father Shell is: $BASH_SUBSHELL"
- if [ -z "$innervar" ] #子shell中定義變量為空,則說明
- then
- echo "The \$innervar is not defined in main body."
- else
- echo "The \$innervar is defined in main body."
- fi
innervar為空值,說明子shell中變量的作用域不能在父Shell中生效。在子shell中將變量export成環境變量,子shell對變量的更改仍然對父shell不可見。
子shell是允許嵌套調用的,可以在函數或圓括號結構內再次調用圓括號結構創建子shell。
子shell只能繼承父shell的一些屬性,而子shell不可能反過來改變父shell的屬性。子shell能夠從父shell繼承得來的屬性有:當 前的工作目錄、環境變量、標准輸入輸出和錯誤輸出、所有已打開的文件描述符、忽略的信號。子shell不能從父shell繼承得來的屬性是:除了環境變量 和.bashrc文件中定義變量之外的shell變量、未被忽略的信號處理。
利用子shell測試變量是否已經定義的例子:
- #!/bin/bash
- if (set -u; : $var) #冒號與$間有空格
- then
- echo "Variable is set."
- fi
其中set -u命令用於設置shell選項,u是nounset表示當使用未定義的變量時,輸出錯誤信息並強制退出。: $var中冒號是不做任何事只是參數展開,若沒有冒號則$var被解釋成shell命令,shell試圖去執行var變量的值。加上冒號,shell試圖 將var變量進行參數展開但不會試圖去執行var變量的值。
子shell還可以接收到父shell從管道傳送過來的數據,例:cat /etc/passwd | (grep 'root'),使用管道符向子shell發送數據,符shell將cat的結果通過管道發送給子shell,子shell執行grep命令。
shell應用將一個計算量較大的任務分成若干個小任務並行執行。
- #子shell用於並行計算的用法
- #!/bin/bash
- #用圓括號結構創建三個子shell同時執行
- (grep -r "root" /etc/* | sort > part1) & #與root關鍵字匹配的行,排序后輸出到某文件
- (grep -r "root" /usr/local/* | sort > part2) &
- (grep -r "root" /lib/* | sort > part3) &
- wait #等待后台執行的作業全部完成
- cat part1 part2 part3 | sort > parttotal
- echo "Run time of this script is:$SECONDS" #輸出該腳本執行時間
grep -r遞歸搜索,搜索時的計算量比較大,對每個目錄創建一個子shell進行並行處理,然后合並。每個圓括號之外有一個&符號,表示此命令放在后台 執行,繼續執行下一條命令;若無&符號則需要一條命令執行完畢后再執行下一條命令,就沒真正實現並行計算。wait是一個內建命令,用於等待后台 執行的作業全部完成后再執行下面的命令;若沒有wait,腳本將三個子shell放在后台執行后直接執行合並臨時文件的命令,三個子shell可能並未執 行完畢,此時臨時文件中的結果不完整,合並后也將產生不完整的結果。
2、Shell的限制模式
處於限制模式下的shell運行一個腳本或腳本片段,將會禁用一些命令或操作。shell的限制模式是Linux系統基於安全方面的考慮,目的為了限制腳本用戶的權限,並盡可能地減小腳本所帶來的危害。
Shell的限制模式限制的操作有:用cd命令更改當前工作目錄、更改重要的環境變量的值($PATH、$SHELL、$BASH_ENV、$ENV 和$SHELLOPTS)、輸出重定向符號(>、>>、>|、>&、<>和&>)、調 用含有一個或多個斜杠的命令名稱、使用內建命令exec、使用set+r等命令關閉限制模式。
- #正常模式和限制模式的區別
- #!/bin/bash
- echo "Changing current work directory"
- cd /etc #正常模式下改變當前工作目錄
- echo "Now in $PWD"
- set -r #shell選項使代碼運行在限制模式下(r是restricted)
- echo "------IN RESTRICTED MODE---------" #開始運行在限制模式下
- echo "Trying to change directory"
- cd /usr/local #cd命令出錯,被限制了
- echo "\$SHELLOPTS=$SHELLOPTS" #可以讀取$SHELLOPTS變量的值
- echo "Now in `pwd`" #還是/etc為當前目錄
- echo
- echo "Trying to change \$SHELL"
- SHELL="/bin/sh" #$SHELL變量在限制模式下只讀
- echo "\$SHELL=$SHELL"
- echo
- echo "Trying to redirect output to a file"
- who > outputnull #輸出重定向失敗,被限制了
- ls -l outputnull #outputnull沒有被創建
set -r開啟shell的restricted選項進入限制模式,還有一種以限制模式運行腳本的方式,就是#!/bin/bash -r,-r表示在限制模式下運行該腳本。
3、進程處理
(1)進程角度看shell執行
內建命令是由shell本身執行的命令,而外部命令則需要創建新的進程來執行。從進程角度歸納shell執行內建命令和外部命令的過程。
當shell命令不是內建命令時,linux利用fork對一個子進程執行該命令,父進程處於等待狀態。若該命令或腳本中包含編譯過的可執行文件,則內核 將新程序裝載到內存,並覆蓋子進程,執行結束退出子進程,父進程被重新激活開始讀取shell的后一條命令。
fork是系統調用,fork創建的子進程是父進程的副本,兩個進程具有同樣的環境、打開的文件、用戶標志符、當前工作目錄和信號等。
(2)進程和作業
作業是用戶層面的概念,而進程是操作系統層面的概念。其區別:一個正在執行的進程稱為作業,一個作業可以包含多個進程,用戶提交作業到操作系統,作業的完成可能依賴於啟動多個進程。
進程的三種基本狀態:
作業號標識的是在該shell下運行的所有進程,而進程號就標識整個系統下正在運行的所有進程。
其中[1]是作業號,7574是進程號。
(3)作業控制
作業是針對shell而言的,有前台運行和后台運行。內建命令fg可將后台運行的作業放到前台,而&符號使得作業在后台運行。
fg可以指定作業的方法(Ctrl+Z組合鍵可將正在運行的作業阻塞):
bg命令可將阻塞狀態的作業轉入后台運行。jobs查看作業列表。disown用於從shell的作業表中刪除作業。wait命令用與等待后台作業完成。
(4)信號
信號是在軟件層次上對中斷機制的一種模擬,原理上一個進程收到一個信號與處理器收到一個中斷請求是一樣的。信號事件的來源:硬件來源(比如按下鍵盤或其他 硬件故障)、軟件來源(比如系統函數kill、raise、alarm、setitimer和sigqueue函數)。信號是進程間通信機制中唯一的異步通信機制。
shell向進程發送信號大多通過Ctrl鍵加上一些功能鍵來實現。
除了利用組合鍵發送信號外,內建命令kill可用於向進程發送TERM(即terminal)信號,功能和INT信號類似用於停止進程。kill可以通過進程號、作業號(kill %n)或進程命令名想任何作業發送信號。kill
記錄了運行該腳本的進程號),其中大於128的退出碼表示腳本是被系統強行結束的。kill -l可看出,kill命令一共能發出64種信號。
(5)trap命令
trap是Linux的內建命令,用於捕捉信號,trap命令可以指定收到某種信號時所執行的命令。trap命令的格式如下:trap command sig1 sig2 ... sigN,當接收到sinN中任意一個信號時,執行command命令,command命令完成后繼續接收到信號前的操作,直到腳本結束。
- #!/bin/bash
- trap "echo 'You hit Ctrl+c!'" INT
- while :; do
- let count=count+1
- echo "This is the $count sleep"
- sleep 5
- done
利用trap命令捕捉INT信號(即與Ctrl+c綁定的中斷信號)。trap還可以忽略某些信號,將command用空字符串代替即可,如trap "" TERM INT,忽略kill %n和Ctrl+c發送的信號(kill發送的是TERM信號)。LInux更強勁的殺死進程的命令:kill -9 進程號(或kill -9 %n作業號)等價與kill -KILL 進程號。
(6)子shell的信號
子shell能繼承父shell所忽略的信號,但是不能繼承父shell未忽略的信號。
- #!/bin/bash
- trap "" QUIT #忽略kill -3信號,並且子shell能繼承父shell所忽略的信號
- trap "echo 'You want to kill me'" TERM #父shell處理的信號,子shell不能繼承
- ( #子shell,子進程號比父進程號大1
- while :; do
- let count=count+1
- echo "This is the $count sleep"
- sleep 5
- done
- )
父shell忽略QUIT信號但不忽略TERM信號,9987為父 shell進程號9988為子shell進程號,kill -3 9987向父shell發送3信號和kill -3 9988向子shell發送3信號,均未退出,可以看出子shell對QUIT的忽略是從父shell繼承而來的。
kill 9987向父shell發送TERM信號,父shell仍存活(因處理了TERM信號),kill 9988向子shell發送TERM信號,子shell退出,隨后父shell執行完畢結束。TERM信號能殺掉子shell,說明子shell不能繼承 父shel未忽略的信號。
最后出現父shell響應TERM信號的輸出,是因為子shell執行fork一個子進程后父shell處於等待狀態,只有子shell退出后父shell才會被激活執行輸出。
三、總結
(1)理解shell在多作業管理和進程處理方面的命名和機制,有助於控制和管理Linux中的進程和作業。
(2)父shell和子shell的繼承特性因充分了解,以及shell的限制模式。
(3)有很多的細節問題還需不斷的總結歸納。
(3)在shell編程中不斷強化其中的概念,進一步消化