从另一个终端控制脚本后无法按 Ctrl-C 组合键

从另一个终端控制脚本后无法按 Ctrl-C 组合键

我正在blah.sh一个终端中运行。然后在另一个终端中,我正在运行一个暂停并稍后继续的脚本blah.sh

...

script_id=`pidof -x blah.sh`
kill -s SIGSTOP $script_id

...

script_id=`pidof -x blah.sh`
kill -s SIGCONT $script_id

之后,blah.sh被推入后台;我必须fg在终端中输入才能用Ctrl-停止它C

有什么方法可以blah.sh在发送后自动在终端中前台显示SIGCONT吗?

答案1

有什么方法可以blah.sh在发送后自动在终端中前台显示SIGCONT吗?

根据您SIGSTOP首先需要什么,您可能会也可能不会实现您想要的。


分析

这就是我认为在你的情况下会发生的情况:

  1. 作业控制在您启动的 shell 中启用blah.sh。 shell 在单独的进程组中启动进程,并通知终端进程组现在是前台进程组。这样,shell 将自身置于后台,而进程则在前台运行。

  2. 当您kill -s STOP执行该进程时(通过在另一个终端中键入),该进程将停止并且 shellSIGCHLD自动接收。这一信号意味着“至少您的一个孩子已被阻止或终止;是时候检查您的孩子并做出反应了”。

  3. shell 做出反应SIGCHLD,它看到子进程停止了,因此它通过将自己的进程组设置为终端的前台进程组来将自己置于前台。请注意,如果您发送SIGTSTP而不是发送,这就是将 shell 置于前台的确切机制SIGSTOP。例如,如果您在进程运行的终端中按Ctrl+,则前台进程组将收到;这意味着进程而不是外壳;它会导致外壳发生反应。ZSIGTSTPSIGCHLD

  4. shell 现在位于前台,它会打印提示符并等待您的输入。

  5. 当您kill -s CONT执行该过程时,该过程将继续。没有类似的机制SIGCHLD会导致外壳发生反应。 shell 不知道并且不会更改终端的前台进程组。实际上,shell 仍然正式处于前台,进程仍然正式处于后台。

人们可能认为忽略提示并停止与 shell 交互就足够了。一般来说:没有。如果后台进程SIGTTIN尝试从终端(很可能是其标准输入)读取数据,它将接收消息。根据终端 ( stty tostop) 的设置,如果后台进程SIGTTOU尝试写入终端(很可能是其 stdout 和 stderr),它会或不会接收。SIGTTIN或 的默认操作SIGTTOU是停止该进程。此外,即使进程在尝试读取时没有停止,外壳仍然从同一终端读取,因此由于外壳“窃取”输入,与进程交互会很困难。

Ctrl+C使终端驱动程序发送SIGINT到前台进程组。如果相关进程位于后台,那么它将不会收到信号。

键入fg(而不是SIGCONT从另一个终端发送)效果很好,因为它fg是 shell 内置的,所以实际上是 shell 处理fg.现在 shell 知道您想要将进程置于前台,因此它会改变自己的行为(停止从终端读取)并相应地设置前台进程组,然后再发送SIGCONT到进程的进程组。

如果您停止进程的原因是希望与 shell 交互,那么您应该通过与 shell 交互(即通过调用 shell)来恢复该进程fg


可能的解决方案

然而如果您停止进程的原因与 shell 无关(例如,您只是希望进程停止计算),那么您应该更改启动进程的方式:在 shell 中禁用作业控制。这就是将要发生的事情:

  1. 你跑set +m; blah.sh set +m禁用作业控制。当shell启动时blah.sh,它会将进程放入shell的进程组中。该进程组现在是、将来也将一直是前台进程组。

  2. 当您kill -s STOP执行该进程时(通过在另一个终端中键入),该进程将停止并且 shellSIGCHLD自动接收。

  3. 外壳对 发生反应SIGCHLD。它想知道子进程是否已终止以及是否该恢复与用户的交互。 shell 看到子进程停止了,而不是终止了;并且由于作业控制被禁用,因此 shell 在这些情况下不会恢复与用户的交互。

  4. shell 和进程都处于前台。shell 不会尝试读取,也不会尝试写入;进程已停止。它们只是待在那里。

  5. 当您kill -s CONT执行该过程时,该过程将继续。 shell 不知道,但这并不重要,因为它只是等待进程最终终止,没有其他事情可做。


笔记

  • 从技术上讲,该解决方案不是“blah.sh在终端中自动前台的方法”。blah.sh始终处于前台,无需“自动前台”它。

  • 因为blah.sh它一直在前台,所以即使它当时停止了,Ctrl+C也会发送给它。 SIGTERMStoppedblah.sh只会SIGTERM在收到后才会做出反应SIGCONT。请注意,发送SIGSTOP到soleblah.sh并不意味着发送SIGSTOP到其后代,后代很可能会立即对Ctrl+做出反应C

  • 由于发送SIGSTOP到soleblah.sh并不意味着发送SIGSTOP到其后代,您可以考虑发送SIGSTOP(然后SIGCONT)到整个进程组blah.sh所在的进程组。每个后代也将在该组中,除非由于某种原因故意将其放入另一个进程组中。请参阅man 1 kill哪里说“负 PID 值可用于选择整个过程组”。

    通常(我的意思是当 shell 中的作业控制启用时)您应该期望blah.sh及其后代位于 PGID 等于 PID 的进程组中blah.sh。在我们的解决方案中(当禁用作业控制时),您应该期望blah.sh及其后代与 shell 位于同一进程组中; PGID 可能等于也可能不等于 shell 的 PID。

  • set +m; blah.sh; set -m如果您想在退出后自动重新启用作业控制blah.sh,看起来很有希望,但如果您在运行时使用Ctrl+中断此操作,则不会执行。有些陷阱可能可以可靠地处理这种情况,但我不会详细说明。Cblah.shset -m

  • 当作业控制被禁用时,shell 会忽略SIGTSTP.由 shell 启动的进程(例如blah.sh)也会忽略它;他们的后代也会忽略它(除非某些进程故意选择不忽略)。这意味着您不应该期望Ctrl+Z能够工作。

答案2

没有办法做到这一点。只有shell可以管理前台应用程序。考虑这样一种情况,您的 shell 有几个应用程序在后台运行,并且您从另一个终端发送SIGCONT到其中几个应用程序。您预计会发生什么?

如果您的唯一目的是删除一个步骤,那么您可以只运行 ,而不是先运行kill -s SIGCONT $script_id然后运行​​。已经发送到后台进程,无需使用命令显式发送信号。fgfgfgSIGCONTkill

相关内容