<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何讓shell腳本自殺

          共 6346字,需瀏覽 13分鐘

           ·

          2024-06-24 08:00

          1.腳本自殺正文

          有些時候我們寫的shell腳本中有一些后臺任務(wù),當(dāng)腳本的流程已經(jīng)執(zhí)行到結(jié)尾處或?qū)⑵鋕ill掉時,這些后臺任務(wù)會直接掛靠在init/systemd進(jìn)程下,而不會隨著腳本退出而停止。

          例如:

          [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

          從結(jié)果中可以看到,腳本退出后,sleep進(jìn)程的父進(jìn)程變?yōu)榱?,也就是掛在了init/systemd進(jìn)程下。

          這時我們可以在腳本中直接使用kill命令殺掉sleep進(jìn)程。

          [root@mariadb ~]# cat test1.sh 
          #
          !/bin/bash
          echo $BASHPID
          sleep
          50 &
          kill $
          !

          但是,如果這個sleep進(jìn)程是在循環(huán)中(for、while、until均可),那就麻煩了。

          例如下面的例子,直接將循環(huán)放入后臺,殺掉sleep、或者exit、或者殺掉腳本自身進(jìn)程、或者讓腳本自動退出、甚至exec退出當(dāng)前腳本shell都是無效的。

          [root@mariadb ~]# cat test1.sh 
          #
          !/bin/bash
          echo $BASHPID

          while true;do
          sleep
          50
          echo
          1
          done
          &

          killall sleep
          kill $BASHPID

          為了分析,新建一個腳本test2.sh:

          #!/bin/bash
          echo $BASHPID

          while true;do
          sleep 50
          echo 1
          done &

          sleep 60

          然后在腳本執(zhí)行的60秒內(nèi)查看test2.sh進(jìn)程的信息:

          [root@mariadb ~]# pstree -p | grep 'test2.sh'
          | `-bash(2687)---test2.sh(2923)-+-sleep(2925)
          | `-test2.sh(2924)---sleep(2926)

           其中pid=2923的test2.sh進(jìn)程是腳本自身進(jìn)程,pid=2924的test2.sh進(jìn)程是while開始運行后為while提供執(zhí)行環(huán)境的子shell進(jìn)程(為什么會生成這個進(jìn)程,見我的另一篇文章)。

          所以,對于前面的test1.sh進(jìn)程,殺掉了 $BASHPID 對應(yīng)的test1.sh進(jìn)程后,其實還有一個為while提供運行環(huán)境的test1.sh進(jìn)程,且這個進(jìn)程在 $BASHPID 結(jié)束后,會掛在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)

          這就是shell腳本中的一個'疑難雜癥',CTRL+C中止了腳本進(jìn)程,這個腳本卻還在后臺不斷運行,且時不時地輸出點信息到終端(我這里是循環(huán)中的echo命令輸出的)

          除非我們手動殺掉新生成的test1.sh,否則這個腳本將無限循環(huán)下去。但是,這不是很麻煩嗎?

          那么如何實現(xiàn)'腳本自殺'?其實很簡單,只要在腳本退出前,使用killall命令殺掉腳本進(jìn)程即可。

          [root@mariadb ~]# cat test1.sh 
          #
          !/bin/bash
          echo $BASHPID

          while true;do
          sleep
          50
          echo
          1
          done
          &

          killall `basename $
          0`

          這樣,在腳本退出前,兩個test1.sh進(jìn)程都會被殺掉。

          再考慮一個問題,如果腳本已經(jīng)執(zhí)行到了while中的后臺任務(wù),但在執(zhí)行到killall命令之前按下了CTRL+C,這時由于沒有執(zhí)行killall,后臺任務(wù)也將掛在新的腳本進(jìn)程下。我們的目的是保證腳本終止,其內(nèi)進(jìn)程一定終止。所以我們需要對這種情況做出合理的處理。

          可以使用trap捕捉ctrl+c信號,捕捉到的時候執(zhí)行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`

          這樣就能保證腳本終止時,其內(nèi)一切任務(wù)都將終止的目的。

           上面的腳本并不健壯,因為 ./test1.sh 和 bash test1.sh 兩種執(zhí)行方式的進(jìn)程名稱不一樣,前者的進(jìn)程名稱為test1.sh,后者的進(jìn)程名稱為bash,所以killall沒法同時解決這兩種情況。為了健壯性,可以加上殺后臺進(jìn)程'$!'的代碼,并將killall換成pkill,且通過篩選全路徑的方式殺掉進(jìn)程:

          [root@mariadb ~]# cat test1.sh 
          #
          !/bin/bash

          trap
          'pkill -f `basename $0`' SIGINT
          echo $BASHPID

          while true;do
          sleep
          50
          echo
          1
          done
          &
          pid=$!
          kill $pid
          pkill
          -f `basename $0`

          為了讓腳本自殺更健壯、更通用化,并省去上面結(jié)尾處的一大堆額外命令??梢栽趖rap中一次性完成這些任務(wù):

          #!/bin/bash

          trap
          'pkill -f $(basename $0);exit 1' SIGINT SIGTERM EXIT ERR

          while true;do
          sleep 1
          echo 'hello world!'
          done &

          #
          do something
          sleep 60

          可能寫100個shell腳本也遇不到需要一個腳本需要將while/for/until這樣的語句放入后臺的。但有時候也是有用的。例如,有個需求:每秒去a.txt文件中同步數(shù)據(jù)到b.txt中,然后每分鐘對b.txt文件做處理。

          #!/bin/bash

          while true;do
          (a.txt
          --->b.txt)
          sleep 1
          done &

          while true;do
          (b.txt)
          sleep 60
          done

          此外,對一些比較復(fù)雜的需求(我個人遇到過多次),可能也會使用到后臺的循環(huán)。

          本文只是提供一種殺腳本的解決方案。很多情形并非如我這里所描述的,例如不是while循環(huán)放后臺,而是循環(huán)內(nèi)的sleep放后臺,這時(腳本終止時)sleep會掛在init/systemd下,不過這很簡單。相信讀懂了本文,各位已經(jīng)了解了一些trap的功能以及處理這類問題的邏輯,也知道其他各種情形如何處理。

          最后,有一種更方便更精確的自殺手段:man kill。在該man手冊中解釋了,如果kill的pid值為0,表示發(fā)送信號給當(dāng)前進(jìn)程組中所有進(jìn)程,對shell腳本來說這意味著殺掉腳本中產(chǎn)生的所有進(jìn)程。方案如下:

          #!/bin/bash

          trap
          'echo 'signal_handled:';kill 0' SIGINT SIGTERM

          while true;do
          sleep 5
          echo 'hello world! hello world!'
          done &
          sleep 60

           

          2.補充:bash內(nèi)置命令的特殊性

          為什么上文運行腳本進(jìn)程,腳本中的后臺while會新生成一個腳本進(jìn)程?在這里補充說明下。

           

          究其原因,是因為while/for/until等是bash內(nèi)置命令,它們的特殊性在于它們有一個很替它們著想的爹:bash進(jìn)程。bash進(jìn)程對他們的孩子非常負(fù)責(zé),所有能直接執(zhí)行的內(nèi)置命令都不會創(chuàng)建新進(jìn)程,它們直接在當(dāng)前bash進(jìn)程內(nèi)部調(diào)用執(zhí)行,所以我們用ps/top等工具是捕捉不到cd、let、expr等等內(nèi)置命令的。但正因為爹太負(fù)責(zé),把孩子們寵壞了,這些bash內(nèi)置命令的執(zhí)行必須依賴于bash進(jìn)程才能執(zhí)行。

          內(nèi)置命令中還有幾個比較特殊的關(guān)鍵字:while、for、until、if、case等,它們無法直接執(zhí)行,需要結(jié)合其他關(guān)鍵字(如do/done/then等)才能執(zhí)行。非后臺情況下,它們的爹會直接帶它們執(zhí)行,但當(dāng)它們放進(jìn)后臺后,它們必須先找個bash爹提供執(zhí)行環(huán)境:

          • 如果是在當(dāng)前shell中放進(jìn)后臺,則這個爹是新生成的bash進(jìn)程。這個新的bash進(jìn)程只負(fù)責(zé)一件事,就是負(fù)責(zé)這個后臺,為它的孩子們提供它們依賴的bash環(huán)境。

          • 如果是在腳本中放進(jìn)后臺,則這個爹就是腳本進(jìn)程。由于腳本不是內(nèi)置命令,它能直接負(fù)責(zé)這個后臺(因為腳本進(jìn)程也算是bash進(jìn)程的特殊變體,也相當(dāng)于一個新的bash進(jìn)程)。

          驗證下就知道咯。

          目前bash進(jìn)程信息為:

          [root@xuexi ~]# pstree -p | grep bash
          |-sshd(1142)-+-sshd(5396)---bash(5398)---mysql(5659)
          | `-sshd(7006)-+-bash(7008)
          | `-bash(12280)-+-grep(13294)

          將for、unitl、while、case、if等語句放進(jìn)后臺。例如:

          [root@xuexi ~]# if true;then sleep 10;fi &

          然后再查bash進(jìn)程信息:

          [root@xuexi ~]# pstree -p | grep bash
          |-sshd(1142)-+-sshd(5396)---bash(5398)---mysql(5659)
          | `-sshd(7006)-+-bash(7008)---bash(13295)---sleep(13296)
          | `-bash(12280)-+-grep(13298)

          不難看出,sleep進(jìn)程之前先生成了一個pid=13295的bash進(jìn)程。(注:如果這幾個特殊關(guān)鍵字不進(jìn)入后臺,則是當(dāng)前在bash進(jìn)程下執(zhí)行的)

          無論它們的爹是腳本進(jìn)程還是新的bash進(jìn)程,它們都是當(dāng)前shell下的子shell。如果某個子shell中有后臺進(jìn)程,當(dāng)殺掉子shell,意味著殺掉了它們的爹。非內(nèi)置bash命令不依賴于bash,所以直接掛在init/systemd下,而bash內(nèi)置命令嚴(yán)重依賴于bash爹,沒有爹就沒法執(zhí)行,所以在殺掉bash進(jìn)程(上面pid=7008)的時候,bash爹(pid=13295)會立即帶著它下面的進(jìn)程(sleep)掛在init/systemd下。

          再來驗證下咯。還是剛才的后臺命令。

          [root@xuexi ~]# while true;do sleep 2;done &

          另一個窗口,查看bash進(jìn)程信息:

          [root@xuexi ~]# pstree -p | grep bash 
          |-sshd(1142)-+-sshd(5396)---bash(5398)---mysql(5659)
          | `-sshd(7006)-+-bash(7008)---bash(13468)---sleep(13526)
          | `-bash(12280)-+-grep(13528)

          殺掉pid=7008的bash進(jìn)程(為什么不殺pid=13468的bash進(jìn)程?它是為while提供環(huán)境的bash進(jìn)程,殺了這個相當(dāng)于殺了while循環(huán)結(jié)構(gòu))。注意,這個bash進(jìn)程是交互式登陸shell,默認(rèn)情況下會忽略SIGTERM信號,所以只能使用SIGKILL信號來殺。

          [root@xuexi ~]# kill -9 7008

          [root@xuexi
          ~]# pstree -p | grep bash
          |-bash(13468)---sleep(13562)
          |-sshd(1142)-+-sshd(5396)---bash(5398)---mysql(5659)
          | `-sshd(7006)---bash(12280)-+-grep(13564)

          可以看到,新生成了一個bash進(jìn)程,而且這個bash進(jìn)程是掛在init/systemd下的,這意味著該bash和終端無關(guān)??聪旅娴臓顟B(tài)為'?'。

          [root@xuexi ~]# ps aux | grep bas[h]
          root
          5398 0.0 0.1 116548 3300 pts/0 Ss 09:04 0:00 -bash
          root
          12280 0.0 0.1 116568 3340 pts/2 Ss 14:43 0:00 -bash
          root
          13468 0.0 0.1 116556 1924 ? S 15:49 0:00 -bash

          bash進(jìn)程竟然會掛在init/systemd下?如此奇怪現(xiàn)象,可能你除了這里外永遠(yuǎn)也不會遇到。

          鏈接:https://www.cnblogs.com/f-ck-need-u/p/8661501.html

                                                                       (版權(quán)歸原作者所有,侵刪)

          瀏覽 168
          1點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          1點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲福利精品内射 | 中文无码熟妇人妻AV在线 | 五月婷婷第四色 | 日韩乱伦影片 | 大香蕉婷婷丁香五月 |