shell中信號處理

 
在 unix 裏,可能發生的每一種類型的事件都是由一個獨立的信號來描述,每個信號都是一個小的正整數,如:

名稱       值     描述
SIGHUP  1      控制終端發現被掛起或控制進程死亡
SIGINT  2      鍵盤終端
SIGQUIT 3      來自鍵盤的退出信號
SIGKILL 9      殺死進程的信號
SIGALRM 14     定時時鐘中斷
SIGTERM 15     終止信號

使用:kill -l 列出所支持的信號

發送信號:kill -signal pid 
SIGTERM: kill pid 等價於 kill -s SIGTERM pid
如: kill -s SIGHUP 1001; 等價於 kill -1 1001,一個使用信號名稱,一個使用表明該信號的整數

SIGKILL: SIGKILL 有不能被捕捉的特殊能力,任何接受到它的進程都要當即終止, kill -9 1001
 
有時須要中止正在運行的腳本,使用 kill -9 <pid> 的方式(即發送信號 SIGKILL)有時不免會產生一些垃圾文件,因此須要找出一種方式讓腳本優雅的退出。

解決方案 
Shell 腳本能夠經過內置的 trap 命令定製本身的信號處理函數,即捕捉髮送到當前進程的信號並執行本身定義的函數,而不是執行系統默認的函數。注意,SIGKILL 不可捕捉,爲何呢?由於系統總要留下一種結束進程的手段,不然若是所有信號都被捕獲但不即出,那麼這個進程就沒法被殺掉了。 
trap 命令格式以下: 
trap 'command_list' signals

其中,command_list 是一個命令清單,能夠包含一個函數,在接收到信號列表中包含的某個信號後運行。而 signals 是將要捕捉的信號的列表。 
例如: 
捕獲 SIGINT, 輸出一條消息後退出,使用 
trap 'echo you hit Ctrl-C\, now exiting..; exit' SIGINT

捕獲 SIGINT, 什麼也不作,即忽略該信號,使用html

trap '' SIGINT

重置 SIGINT,即便用系統默認的信號處理函數shell

trap - SIGINT


本例中,能夠捕獲 SIGTERM 信號,並指定信號處理函數來實現資源的清理工做,資源清理乾淨後再退出。編程

實現  
horen@heart$~> cat signal.sh 
#!/bin/bash

trap 'TaskClean; exit' SIGTERM

function TaskOne()
{
   echo "Now do task one..."
   sleep 10
   echo "TaskOne is done"
}

function TaskTwo()
{
   echo "Now do task two..."
   sleep 10
   echo "TaskTwo is done"
}

function TaskClean()
{
   echo "Now do task clean..."
   sleep 2
   echo "TaskClean is done"
}


TaskOne
TaskTwo

運行
bash

horen@heart$~> sh signal.sh 
Now do task one...
Now do task clean...
TaskClean is done

在執行 TaskOne 時給腳本發送信號 SIGTERM,腳本當時在執行 「sleep」,sleep 結束時轉而去執行信號處理函數。注意,並非在 TaskOne 結束後再去執行信號處理函數。 ide

 

trap的一些使用說明:函數

它有三種形式分別對應三種不一樣的信號迴應方式.
第一種:
 trap 'commands' signal-list
當腳本收到 signal-list 清單內列出的信號時, trap 命令執行雙引號中的命令.

第二種:
 trap signal-list 
trap 不指定任何命令, 接受信號的默認操做. 默認操做是結束進程的運行.

第三種:
 trap ' ' signal-list
trap 命令指定一個空命令串, 容許忽視信號.工具

 

NOTE:trap 對同種 signal 只能相應一種設定,若是在一個 shell 裏面設置多個 trap,如:ui

trap   'echo 「aaaaaaaaaaa」'  INT
trap   'echo 「bbbbbbbbbbb」'  INT

 

那麼它只會響應最後一個信號設定。spa

 

信號處理(Signal Handling)在 Linux 編程中一直扮演者重要的角色,幾乎每一個系統工具都要用到它,最多見的功能莫過於用信號進行進程間通訊(尤爲是父子進程)以及捕捉 SIGINT、SIGTERM 之類的退出信號以作一些善後處理(cleanup)。C 中自沒必要多說,可使用 wait 族函數;而 shell 腳本中也有捕捉信號的 trap 功能——然而許多人在使用 trap 功能的時候卻存在着這樣那樣的誤解,這些看似可有可無的小細節最後有可能使得你的腳本與你預想的行爲徹底不一樣。.net

 

如無特殊說明,下文所指 shell 均以 Bash 爲例。

0. trap 的使用簡介

