1. 程式人生 > >shell中信號處理

shell中信號處理

註意 一段 是什麽 ots ask rip 它的 rap 控制

參考: Shell 腳本中信號處理實踐 Linux Shell 的信號 trap 功能你必須知道的細節 在 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, 什麽也不做,即忽略該信號,使用

trap ‘‘ SIGINT

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

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

運行

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

在執行 TaskOne 時給腳本發送信號 SIGTERM,腳本當時在執行 “sleep”,sleep 結束時轉而去執行信號處理函數。註意,並不是在 TaskOne 結束後再去執行信號處理函數。

trap的一些使用說明:

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

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

第三種:
 trap ‘ ‘ signal-list
trap 命令指定一個空命令串, 允許忽視信號.

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

trap   echo “aaaaaaaaaaa”  INT
trap   echo “bbbbbbbbbbb”  INT

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

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

如無特殊說明,下文所指 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 是內建命令,會被打斷。)

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

shell中信號處理