一、 腳本執行過程中的控制
之前的內容中,運行編寫好的腳本時都是在命令行上直接確定運行的,並且運行的腳本是實時的,這並不是腳本唯一的運行方式,下面的內容是腳本的其他運行方式。例如在Linux系統中如何控制腳本的執行過程,想在腳本運行過程中對運行中的腳本執行流程進行控制,或者控制腳本的運行時機等等,這些都是通過信號來實現的。
15.1 Linux信號
在Linux系統中,Linux是通過信號和運行在系統上的進程實現通信的。信號就是一個很短的信息,可以發送給一個或多個進程。在前面的內容中講了如何通過信號啓動、停止或終止一個進程的運行。當然可以通過這些信號來控制Bash腳本的運行,如終止一個進程或將運行的腳本掛起(暫停執行)。
15.1.1 常用的信號
在Linux中,系統和Shell進程中進行通信的信號有幾十個,可以通過下面的命令在控制檯顯示這些信號的值和對應的信號名稱。
控制檯顯示:
但是常用到的系統信號如下表中列出的。
Linux系統中常用到的信號以及對應的信號名稱和描述信息:
編號 | 信號名稱 | 描述 | 默認狀態 |
1 | SIGHUP | 終止終端或進程 | 進程終止 |
2 | SIGINT | 來自鍵盤的中斷,【Ctrl+C】 | 進程終止 |
3 | SIGQUIT | 來自鍵盤的退出,【Ctrl+\】 | 進程流產 |
9 | SIGKILL | 強迫進程終止,不可以屏蔽 | 進程終止 |
15 | SIGTERM | 使進程終止 | 進程終止 |
17 | SIGCHLD | 使子進程停止或終止 | 忽略 |
18 | SIGCONT | 進程繼續運行,與SIGSTOP結合使用 | 忽略 |
19 | SIGSTOP | 進程暫停執行 | 進程暫停 |
20 | SIGTSTP | 來自鍵盤的進程掛起,【Ctrl+Z】 | 進程暫停 |
在Linux系統中使用信號的目的主要有兩個:
1. 讓運行的進程感知特定的事件並作出反應。
2. 在腳本進程執行代碼過程中處理相關信號的代碼塊。
在Linux系統中,信號的傳遞過程中主要分爲兩個階段,第一個階段是信號的發送階段,第二個階段是進程對信號的接收階段。在第一個階段中系統修改目標進程的狀態描述符表示一個新的信號已經發送。第二個階段系統強制進程做出反應並改變進程的運行狀態,或者執行一段特定的代碼,這段代碼是腳本中捕獲信號後執行的特定代碼。
另外需要注意的是信號只被當前正在運行的進程接收,並且進程可以屏蔽特定的信號,相同的信號除第一個被進程接收後其他的會被屏蔽。下面會通過例子詳細說明。
通常情況下,運行一個腳本會啓動一個新的Shell進程,在這個Shell進程中運行腳本,當腳本執行結束後對應的Shell進程也會終止。特殊情況下,如果使用source或點命令啓動腳本不會啓動一個新的Shell進程,而是在當前的Shell進程中運行腳本,這就會使結果有些不同,下面會通過例子說明。當Shell進程接收到掛起或終止信號時,Shell進程會終止運行,但Shell進程終止前會將接收到的信號發送到本Shell進程中運行的所有的子進程,包括運行的腳本進程或某個命令進程。
15.1.2 SIGINT和SIGTSTP
掛起一個進程和終止一個進程是不同的,當一個進程被掛起時,進程的運行狀態會保存到內存,只是系統將不會在分配CPU的時間片給掛起的進程,當通過命令啓動被掛起的進程時,進程會從斷點開始繼續執行。而終止一個進程是將進程的運行狀態從內存中清除並且不能在繼續執行。
在Linux系統中,使用組合鍵可以生成兩個基本的Linux信號,它們是【Ctrl+C】終止進程信號和【Ctrl+Z】掛起一個進程。使用這兩個組合鍵可以將運行的進程終止或掛起,下面通過簡單的例子說明。
使用【Ctrl+C】終止一個命令行上的命令進程,sleep命令可以程序運行過程中暫停運行指定的秒數。使用這個命令會更加直觀,其他命令很快就會執行結束。
當sleep命令執行開始,會等待60秒後纔會顯示命令行提示符$,當採用組合鍵【Ctrl+C】可以終止這個進程,注意:終止的是當前運行的進程,可以是一個或多個進程。
通過ps命令查看sleep命令執行時和終止sleep命令進程的進程信息。
可以看到sleep命令沒有執行結束時的進程號是5099,執行終止命令後進程被清除。
採用【Ctrl+Z】組合鍵可以掛起一個進程,下面還用sleep命令演示。
通過組合鍵掛起了命令行進程,通過ps命令查看進程狀態。
可以看到sleep命令進程的STAT字段由S+變爲了T,從運行狀態轉變爲掛起狀態。掛起的進程狀態保存到了內存可以通過命令jobs查看當前掛起的進程。
對於掛起的進程,可以使用fg命令恢復進程的運行,如下所示。
當通過exit命令退出Shell進程時,如果Shell進程中存在被掛起的進程,Shell進程會給出提示信息,如果再次執行exit命令Shell進程將會終止並退出,暫停運行的進程也會被終止。
如果想終止一個暫停的進程,可以使用kill命令向暫停的進程發送SIGKILL信號將進程終止。
15.2 在腳本中捕捉信號
默認情況下,腳本會忽略這些Linux系統信號,發送的信號會被Shell進程接收並處理,例如,當終止或掛起一個進程(或多個進程)時,接收到信號的Shell進程會被終止或者被掛起,運行在Shell進程中的腳本也同時被終止或被掛起。但是,也可以讓腳本不忽略這些信號,在信號出現時攔截並執行相應的代碼塊,完成特定的任務或者釋放資源。需要注意的是如果信號在腳本中被攔截,Shell進程將不再接收並處理這些信號,而是在攔截信號的腳本中被處理。在腳本中捕捉信號是非常有意義,當腳本在運行過程中遇到不可預知的異常情況下,可以採用這種方式做一些系統的善後工作,如斷電等異常情況下造成的進程終止。在腳本中可以使用trap命令來攔截Linux系統發出的信號,命令格式如下:
在腳本執行過程中,如果出現trap命令列出的信號類型列表中的其中一個信號,這個信號將被腳本攔截,並且暫停腳本的繼續執行,進而會執行trap命令指定的命令。當trap命令指定的命令被執行後,腳本會繼續接着暫停執行的斷點繼續向下執行,直到腳本執行結束或再次出現指定的信號,攔截、執行指定的命令、繼續執行腳本。
15.2.1 trap命令
在腳本執行過程中可以通過trap命令攔截指定的系統信號,並且執行指定的命令。下面通過幾個簡單的例子說明在腳本中如何通過trap命令攔截信號和攔截信號後腳本的執行流程。第一個例子是當腳本運行過程中不允許通過鍵盤組合鍵終止腳本的執行。
例:trapTest01.sh
控制檯顯示:
^C --Process can not be terminated! ^C --Process can not be terminated! |
可以看到,當從控制檯通過【Ctrl+C】發出終止進程的信號時,被腳本中的trap命令攔截,並在控制檯輸出信息,當指定的命令執行結束後,腳本繼續向下執行並沒有被終止。trap命令指定的命令可以是一個腳本,在腳本中執行相關的代碼。修改上面的例子,將輸出的信息放置在腳本中。
例:trapTest02.sh
例:trapTest02_1.sh
控制檯顯示:
^C --Process can not be terminated! ^C --Process can not be terminated! |
第二種方式可以在腳本中執行相關的代碼塊,如釋放資源等等。需要注意的是每一次攔截到指定的信號後都會執行一次指定的命令或腳本。
15.2.2 trap命令捕捉Shell進程退出
啓動一個腳本可以有兩種方式,第一種是在新的Shell進程中運行腳本,另一種方式是在當前Shell進程中運行腳本。啓動腳本的方式不同將會產生不同的結果,如下例子。
例:trapTest03.sh
# 當腳本運行結束,對應的Shell進程也同樣會結束,當Shell進程終止時會被攔截 |
第一種啓動腳本的方式,啓動一個新的Shell進程運行腳本,在這種情況下,腳本執行結束時會被攔截,因爲腳本執行結束後運行腳本的Shell進程也同時會終止,攔截的是Shell進程的終止。
控制檯顯示:
第二種啓動腳本的方式是,在當前Shell進程中運行腳本,這種情況下,當腳本運行結束後,運行腳本的Shell進程並不會終止,所以不會在控制檯輸出相關的信息。注意:攔截的是Shell進程的終止信號,不是腳本運行結束的信號。
控制檯顯示:
通過source命令或點命令啓動腳本時,不會啓動一個新的Shell進程,而是在當前的Shell進程中運行腳本,要注意。同樣,如果在腳本運行過程中終止腳本的運行,也同樣會被攔截。下面通過組合鍵【Ctrl+C】終止腳本的運行,同樣分爲兩種情況。
控制檯顯示:
|
上面通過兩種方式運行腳本,第一種方式,當從鍵盤終止進程時會被攔截,因爲腳本是在新的Shell進程中運行,而第二種方式運行腳本時是在當前的Shell進程中,所以當腳本運行結束後當前Shell進程並沒有終止,所以沒有Shell進程終止信號。
15.2.3 移除捕捉信號
在腳本中可以使用trap命令加單破折號來移除指定要捕捉的信號,一旦捕捉信號被移除,相應的信號將不會再被捕捉。命令格式如下。
trap - 指定捕捉的信號 |
通過trap命令可以移除指定要捕捉的信號,當捕捉信號被移除後,腳本將不會在攔截這樣的信號,信號將會被Shell進程接收並處理,下面通過例子說明。
例:trapTest04.sh
trap "echo '--OK! end of execution!'" EXIT
|
控制檯顯示:
|
攔截信號沒有移除時,終止信號被攔截控制檯輸出相關信息。當攔截信號移除後,終止信號會終止腳本進程但不會被腳本攔截。
例:trapTest05.sh
trap "echo ' --Process can not be terminated!'" SIGINT
|
控制檯顯示:
一旦捕捉信號被移除後,腳本將會忽略該信號。但是在移除捕捉信號前,腳本還是會攔截指定的信號。
15.3 系統後臺運行模式
之前運行腳本時都是在命令行直接運行,這種運行腳本的方式是在系統前臺的運行模式。這種運行腳本的方式在某些情況下不是最方便的方法,如有的腳本運行時間很長,在這段時間內,只能等待腳本運行結束而不能進行其他的工作。或者某些服務在系統生命週期內都在運行,這樣的服務腳本如果運行在系統前臺,用戶將無法工作。
通過ps命令可以將系統運行的進程顯示到控制檯,但不是所有的進程都運行在終端上的,如下所示。
不是運行在終端內的進程我們稱爲後臺進程,這些進程運行在後臺模式下,通常都是一些服務類進程,當然,用戶也可以將自己的腳本在後臺運行,這種運行方式不會佔用終端資源。對於用戶來說,後臺運行的進程是透明的,用戶可以不用關心後臺運行的進程。
15.3.1 在系統後臺運行腳本
在Linux系統中,以後臺模式運行自己編寫的腳本同樣要在終端命令行啓動腳本進程,與在前臺啓動腳本不同的是,需要在命令後面加一個&符號,&符號會將命令或腳本與Shell分離開,並將命令或腳本作爲獨立的進程在系統後臺運行,命令格式如下。
通過終端命令行比較前臺模式運行和後臺模式運行的區別,還以sleep命令演示,如下。
上面的例子中依次執行了三條sleep命令,三條命令都是在後臺執行,當執行了第一條sleep命令後,光標沒有停頓等待暫停的秒數,而是在控制檯顯示了一行信息,方括號中的是當前Shell進程中的正在運行的進程的作業號,第一個作業是1,第二個作業的作業號是2,以此類推,作業號後面的的數字是進程ID號(進程ID是唯一的)。命令實際上是在後臺運行,控制檯只顯示作業號和進程ID。第二條命令暫停2秒,顯示了兩行信息,第一行是作業號和進程ID。第二行顯示的是第一條命令在後臺執行結束後的信息,作業號、進程狀態是完成和執行的命令名稱。第三條命令和第二條命令顯示的內容相同,作業號、進程ID和第二條命令執行結束後的信息。
一旦在控制檯顯示了作業號和進程ID,新的命令行提示符會重新出現,這時用戶可以在命令行執行其他的命令或腳本,而之前執行的命令正在後臺模式下運行。前臺進程和後臺進程平行運行,如下例子。
上面第一條sleep命令在後臺運行,第二條命令是在前臺運行,兩條命令是平行運行的,當第三條命令執行時,後臺運行的sleep命令執行結束。
運行腳本也可以在後臺運行,方式與命令在後臺執行是相同的,如下例子。
例:process01.sh
控制檯顯示:
|
上面例子中,腳本後臺運行時任然會將標準輸出輸出到控制檯。通常情況下可以將輸出重定向到日誌文件。
15.3.2 運行多個後臺作業
在終端命令行可以同時啓動多個後臺作業。
例:process02_1.sh
|
例:process02_2.sh
|
例:process02_3.sh
|
例:process02_4.sh
|
控制檯顯示:
|
每啓動一個新的作業,Linux系統都會爲其分配一個作業號和一個唯一的進程ID號,通過ps命令可以查看到運行的進程。
通過ps命令可以查看到所有啓動的進程,包括後臺進程和前臺進程。即使是後臺運行的進程如果有輸出,默認情況下也同樣會輸出到控制檯。在終端中啓動後臺進程時,每個後臺進程都會綁定到啓動後臺進程的終端上,上面的例子中,所有的後臺進程都綁定到了pts/6終端上(啓動的第六個終端),如果pts/6終端退出,綁定在終端上的後臺進程也可能會退出(不同的Linux版本略有不同)。需要注意的是有的Linux版本的終端模擬器會提示有後臺進程在運行,有的終端不會提示,這就需要注意了,如果在當前終端啓動了後臺進程,在後臺進程沒有結束前,不能將終端退出運行。如果想將後臺進程和控制檯終端分離,需要使用其他的方式。
15.4 在非控制檯下運行腳本進程
在終端命令行上啓動腳本,讓腳本一直運行在後臺,即使終端退出運行,腳本進程也不會退出運行,也就是後臺進程不綁定特定的終端。這種運行腳本的方式使用nohup命令可以實現。命令格式如下。
先通過sleep命令做一個演示再進行說明,分爲兩種情況,執行命令的終端運行時和退出運行兩種情況。
控制檯顯示:
上面的命令啓動命令進程後使用ps命令查看進程,可以看到當終端沒有退出運行時,sleep命令進程是綁定在pts/16終端上的,當終端退出運行後通過ps命令可以看到sleep命令進程沒有綁定任何終端。就是說不管啓動進程的終端是否運行,命令進程都會在後臺獨立運行,直到進程結束。實現的原理是由nohup命令啓動進程時,nohup命令會運行另一個命令進程來阻斷所有發送給當前進程的SIGHUP信號,這樣在終端退出運行時進程也不會退出。終端退出運行時進程被阻止退出。
使用nohup命令啓動後臺進程和普通方式啓動後臺進程相同的是,Shell進程會爲啓動的進程分配作業號和進程ID,不同的是當使用nohup命令啓動的進程在啓動進程的終端退出運行時,進程會忽略終端發送過來的SIGHUP信號,不會終止進程,會繼續在後臺運行進程,直到進程運行結束。
在使用nohup命令啓動後臺進程時,在控制檯可以看到一行輸出“忽略輸入並把輸出追加到"nohup.out"”,這是因爲nohup命令會從終端解除與進程的關聯,進程會失去與標準輸出STDOUT和STDERR的鏈接,爲了保存進程運行過程中的輸出,nohup命令會自動將進程運行過程中的STDOUT和STDERR信息重新定向到當前目錄下的nohup.out文件中,nohup.out文件保存了所有輸出到控制檯的信息,相當於進程運行過程中的日誌文件,可以通過cat命令顯示文件內容。需要注意的是如果在當前目錄使用nohup命令啓動多個後臺運行的進程,多個進程的輸出都會輸出到nohup.out文件中。
下面的例子採用nohup命令在後臺啓動多個腳本進程並通過ps命令查看後臺進程的運行狀態。採用的腳本還是上面例子的腳本process02_*.sh。
在控制檯使用nohup命令啓動腳本:
$ nohup process02_1.sh & [1] 3797 $ nohup: 忽略輸入並把輸出追加到"nohup.out"
$ nohup process02_2.sh & [2] 3800 $ nohup: 忽略輸入並把輸出追加到"nohup.out"
$ nohup process02_3.sh & [3] 3804 $ nohup: 忽略輸入並把輸出追加到"nohup.out"
$ nohup process02_4.sh & [4] 3807 $ nohup: 忽略輸入並把輸出追加到"nohup.out"
$ |
通過ps命令查看後臺進程的運行狀態,分爲兩種情況,執行命令的終端運行時和退出運行兩種情況。
$ ps -ef UID PID PPID C STIME TTY TIME CMD … yarn 3659 3638 0 11:55 pts/1 00:00:00 bash yarn 3774 3638 0 12:05 pts/2 00:00:00 bash yarn 3797 3774 0 12:06 pts/2 00:00:00 /bin/bash ./process02_1.sh yarn 3798 3797 0 12:06 pts/2 00:00:00 sleep 50 yarn 3800 3774 0 12:06 pts/2 00:00:00 /bin/bash ./process02_2.sh yarn 3801 3800 0 12:06 pts/2 00:00:00 sleep 30 yarn 3804 3774 0 12:06 pts/2 00:00:00 /bin/bash ./process02_3.sh yarn 3805 3804 0 12:06 pts/2 00:00:00 sleep 30 yarn 3807 3774 1 12:06 pts/2 00:00:00 /bin/bash ./process02_4.sh yarn 3808 3807 0 12:06 pts/2 00:00:00 sleep 30 yarn 3809 3659 0 12:07 pts/1 00:00:00 ps -ef $ ps -ef UID PID PPID C STIME TTY TIME CMD … yarn 3659 3638 0 11:55 pts/1 00:00:00 bash yarn 3797 1 0 12:06 ? 00:00:00 /bin/bash ./process02_1.sh yarn 3798 3797 0 12:06 ? 00:00:00 sleep 50 yarn 3800 1 0 12:06 ? 00:00:00 /bin/bash ./process02_2.sh yarn 3801 3800 0 12:06 ? 00:00:00 sleep 30 yarn 3804 1 0 12:06 ? 00:00:00 /bin/bash ./process02_3.sh yarn 3805 3804 0 12:06 ? 00:00:00 sleep 30 yarn 3807 1 0 12:06 ? 00:00:00 /bin/bash ./process02_4.sh yarn 3808 3807 0 12:06 ? 00:00:00 sleep 30 yarn 3812 3659 0 12:07 pts/1 00:00:00 ps -ef $ |
終端退出運行後,後臺運行的進程就沒有綁定到啓動進程的終端了,並且進程繼續運行直到運行結束。
查看nohup.out文件的內容。四個腳本進程將所有的輸出都輸出到nohup.out文件。
$ cat nohup.out This is first process! This is second process! This is third process! This is fourth process! $ |
15.5 進程控制
對於運行中的進程我們可以採用組合鍵將其終止或者掛起,使用kill命令向掛起的進程發送SIGCONT信號可以使掛起的進程重新啓動。通常情況下,我們將啓動一個進程、掛起一個進程、終止和重新啓動進程這些操作稱做進程控制,通過進程控制我們可以對運行在Shell進程中的所有進程進行控制。
15.5.1 查看Shell進程中正在運行的進程
通過jobs命令可以查看當前Shell進程中掛起的進程,需要注意的是jobs命令只能查看當前Shell進程中的進程信息,如果在不同的Shell進程中啓動的進程,只能在啓動進程的Shell中可以查看到,如下所示。
注:啓動一個命令進程 $ sleep 50 注:讓進程掛起 ^Z [1]+ Stopped sleep 50 注:使用jobs命令查看當前Shell進程中正在運行的進程 $ jobs [1]+ Stopped sleep 50 注:通過bash命令重新啓動一個新的子Shell進程(對於當前Shell進程) $ bash 注:再通過jobs命令查看子Shell進程中正在運行的進程,沒有運行的進程 $ jobs 注:退出並終止當前子Shell進程並返回到父Shell進程 $ exit exit 注:再通過jobs命令查看當前Shell進程中運行的進程,可以看到掛起的進程 $ jobs [1]+ Stopped sleep 50 $ |
下面的例子採用的腳本的方式
例:process03.sh
#!/bin/bash # 控制檯顯示,變量$$保存當前腳本進程的進程號 echo "This is a process! Process number is $$" # 控制循環變量 count=0
while [ $count -le 5 ] do echo "count = $count" count=$[ $count + 1 ] sleep 30 done
echo "End of the test process!" |
注:啓動一個進程並通過鍵盤進進程掛起 $ process03.sh This is a process! Process number is 4708 count = 0 count = 1 ^Z [1]+ Stopped process03.sh 注:啓動一個後臺進程 $ process03.sh & [2] 4712 $ This is a process! Process number is 4712 count = 0 # 通過jobs命令可以看到兩個進程,一個暫停運行,一個正在運行 $ jobs [1]+ Stopped process03.sh [2]- Running process03.sh & $ |
通過jobs命令可以查看到當前Shell進程中正在運行或者暫停運行的進程,可以看到輸出的信息中一條信息帶有加號,一條信息帶有一個減號。帶有加號的進程是當前Shell進程中默認的進程,帶有減號的進程是當默認進程結束後將變爲默認進程。不管當前Shell進程中有多少個進程,只有一個進程帶有加號,一個進程帶有減號。默認進程是當使用進程控制命令時沒有指定進程,那麼操作的就是默認進程。
上面的例子中在使用jobs命令時沒有帶任何參數,下面表格中是jobs命令常用到的參數。
參數 | 描述 |
-l | 顯示進程的進程ID號 |
-n | 只列出狀態改變的進程 |
-p | 只列出進程的PID |
-r | 只列出運行中的進程 |
-s | 只列出已停止的進程 |
下面通過一個例子說明jobs命令的參數的用法。
注:啓動一個進程並將進程掛起 $ process03.sh This is a process! Process number is 4948 count = 0 ^Z [1]+ Stopped process03.sh 注:在後臺啓動一個進程 $ process03.sh & [2] 4953 $ This is a process! Process number is 4953 count = 0 注:啓動第三個進程並將進程掛起 $ process03.sh This is a process! Process number is 4956 count = 0 ^Z [3]+ Stopped process03.sh 注:使用jobs命令的參數-l,顯示進程調度ID號 $ jobs -l [1]- 4948 停止 process03.sh [2] 4953 Running process03.sh & [3]+ 4956 停止 process03.sh 注:參數-p只顯示進程的ID號 $ jobs -p 4948 4953 4956 注:參數-r只顯示運行的進程 $ jobs -r [2] Running process03.sh & 注:參數-s只顯示停止運行的進程 $ jobs -s [1]- Stopped process03.sh [3]+ Stopped process03.sh $ jobs -l [1]- 4948 停止 process03.sh [2] 4953 Running process03.sh & [3]+ 4956 停止 process03.sh 注:根據進程號終止進程,注意是默認進程 $ kill -9 4956 注:帶減號的進程成了默認進程 $ jobs -l [1]+ 4948 停止 process03.sh [2]- 4953 Running process03.sh & $ |
注意:參數可以連起來使用,如:jobs -ls。顯示停止的進程信息,包括進程ID。另外,Shell進程的默認進程是最後啓動的進程。當默認進程被終止後,帶減號的進程變成了默認進程。
15.5.2 重啓停止的作業
在Shell進程中停止的進程可以通過命令重新啓動,重啓進程的模式有兩種,既前臺運行模式和後臺運行模式。前臺模式重啓進程的命令格式如下。
fg 進程作業號 |
以前臺模式重啓停止運行的進程需要注意的是重啓後的進程會佔用當前啓動進程的終端,直到進程運行結束。下面通過例子說明。
$ process03.sh This is a process! Process number is 5451 count = 0 ^Z [1]+ Stopped process03.sh $ jobs [1]+ Stopped process03.sh $ fg 1 process03.sh count = 1 count = 2 count = 3 count = 4 count = 5 End of the test process! $ |
以前臺模式啓動的進程會一直佔用當前的終端,直到進程運行結束。
以後臺模式重啓進程,進程會在後臺執行,命令格式如下。
bg 進程作業號 |
下面通過例子說明。
$ process03.sh This is a process! Process number is 5482 count = 0 ^Z [1]+ Stopped process03.sh $ process03.sh This is a process! Process number is 5488 count = 0 ^Z [2]+ Stopped process03.sh $ jobs -l [1]- 5482 停止 process03.sh [2]+ 5488 停止 process03.sh $ bg 2 [2]+ process03.sh & count = 1 $ jobs -n [2]- Running process03.sh & $ bg 1 [1]+ process03.sh & count = 1 $ jobs -ln [1]- Running process03.sh & $ jobs -r [1]- Running process03.sh & [2]+ Running process03.sh & $ jobs -l [1]- 5482 Running process03.sh & [2]+ 5488 Running process03.sh & … |
通過bg命令加上進程作業號可以在後臺重啓停止的進程,重啓的進程信息後面加上了&符號,說明進程在後臺運行。需要說明的是如果bg命令後面不跟進程作業號,會默認重啓帶加號的進程,也就是當前Shell進程中默認的進程。通過jobs -n可以查看改變進程狀態的進程信息。
15.6 進程的謙讓度
在Linux系統中,通過ps命令可以看到有多個進程同時在系統中運行,有的在前臺運行,有的在後臺運行。如下表中的bash進程、ps-ef進程是前臺進程,字段TTY爲?號的進程是後臺進程。
$ ps -ef UID PID PPID C STIME TTY TIME CMD … yarn 2808 1 0 14:04 ? 00:00:04 gnome-terminal yarn 2809 2808 0 14:04 ? 00:00:00 gnome-pty-helper yarn 2810 2808 0 14:04 pts/0 00:00:00 bash root 2895 1 0 14:10 tty2 00:00:00 /sbin/mingetty /dev/tty2 root 3014 2042 0 14:24 ? 00:00:00 tpvmlpd2 yarn 3039 2810 3 14:26 pts/0 00:00:00 ps -ef |
對於在Linux中運行的所有進程,Linux內核會將CPU時間片分配給系統上的每一個進程,在一個特定的時間點只有一個進程佔用CPU,所以Linux內核會輪流將CPU的使用權分配給每一個進程。默認情況下,從Shell啓動的所有進程在Linux系統中具有相同的調度優先級。調度優先級是系統內核分配給進程的CPU使用權的大小。
在系統中,進程的調度優先級是使用整數值來表示的,取值範圍是從-20~+19。-20具有最高的調度優先級,+19具有最低的調度優先級。默認情況下,Bash Shell以調度優先級0來啓動所有的進程。調度優先級越高,獲得CPU的時間機會越高,調度優先級越低,獲得CPU的時間機會越低。要注意,整數值越大調度優先級越低。值越小調度優先級越高。
可以通過命令修改進程的調度優先級,對於運行時間較長的進程可以提高進程的調度優先級(取得CPU時間片的機會更多)或者降低一個進程的調度優先級(取得CPU時間片的機會減少)。
15.6.1 nice命令
在Linux系統中可以通過nice命令在啓動進程時指定進程的調度優先級,nice命令後面不跟任何參數將返回當前進程的調度優先級,在Shell進程中啓動的腳本進程默認調度優先級爲0,下面通過一個例子說明。
例:process04.sh
#!/bin/bash
echo "process begin" # 在控制檯顯示當前進程的調度優先級值 nice
for (( i = 0;i < 5;i++ )) do echo "i = $i" # 暫停30秒 sleep 30 done |
控制檯顯示:
$ process04.sh process begin 0 i = 0 … |
可以看到腳本運行過程中的調度優先級值爲默認的0。也可以通過ps命令查看進程的調度優先級值,如下。
$ ps -efl F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD … 0 S yarn 2810 2808 0 80 0 - 1739 - 14:04 pts/0 00:00:00 bash 4 S root 2895 1 0 80 0 - 502 ? 14:10 tty2 00:00:00 /sbin/mingetty /dev/tty2 0 S yarn 3290 2808 0 80 0 - 1719 - 15:16 pts/1 00:00:00 bash 4 S postfix 4321 2141 0 80 0 - 3150 ? 17:22 ? 00:00:00 pickup -l -t fifo -u 0 S yarn4493 2810 0 80 0 - 1671 -17:59 pts/0 00:00:00 /bin/bash ./process04.sh 0 S yarn 4504 4493 0 80 0 - 1420 - 18:00 pts/0 00:00:00 sleep 30 0 R yarn 4506 3290 0 80 0 - 1639 - 18:00 pts/1 00:00:00 ps -efl |
從上面控制檯顯示的信息中可以看到,字段NI顯示的內容就是進程的調度優先級,可以看到所有的進程調度優先級默認情況下都是0。
如果nice命令未指定進程的調度優先值,則以缺省值10來調整程序運行優先級,既在當前程序運行優先級基礎之上增加10。如下例子。
$ nice process04.sh process begin 10 i = 0 … |
默認情況下,進程的調度優先級是0,上面的例子中在默認值的基礎上增加10的優先級。如果調整後的調度優先級高於-20,則就以優先級-20來運行命令行;若調整後的程序運行優先級低於19,則就以優先級19來運行命令行。這裏需要注意的是一般用戶只能降低進程的調度優先級,如果想要提高進程的調度優先級,只有root用戶才具備權限,下面通過一個例子說明。
[yarn@YARN bash01]$ nice -n -1 process04.sh nice: 無法設置優先級: 權限不夠 process begin 0 i = 0 ^C [yarn@YARN bash01]$ su 密碼: [root@YARN bash01]# nice -n -1 process04.sh process begin -1 i = 0 ^C [root@YARN bash01]# nice -n 30 process04.sh process begin 19 i = 0 ^C [root@YARN bash01]# nice -n -30 process04.sh process begin -20 i = 0 ^C [root@YARN bash01]# |
通過上面的例子說明,一般用戶只能降低進程的調度優先級,而不能提高進程調度優先級。
15.6.2 renice命令
對已經運行的進程進行調度優先級修改需要使用renice命令,需要指定進程的PID,命令格式如下。
renice 調度優先級值 -p 進程PID |
需要說明的是,設定的值是具體的優先級值。與nice命令不同的是可以使用renice命令多次對運行中的進程進行進程優先級設置,但需要注意的有以下幾點:
1. 用戶只能對屬於自己的進程進行優先級設置。
2. 普通用戶只能降低進程的調度優先級而不能提高進程的優先級。
3. root用戶可以對如何進程進行優先級的提高和降低操作。
下面通過一個例子進行說明。
$ process04.sh & [1] 4249 … [yarn@YARN bash01]$ renice 10 -p 4249 4249: old priority 0, new priority 10 [yarn@YARN bash01]$ renice 3 -p 4249 renice: 4249: setpriority: 權限不夠 [yarn@YARN bash01]$ su 密碼: [root@YARN bash01]# renice -12 -p 4249 4249: old priority 10, new priority -12 [root@YARN bash01]# renice -16 -p 4249 4249: old priority -12, new priority -16 |
可以看到yarn用戶第一次將進程的調度優先級設置成10,第二次設置成3時提示權限不夠,優先級從10到3是調度優先級提高了,普通用戶只能降低進程的調度優先級。而root用戶可以對所有的進程進行優先級的提高或降低,這是要注意的。並且普通用戶只能對屬於自己的進程進行優先級設置,對於屬於其他用戶的進程是沒有權限進行設置的。所以有時要用到root用戶的權限進行操作。
15.7 指定腳本的執行時機
通常情況下執行的腳本是實時的,比如手動啓動某個服務進程。但是有些情況下需要在特定的情況下或時間點才需要啓動腳本。在Linux系統中提供了這樣的方法,用來指定腳本的執行時機或特定的時間執行腳本,本節的內容主要對at命令和cron表的使用方法進行說明。
15.7.1 使用at命令計劃執行腳本
在Linux中,可以使用at命令指定何時執行腳本,at命令可以將這個定時啓動腳本的作業提交到隊列中,指定Shell進程何時啓動腳本(需要注意的是,通過at命令指定執行的腳本只執行一次,如果需要定期執行腳本作業,在同一個時間點執行作業需要使用其他的命令)。at命令對應的後臺服務進程是atd,Linux不同的版本有一些不同,有的版本中沒有預裝at工具包,有的版本沒有自動啓動atd服務。使用at命令需要安裝at工具包和啓動atd服務,大多數Linux發行版都會在啓動時運行此守護進程。可以通過下面的命令檢查是否安裝at工具包。
# 檢查是否安裝at工具包 $ rpm -qa | grep at # 檢查atd服務是否啓動 $ ps -ef | grep atd # 安裝at工具包,注意:需要聯網 $ yum install at |
注意:安裝at工具包需要root用戶的權限,
atd服務會定時檢查一個特定的目錄:/var/spool/at。此目錄用來保存at命令提交的作業。默認情況下,atd服務會60秒檢查一下這個目錄。當有at命令提交的作業時,atd服務會檢查作業設置的運行時間。如果設定運行的實際和當前時間匹配,atd服務會啓動運行此作業。at命令的格式如下。
at -f 執行的腳本 執行的日期時間 at 執行的日期時間 |
日期格式:
日期格式 | 舉例 |
HH:MM | 05:30 |
HH:MM YYYY-MM-DD | 05:30 2016-06-18 |
[am|pm] + 數字 [minutes|hours|days|weeks] | 8am + 3 days 9pm + 2 weeks now + 3 hours now + 5 minutes |
第一種方式是指定在特定的時間執行腳本文件,這種方式較爲方便和明確,這種方式可以在腳本中使用,指定特定的時間執行不同的任務。需要說明的是,當作業開始運行時不會綁定任何終端,所以在命令行看不到任何執行過程,所執行的命令和腳本中所有的STDOUT和STDERR都會輸出到當前執行at命令的用戶的E-mail中。下面通過例子說明。
例:process05.sh
#!/bin/bash # 輸出,注意:不會輸出到控制檯,會輸出到當前用戶的E-mail中 echo "Test at command!" echo "Script is running" # 切換目錄 cd /home/yarn/bash01 # 創建日誌目錄 mkdir log.d # 進入目錄 cd log.d # 創建一個空的日誌文件 touch syslog.log # 輸出到E-mail中 echo "Create Log file success" |
控制檯顯示:
[yarn@YARN bash01]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 6 at 2016-04-08 18:05 [yarn@YARN bash01]$ atq 6 2016-04-08 18:05 a yarn [yarn@YARN bash01]$ su 密碼: [root@YARN bash01]# cd /var/spool/at [root@YARN at]# ll 總用量 12 -rwx------. 1 yarn yarn 5024 4月 8 18:00 a0000701739bff drwx------. 2 daemon daemon 4096 4月 8 17:57 spool |
設置5分鐘後執行process05.sh腳本,並輸出了作業的設置時間日期和作業號,這個作業屬於哪個用戶等。at命令會將作業提交到隊列中,在目錄/var/spool/at下,這個目錄要用root用戶的權限才能看到,看到的a0000701739bff文件是隊列文件,當作業完成後,此文件會被刪除。
腳本執行結束後會提示將信息輸出到了E-mail中:
[root@YARN at]# atq You have new mail in /var/spool/mail/yarn |
所有的輸出都輸出到當前用戶的E-mail中:
# cat /var/spool/mail/yarn From [email protected] Fri Apr 8 18:05:01 2016 Return-Path: <[email protected]> X-Original-To: yarn Delivered-To: [email protected] Received: by YARN.localdomain (Postfix, from userid 500) id CB04F408AD; Fri, 8 Apr 2016 18:05:01 +0800 (CST) Subject: Output from your job 6 Message-Id: <[email protected]> Date: Fri, 8 Apr 2016 18:05:01 +0800 (CST) From: [email protected] (yarn)
Test at command! Script is running Create Log file success |
a0000701739bff文件:
#!/bin/sh # atrun uid=500 gid=500 # mail yarn 0 umask 2 ORBIT_SOCKETDIR=/tmp/orbit-yarn; export ORBIT_SOCKETDIR HOSTNAME=YARN; export HOSTNAME USER=yarn; export USER … #!/bin/bash
echo "Test at command!" echo "Script is running"
cd /home/yarn/bash01 mkdir log.d cd log.d touch syslog.log
echo "Create Log file success"
marcinDELIMITER42a52adb |
這個文件是個臨時腳本文件,作業執行後此腳本會被刪除。可以看到腳本的前面部分有環境變量,在腳本的後面是執行的腳本的代碼複製。
腳本執行後創建的目錄:
$ ll /home/yarn/bash01/log.d 總用量 0 -rw-rw-r--. 1 yarn yarn 0 4月 8 18:05 syslog.log |
at命令的的另一種使用方式是在命令行直接輸入要執行的命令,【Ctrl+d】結束。將上面的例子採用命令行方式設定執行計劃。
$ at now + 5 minutes at> echo "Test at command!" at> echo "Script is running" at> cd /home/yarn/bash01 at> mkdir log2.d at> cd log2.d at> touch syslog.log at> echo "Create Log file success" at> <EOT> job 8 at 2016-04-21 17:18 $ |
上面的方式比較腳本而言適合執行不常用的任務,如果採用腳本的方式就不需要每次輸入命令而直接指定腳本就可以了。兩種方式中採用腳本的方式較爲常見。
刪除一個未執行的作業可以使用atrm命令,命令格式如下。
atrm 作業號 |
刪除一個未執行的作業需要指定作業的作業號,下面通過例子說明。
[yarn@YARN mail]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 13 at 2016-04-22 10:41 [yarn@YARN mail]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 14 at 2016-04-22 10:41 [yarn@YARN mail]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 15 at 2016-04-22 10:41 [yarn@YARN mail]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 16 at 2016-04-22 10:41 [yarn@YARN mail]$ atq 16 2016-04-22 10:41 a yarn 14 2016-04-22 10:41 a yarn 15 2016-04-22 10:41 a yarn 13 2016-04-22 10:41 a yarn [yarn@YARN mail]$ atrm 16 [yarn@YARN mail]$ atq 14 2016-04-22 10:41 a yarn 15 2016-04-22 10:41 a yarn 13 2016-04-22 10:41 a yarn [yarn@YARN mail]$ atrm 15 [yarn@YARN mail]$ atq 14 2016-04-22 10:41 a yarn 13 2016-04-22 10:41 a yarn [yarn@YARN mail]$ atrm 13 [yarn@YARN mail]$ atq 14 2016-04-22 10:41 a yarn [yarn@YARN mail]$ |
通過atrm命令可以刪除未執行的作業,atq命令顯示未執行的作業。
15.8 啓動時運行腳本或命令
在Linux系統啓動時或者用戶重新啓動一個Shell進程時,執行一個或多個特定功能的腳本,完成特定的功能或啓動特定的服務。例如當系統啓動時啓動WEB服務、數據庫服務或重新生成一個系統日誌文件等。另外,當用戶重新啓動一個Shell進程時,也同樣可以先執行一個或多個腳本,例如用戶專用的環境變量或特定的命令函數等。
15.8.1 系統啓動時運行腳本
Linux系統採用特定的順序在系統啓動時運行腳本,不同的Linux版本啓動的流程略有不同。瞭解系統啓動流程可以幫助我們在系統啓動時運行自己的腳本。下面將系統啓動時的順序進行簡要的說明。
1. 開機過程
當開始運行Linux系統時,Linux會將系統內核加載到內存中並運行。首先讀取/etc/inittab文件,/etc/inittab文件列出了系統的運行級別,不同的Linux運行級別會啓動不同的程序和腳本。
/etc/inittab文件:
# inittab is only used by upstart for the default runlevel. … # Default runlevel. The runlevels used are: # 0 - halt (Do NOT set initdefault to this) # 1 - Single user mode # 2 - Multiuser, without NFS (The same as 3, if you do not have networking) # 3 - Full multiuser mode # 4 - unused # 5 - X11 # 6 - reboot (Do NOT set initdefault to this) # id:5:initdefault: |
以CentOS系統爲例,系統的運行級別分爲六種,如下表說明。
運行級別 | 說明 |
0 | 關機 |
1 | 單用戶模式 |
2 | 多用戶模式,通常不支持網絡 |
3 | 全功能多用戶模式,支持網絡 |
4 | 可定義用戶 |
5 | 多用戶模式,支持網絡和圖形化桌面系統 |
6 | 重啓 |
系統運行級別可以控制啓動系統上的那些服務,可以看到/etc/inittab文件默認的運行級別是5,是多用戶模式。每個運行級別將決定啓動過程運行和停止哪些腳本。這些開機腳本都是Shell腳本,通過提供必要的環境變量來啓動應用程序。開機腳本會放置在/etc/rc*.d目錄或init.d目錄下,*號代表系統運行級別。不同的Linux版本略有不同。
2. 定義自己的開機腳本
不要將用戶的啓動腳本和系統的啓動的腳本放置在同一目錄下,例如:將自己的Shell腳本放置在rc5.d目錄下,或者在系統啓動腳本中添加自己的腳本內容,這樣可能會出現不可預測的問題。通常情況下,以CentOS系統爲例,用戶可以在/etc/rc.d/rc.local文件中添加用戶啓動時執行的腳本。
在rc.local文件中可以指定要執行的命令或者腳本,但要注意的是執行的腳本需要完整的路徑,否則系統找不到執行文件會提示錯誤信息。如下例子,在系統啓動時執行yarn用戶目錄下的一個腳本文件,注意:需要提供完整路徑。
/etc/rc.d/rc.local 文件 :
#!/bin/sh # # This script will be executed *after* all the other init scripts. # You can put your own initialization stuff in here if you don't # want to do the full Sys V style init stuff.
touch /var/lock/subsys/local /home/yarn/bash01/init.sh |
配置完成後,每次啓動系統,都會執行init.sh腳本,如果創建文件或刪除文件等等。
15.8.2 啓動新的Shell進程時運行腳本
在前面的章節中講過,每個Linux系統用戶的工作目錄下都存在兩個文件:.bash_profile文件和.bashrc文件,可以使用這兩個文件設置用戶環境變量、函數、命令別名和啓動用戶腳本。
當用戶登錄系統時啓動的Shell進程會執行.bash_profile文件,並且只執行一次,所以可以將用戶登錄時要執行的腳本設置到這個文件中。而.bashrc文件是通過bash命令或打開一個終端時啓動的新的Shell進程時都會執行一次,所以這個文件中也可以設置要運行的腳本,但要注意,每次啓動一次新的Shell進程時都會運行一次。
或者在/etc/profile.d目錄下創建用戶的腳本,當用戶登錄時,這個腳本會被執行。或者在/etc/profile文件中指定要執行的腳本,需要指定腳本的完整路徑。/etc/profile文件只在用戶登錄時執行一次。另外,可以在/etc/bashrc文件中指定要運行的腳本,但不同之處在於,系統上所有的用戶只要通過bash命令或重新打開一個終端時都會執行一次,用戶可以根據不同的情況選擇不同的文件執行用戶的腳本。