雖然我很想說這些應當要本身看 manpage ,但考慮到也許正在讀文章的你手邊沒有 Linux ,仍是簡單說一下吧。

1
USAGE:  trap [action condition ...]

即當捕捉到 condition 列表所對應的任何一個信號時,執行 action 動做(使用 eval action 來執行,故 action 能夠是 shell 內建指令、外部命令及腳本中的函數等)。action 還但是」"(空)、’-'等,分別表明忽略相應信號及重置相應信號爲默認行爲。

1. condition 的標準格式是什麼?

condition 中的信號到底應該如何書寫?好比終端中斷信號(通常用 CTRL-C 發出),究竟是寫 SIGINT 、 INT 仍是 2(大部分系統上該信號對應的信號數)?是大寫仍是小寫?

若是你使用最新版的 Bash ,那麼這幾種寫法均可以。而若是你須要在不一樣 shell 中保持可移植性,請使用大寫、不帶前綴的 INT !根據 POSIX 標準, trap 的 condition 不該當加上 SIG 前綴,且必須全大寫,容許帶 SIG 前綴或小寫是某些 shell 的擴展功能。而信號數在不一樣的系統上可能不一樣,因此也不是一個好主意。

2. trap 必須放在第一行麼?

許多資料,尤爲是中文資料中不容申辯地指明—— trap 必須放在腳本中第一個非註釋行。事實果然如此麼?

不管是 manpage 仍是 POSIX 文檔中,我都沒有找到任何與之相關的說明。甚至在 TLDP 的 Bash Guide for Beginners 中,多個例子都分明把 trap 放在了腳本的中間。最後我在這篇文檔中找到了下面這句常常被誤讀的話:

Normally, all traps are set before other executable code in the shell script is encountered, i.e., at the beginning of the shell script.

果真,這只是一個爲了保證信號鉤子儘早被設立的一個設計慣例罷了。事實上, trap 能夠根據你的須要放在腳本中的任何位置。腳本中也能夠有多個 trap ,能夠爲不一樣的信號定義不一樣的行爲,或是修改、刪除已定義的 trap 。更進一步地, trap 也有做用範圍,你能夠把它放在函數中,它將只在這個函數裏起效!你看,其實 trap 的行爲是很符合 UNIX 的慣例的。

3. 信號到底是在何時被 trap 處理?

這是本文最重要的一點。信號究竟是何時被處理的?更準確地說,好比腳本正在執行某個命令時收到了某個信號,那麼它會被當即處理,仍是要等待當前命令完成?

我不打算直接說明答案。爲了讓咱們對這個問題有更透徹的理解,讓咱們來作一下實驗。看下面這個時常被用來說解 trap 的腳本:

1
2
3
#!/bin/bash
trap 'echo"INTERRUPTED!"; exit' INT
sleep 100

大多數教程都是這麼作的,運行這個腳本,按下 CTRL-C 。你看到了什麼?腳本打出了 「INTERRUPTED!」 並中止了運行。這看起來彷佛很正常、很直覺——以此看來, trap 會當即捕捉到信號並執行,無論當前正在執行的命令。許多腳本也正是在這個假設下寫的。

然而真的是這樣麼?讓咱們作另外一個實驗——在一個終端執行這個腳本,並打開另外一個終端,用ps-ef|grepbash找到這個腳本的進程號,而後用kill-SIGINT pid向這個進程發送 SIGINT 信號。你在原先的終端中看到了什麼?沒有任何反應!若是你願意等上 100 秒,你最終會看到 「INTERRUPTED!」 被輸出。這樣看來 trap 是等到當前命令結束之後再處理信號。

這樣的矛盾到底是爲何?問題其實出在 CTRL-C 身上。 Bash 等終端的默認行爲是這樣的:當按下 CTRL-C 以後,它會向當前的整個進程組發出 SIGINT 信號。而 sleep 是由當前腳本調用的,是這個腳本的子進程,默認是在同一個進程組的,因此也會收到 SIGINT 並中止執行,返回主進程之後 trap 捕捉到了信號。

這篇文檔給了咱們一個更準確的說明——若是當前正有一個外部命令在前臺執行,那麼 trap 會等待當前命令結束之後再處理信號隊列中的信號。(而許多教程出錯的另外一個緣由就是——某些 shell 中 sleep 是內建命令,會被打斷。)

那麼上文的例子應當要如何寫才能達到想要的效果呢?有兩種方法:1、把 sleep 放到後臺進行,再用內建的 wait 去等待其執行結束(詳見上一段提到的那篇文檔);2、暴力一點,把一長段 sleep 拆成一秒的小 sleep 的循環,這在對精度要求不高的狀況下也是一個可行的辦法。

相關文章
相關標籤/搜索