shell淺談之九子shell與進程處理


目錄(?)[+]

一、簡介

      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的層次。

 

  1. #圓括號結構用法  
  2. #!/bin/bash  
  3.   
  4. echo "Father Shell is: $BASH_SUBSHELL"      #打印父shell的層次,為0  
  5. outervar=OUTER                              #父shell的變量outervar  
  6.   
  7. (                                           #利用圓括號結構創建子shell  
  8.     echo "SubShell is: $BASH_SUBSHELL"        #子shell的層次為1  
  9.     (  
  10.         echo "GrandSubShell is: $BASH_SUBSHELL" #孫shell的層次為2  
  11.     )   
  12.     innervar=INNER                            #子shell的變量  
  13.     echo "innervar=$innervar"  
  14.     echo "outervar=$outervar"                 #outervar繼承了符shell所賦給它的值  
  15. )                                           #回到父shell  
  16.   
  17. echo "Father Shell is: $BASH_SUBSHELL"     
  18.   
  19. if [ -z "$innervar" ]                      #子shell中定義變量為空,則說明  
  20. then  
  21.   echo "The \$innervar is not defined in main body."  
  22. else  
  23.    echo "The \$innervar is defined in main body."  
  24. fi  


      innervar為空值,說明子shell中變量的作用域不能在父Shell中生效。在子shell中將變量export成環境變量,子shell對變量的更改仍然對父shell不可見。

      子shell是允許嵌套調用的,可以在函數或圓括號結構內再次調用圓括號結構創建子shell。

      子shell只能繼承父shell的一些屬性,而子shell不可能反過來改變父shell的屬性。子shell能夠從父shell繼承得來的屬性有:當 前的工作目錄、環境變量、標准輸入輸出和錯誤輸出、所有已打開的文件描述符、忽略的信號。子shell不能從父shell繼承得來的屬性是:除了環境變量 和.bashrc文件中定義變量之外的shell變量、未被忽略的信號處理。

       利用子shell測試變量是否已經定義的例子:

  1. #!/bin/bash  
  2.   
  3. if (set -u; : $var)  #冒號與$間有空格  
  4. then   
  5.   echo "Variable is set."  
  6. 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應用將一個計算量較大的任務分成若干個小任務並行執行。

 

  1. #子shell用於並行計算的用法  
  2. #!/bin/bash  
  3.   
  4. #用圓括號結構創建三個子shell同時執行  
  5. (grep -r "root" /etc/* | sort > part1)       &       #與root關鍵字匹配的行,排序后輸出到某文件  
  6. (grep -r "root" /usr/local/* | sort > part2) &  
  7. (grep -r "root" /lib/* | sort > part3)       &  
  8.   
  9. wait                                                 #等待后台執行的作業全部完成  
  10. cat part1 part2 part3 | sort > parttotal  
  11. 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等命令關閉限制模式。

 

  1. #正常模式和限制模式的區別  
  2. #!/bin/bash  
  3.   
  4. echo "Changing current work directory"   
  5. cd /etc                                    #正常模式下改變當前工作目錄  
  6. echo "Now in $PWD"  
  7.   
  8. set -r                                     #shell選項使代碼運行在限制模式下(r是restricted)  
  9. echo "------IN RESTRICTED MODE---------"   #開始運行在限制模式下  
  10. echo "Trying to change directory"  
  11. cd /usr/local                              #cd命令出錯,被限制了  
  12. echo "\$SHELLOPTS=$SHELLOPTS"              #可以讀取$SHELLOPTS變量的值  
  13. echo "Now in `pwd`"                        #還是/etc為當前目錄  
  14. echo  
  15. echo "Trying to change \$SHELL"  
  16. SHELL="/bin/sh"                            #$SHELL變量在限制模式下只讀  
  17. echo "\$SHELL=$SHELL"                
  18. echo  
  19. echo "Trying to redirect output to a file"  
  20. who > outputnull                          #輸出重定向失敗,被限制了  
  21. 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命令完成后繼續接收到信號前的操作,直到腳本結束。

 

  1. #!/bin/bash  
  2.   
  3. trap "echo 'You hit Ctrl+c!'" INT  
  4.   
  5. while :; do  
  6.   let count=count+1  
  7.   echo "This is the $count sleep"  
  8.   sleep 5  
  9. 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未忽略的信號。

  1. #!/bin/bash  
  2.   
  3. trap "" QUIT            #忽略kill -3信號,並且子shell能繼承父shell所忽略的信號  
  4. trap "echo 'You want to kill me'" TERM    #父shell處理的信號,子shell不能繼承  
  5. (                       #子shell,子進程號比父進程號大1  
  6.     while :; do  
  7.         let count=count+1  
  8.         echo "This is the $count sleep"  
  9.         sleep 5  
  10.     done  
  11. )  

       父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編程中不斷強化其中的概念,進一步消化


免責聲明!

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



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