如何在shell脚本中使用用户提示处理SIGINT陷阱?

时间:2015-11-03 22:39:15

标签: linux shell signals interrupt-handling sigint

我试图以这样的方式处理SIGINT / CTRL + C中断:如果用户意外按下ctrl-c,系统会提示他“你想退出吗?(是/否)”。如果他输入yes,则退出脚本。如果否,则从中断发生的地方继续。基本上,我需要Ctrl-C工作类似于Ctrl-Z / SINTSTP,但方式略有不同。我尝试了各种方法来解决这个问题,但我没有得到预期的结果。以下是我尝试过的几个场景。

案例:1

脚本:play.sh

create table t ( id varchar2(20), val number );

delete from t;

INSERT INTO t ( id, val ) values ( '12345', 50);
INSERT INTO t ( id, val ) values ( '12345', 50);
INSERT INTO t ( id, val ) values ( '12345', -50);
INSERT INTO t ( id, val ) values ( '12345', -50);
INSERT INTO t ( id, val ) values ( 'abcde', 40);
INSERT INTO t ( id, val ) values ( 'abcde', 40);
INSERT INTO t ( id, val ) values ( 'abcde', 20);
INSERT INTO t ( id, val ) values ( 'abcde', 40);
INSERT INTO t ( id, val ) values ( 'abcde', -40);
INSERT INTO t ( id, val ) values ( '11111', 30);
INSERT INTO t ( id, val ) values ( '11111', -30);
INSERT INTO t ( id, val ) values ( '11111', -30);
INSERT INTO t ( id, val ) values ( 'aaaaa', 10);
INSERT INTO t ( id, val ) values ( 'aaaaa', -30);

COMMIT;

MERGE INTO t
USING  (WITH value_partition AS
               (SELECT t.*,
                       ROW_NUMBER () OVER (PARTITION BY t.id, t.val ORDER BY ROWID) rn_in_value
                FROM   t)
        SELECT sp.ROWID row_id,
               sp.*,
               CASE WHEN SUM (sp.val) OVER (PARTITION BY sp.id, ABS (sp.val), rn_in_value) = 0 THEN 'N' ELSE 'Y' END
                 keep_row
        FROM   value_partition sp) u
ON     (t.ROWID = u.row_id
AND     u.keep_row = 'N')
WHEN MATCHED THEN
  UPDATE SET t.val   = u.val
  DELETE
    WHERE  u.keep_row = 'N';

SELECT * FROM t;

当我运行上面的脚本时,我得到了预期的结果。

输出:

#!/bin/sh
function stop()
{
while true; do 
    read -rep $'\nDo you wish to stop playing?(y/n)' yn
    case $yn in
        [Yy]* ) echo "Thanks for playing !!!"; exit 1;;
        [Nn]* ) break;;
        * ) echo "Please answer (y/n)";;
    esac
done
} 
trap 'stop' SIGINT 
echo "going to sleep"
for i in {1..100}
do
  echo "$i"
  sleep 3   
done
echo "end of sleep"

案例:2 我将for循环移动到新的脚本 loop.sh ,因此play.sh成为父进程,loop.sh成为子进程。

脚本:play.sh

    $ play.sh 
    going to sleep
    1
    ^C
    Do you wish to stop playing?(y/n)y
    Thanks for playing !!!

    $ play.sh 
    going to sleep
    1
    2
    ^C
    Do you wish to stop playing?(y/n)n
    3
    4
    ^C
    Do you wish to stop playing?(y/n)y
    Thanks for playing !!! 
    $  

脚本:loop.sh

#!/bin/sh
function stop()
{
while true; do 
    read -rep $'\nDo you wish to stop playing?(y/n)' yn
    case $yn in
        [Yy]* ) echo "Thanks for playing !!!"; exit 1;;
        [Nn]* ) break;;
        * ) echo "Please answer (y/n)";;
    esac
done
}
trap 'stop' SIGINT 
loop.sh

此情况下的输出与预期不符。

输出:

#!/bin/sh
echo "going to sleep"
for i in {1..100}
do
  echo "$i"
  sleep 3   
done
echo "end of sleep"

我理解当进程收到SIGINT信号时,它会将信号传播到所有子进程,因此我的第二种情况失败了。有没有办法可以避免SIGINT传播到子进程,从而使 loop.sh 完全按照它在第一种情况下的工作方式工作?

注意: 这只是我实际应用的一个示例。 我正在处理的应用程序在play.sh和loop.sh中有几个子脚本。我应该确保接收SIGINT的应用程序不应该终止,但它应该提示用户输入消息。

1 个答案:

答案 0 :(得分:1)

play.sh中不要调用新脚本,但请将其源于此:

source ./loop.sh

我说由于没有为脚本生成新的子shell,当你退出陷阱时程序流程会继续。如果您调用新脚本,则陷阱将传播到子shell,但如果退出子shell,它将无法返回。