bash 如何捕获 Ctrl-C 并使用它正确退出无限循环

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20071030/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 08:40:55  来源:igfitidea点击:

How to capture Ctrl-C and use it to exit an endless loop properly

bashwinesigint

提问by localhost

I'm trying to run a program inside an endless loop because it sometimes dies for no reason. I would like to be able to hit Ctrl-C to prevent the program being restarted though.

我试图在一个无限循环中运行一个程序,因为它有时会无缘无故地死掉。我希望能够按 Ctrl-C 以防止程序重新启动。

I don't want Ctrl-C to kill the program, just to wait until it dies, then not restart it again.

我不希望 Ctrl-C 杀死程序,只是等到它死掉,然后再不重新启动它。

theprogramis a wine program (utorrent).

theprogram是一个葡萄酒程序(utorrent)。

Bonus points for telling me how to make it so it will safely exit theprogramjust like clicking on the 'x' in the top right of it. When I manually killit from the command line or hit Ctrl-C, it doesn't get to run its cleanup code. Hence my attempt to just stop it being restarted.

告诉我如何制作它的奖励积分,以便它可以theprogram像单击右上角的“x”一样安全退出。当我kill从命令行手动执行它或按 Ctrl-C 时,它无法运行其清理代码。因此,我试图阻止它重新启动。

I checked a few of the other questions about trapping SIGINT, but I couldn't work out how to do this.

我检查了一些关于捕获 SIGINT 的其他问题,但我不知道如何做到这一点。

Can anyone fix this code? My code seems to kill theprogramthen exit the loop when Ctrl-C is pressed, without letting theprogramclean up.

任何人都可以修复此代码吗?我的代码似乎theprogram在按下 Ctrl-C 时杀死然后退出循环,而没有theprogram清理。

#!/bin/bash

EXIT=0
trap exiting SIGINT

exiting() { echo "Ctrl-C trapped, will not restart utorrent" ; EXIT=1;}

while [ $EXIT -eq 0 ] ; do
        wine theprogram
        echo "theprogram killed or finished"
        date
        echo "exit code $?"
        echo "sleeping for 20 seconds, then restarting theprogram..."
        sleep 20
done

echo "out of loop"

回答by Alfe

Try this:

尝试这个:

while true
do
  xterm -e wine theprogram || break
  sleep 3
done

The trick is done by using another xterm to start the wine. That way the wine has a different controlling tty and won't be affected by the Ctrl-cpress.

这个技巧是通过使用另一个 xterm 来启动 wine 来完成的。这样酒就有不同的控制 tty 并且不会受到Ctrl-c印刷机的影响。

The drawback is that there will be an additional xterm lingering around on your desktop. You could use the option -iconicto start it iconified.

缺点是您的桌面上会有一个额外的 xterm。您可以使用该选项-iconic来启动它图标化。

回答by localhost

Well, I ended up not using Ctrl-C as per my question because I couldn't find a good solution, but I used zenityto popup a box that I can click to exit the loop:

好吧,我最终没有按照我的问题使用 Ctrl-C,因为我找不到一个好的解决方案,但我曾经zenity弹出一个框,我可以单击它退出循环:

zenity popup

zenity 弹出窗口

#!/bin/bash
zenity --info --title "thewineprogram" --text "Hit OK to disable thewineprogram auto-restart" &  # run zenity in the background
zen_pid=$!

while : 
do
    wine <wineprogramlocation>
    EXITCODE=$?
    echo "thewineprog killed or finished"
    echo "exit code was $EXITCODE"
    date

    kill -0 $zen_pid > /dev/null 2>&1  # kill -0 just checks if a pid exists
    if [ $? -eq 1 ] # process does not exist
    then
        break
    fi

    echo "sleeping for 5 seconds, then restarting the wine program..."
    sleep 5     
done

echo "finished"

回答by Curtis Yallop

Use a monitoring process:

使用监控流程:

This allows the SIGINT signal to hit the monitor process trap handler without affecting the child.

这允许 SIGINT 信号在不影响子进程的情况下命中监视器进程陷阱处理程序。

