有些時候我們寫的shell腳本中有一些後臺任務,當腳本的流程已經執行到結尾處並退出時,這些後臺任務會直接掛靠在init/systemd進程下,而不會隨著腳本退出而停止。 例如: 從結果中可以看到,腳本退出後,sleep進程的父進程變為了1,也就是掛在了init/systemd進程下。 這時我們可以在 ...
有些時候我們寫的shell腳本中有一些後臺任務,當腳本的流程已經執行到結尾處或將其kill掉時,這些後臺任務會直接掛靠在init/systemd進程下,而不會隨著腳本退出而停止。
例如:
[root@mariadb ~]# cat test1.sh #!/bin/bash echo $BASHPID sleep 50 & [root@mariadb ~]# ps -elf | grep slee[p] 0 S root 10806 1 0 80 0 - 26973 hrtime 19:26 pts/1 00:00:00 sleep 50
從結果中可以看到,腳本退出後,sleep進程的父進程變為了1,也就是掛在了init/systemd進程下。
這時我們可以在腳本中直接使用kill命令殺掉sleep進程。
[root@mariadb ~]# cat test1.sh #!/bin/bash echo $BASHPID sleep 50 & kill $!
但是,如果這個sleep進程是在迴圈中(for、while、until均可),那就麻煩了。
例如下麵的例子,殺掉sleep、或者exit、或者殺掉腳本自身進程、或者讓腳本自動退出、甚至exec退出當前腳本shell都是無效的。
[root@mariadb ~]# cat test1.sh #!/bin/bash echo $BASHPID while true;do sleep 50 echo 1 done & killall sleep kill $BASHPID
因為sleep在while中迴圈,殺了sleep後,稍後又會生成一個sleep。而exit、殺掉腳本自身、或者讓腳本自動退出、或者使用exec,由於有迴圈存在,它都會重新fork一個腳本進程出來。註意,這時迴圈的進程不會掛在init/systemd進程下,而是掛在一個新的腳本進程之下。
[root@mariadb ~]# ./test1.sh 10859 ./test1.sh: line 7: 10862 Terminated sleep 50 Terminated 1 [root@mariadb ~]# pstree -p | grep sleep |-test1.sh(10860)---sleep(10863)
從結果中可以看到test1.sh的10859進程被自身殺掉了,但是重新生成了一個10860的test1.sh進程,且不斷生成的sleep進程也總是在test1.sh下。
除非我們手動殺掉新生成的test1.sh,否則這個腳本將無限迴圈下去。但是,這不是很麻煩嗎?
那麼如何實現"腳本自殺"?其實很簡單,只要在腳本退出前,使用killall命令殺掉腳本進程即可。
[root@mariadb ~]# cat test1.sh #!/bin/bash echo $BASHPID while true;do sleep 50 echo 1 done & killall `basename $0`
這樣,在腳本退出前,內核准備fork新的test1.sh進程接管後臺的while內的sleep進程時,killall會將這個新的test1.sh也殺掉,這樣後臺進程就隨著test1.sh也一起消逝了。
這裡的關鍵點是:
1.當腳本中有迴圈的後臺任務時,腳本退出有一個過程:(1)腳本準備退出-->(2)內核fork新的腳本進程用來接管腳本內的後臺迴圈任務-->(3)新腳本進程接管後臺任務-->(4)舊腳本進程消逝。
2.killall在發送信號後會檢查進程是否仍然存活,如果還存活,則會繼續發送信號,只有已經確定不再存活時,才會成功返回。預設檢查的時間間隔為1秒。
killall正好在這種情況下派上用場,它趕在第(3)步之前將新舊腳本進程都殺掉,這使得腳本進程成功自殺。
再考慮一個問題,如果腳本已經執行到了while中的後臺任務,但在執行到killall命令之前按下了CTRL+C,這時由於沒有執行killall,後臺任務也將掛在新的腳本進程下。我們的目的是保證腳本終止,其內進程一定終止。所以我們需要對這種情況做出合理的處理。
可以使用trap捕捉ctrl+c信號,捕捉到的時候執行killall命令即可。例如:
[root@mariadb ~]# cat test1.sh #!/bin/bash trap "killall `basename $0`" SIGINT echo $BASHPID while true;do sleep 50 echo 1 done & killall `basename $0`
這樣就能保證腳本終止時,其內一切任務都將終止的目的。
回到Linux系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
回到網站架構系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7576137.html
回到資料庫系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7586194.html
轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/8661501.html
註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!