(this could also be done in perl, python or any language)

(这也可以用 perl、python 或任何语言完成)

#!/bin/bash

cmd() {
    trap '' INT
    trap 'echo "Signal USR1 received (pid=$BASHPID)"; EXIT=1' USR1
    EXIT=0
    while [ $EXIT -eq 0 ]
    do
        echo "Starting (pid=$BASHPID)..."
        sleep 5 # represents "wine theprogram"
        echo "theprogram killed or finished"
        date
        echo "Exit code $?"
        if [ $EXIT -eq 0 ]; then
            echo "Sleeping for 2 seconds, then restarting theprogram..."
            sleep 2
        fi
    done
    echo "Exiting (pid=$BASHPID)"
}

run() { cmd & PID=$!; echo Started $PID; }
graceful_exit() { kill -s USR1 $PID && echo "$PID signalled to exit (USR1)"; }
shutdown() { kill -0 $PID 2>/dev/null && echo "Unexpected exit, killing $PID" && kill $PID; }

trap 'graceful_exit' INT
trap 'shutdown' EXIT
run

while :
do
    wait && break
done

echo "Exiting monitor process"

回答by Curtis Yallop

It appears that trap on SIGINT must terminate the currently executing sub-command. The only exception appears to be the empty-string handler.

SIGINT 上的陷阱似乎必须终止当前正在执行的子命令。唯一的例外似乎是空字符串处理程序。

To demonstrate this: When ctrl-c is pressed this (trap "" INT;echo 1;sleep 5;echo 2)does not halt the sleep command. However this (trap "echo hi" INT;echo 1;sleep 5;echo 2)does. After this trap handler executes, execution continues on the command that follows, specifically "echo 2". So empty-string as a handler seems to be a special case which does not kill the current sub-command. There seems to be no way to run a handler plus not kill the current sub-command.

为了证明这一点:当按下 ctrl-c 时,这(trap "" INT;echo 1;sleep 5;echo 2)不会停止 sleep 命令。然而这(trap "echo hi" INT;echo 1;sleep 5;echo 2)确实如此。执行此陷阱处理程序后,将继续执行随后的命令,特别是“echo 2”。因此,作为处理程序的空字符串似乎是一种特殊情况,它不会终止当前的子命令。似乎没有办法运行处理程序加上不终止当前子命令。

Why this happens: Shell forks + execs to execute each program. On system call exec, it resets signal handlers to their default behavior (calling process is overwritten so the handlers are gone). Ignored signals are inherited (see "man 2 execve", "man 7 signal" and POSIX.1; http://www.perlmonks.org/?node_id=1198044)

为什么会发生这种情况:Shell fork + execs 来执行每个程序。在系统调用 exec 上,它将信号处理程序重置为其默认行为(调用进程被覆盖,因此处理程序消失)。忽略的信号被继承(参见“man 2 execve”、“man 7 signal”和POSIX.1;http://www.perlmonks.org/?node_id =1198044 )

I had a second idea: use 'trap "" INT' to fully disable ctrl-c and then trap ctrl-z as the signal to gracefully exit your program. Only trapping ctrl-z (STP) seems to not work properly for me. When I run '(trap "echo test" TSTP;sleep 5)' and press ctrl-z, my shell is hung. sleep never completes after 5 seconds and oddly ctrl-c no longer works. I don't know any other hotkey-signals to use other than ctrl-c and ctrl-z. This is known behavior: see Bash script: can not properly handle SIGTSTP.

我有第二个想法:使用 'trap "" INT' 完全禁用 ctrl-c,然后将 ctrl-z 作为信号优雅地退出程序。只有捕获 ctrl-z (STP) 对我来说似乎不能正常工作。当我运行 '(trap "echo test" TSTP;sleep 5)' 并按 ctrl-z 时,我的外壳挂了。sleep 在 5 秒后永远不会完成,奇怪的是 ctrl-c 不再起作用。除了 ctrl-c 和 ctrl-z 之外,我不知道可以使用任何其他热键信号。这是已知行为:请参阅Bash 脚本:无法正确处理 SIGTSTP