我有一个 Go 程序,它消耗来自 shell 管道的“实时”输入,例如:
tail -f some/file | my-program
my-program
是一个使用 rivo/tview
构建的交互式程序。我希望能够使用 Ctrl-C 关闭我的程序,并让它终止向其提供输入的 tail -f
。
目前,我必须按 Ctrl-C 两次才能返回 shell 提示符。有什么办法可以通过按 Ctrl-C 一次返回提示符吗?
根据@torek's explanation of progress groups调整了我的程序和 observation that I can get the progress group ID using unix.Getpgid(pid)
:
import (
"os"
"golang.org/x/sys/unix"
)
func main() {
// do stuff with piped input
pid := os.Getpid()
pgid, err := unix.Getpgid(pid)
if err != nil {
log.Fatalf("could not get process group id for pid: %v\n", pid)
}
processGroup, err := os.FindProcess(pgid)
if err != nil {
log.Fatalf("could not find process for pid: %v\n", pgid)
}
processGroup.Signal(os.Interrupt)
}
这从我最初的问题中得到了我想要的行为。
由于warning I found,我选择不使用syscall
:
Deprecated: this package is locked down. Callers should use the corresponding package in the golang.org/x/sys repository instead. That is also where updates required by new systems or versions should be applied. See https://golang.org/s/go1.4-syscall for more information.
我计划更新我的程序以检测是否使用 strategy outlined in this article 为其提供了管道。 ,因此当检测到管道时,我将在中断时执行上述进程组信号。
有什么问题吗?
最佳答案
我们将假设一个类 Unix 系统,使用一个能够理解并参与作业控制的 shell(现在它们都这样做了)。当您运行命令时,shell 会创建一个称为进程组或“pgroup”的东西来保存组成该命令的每个进程。如果命令是一个管道(就像这个),管道中的每个进程都会获得相同的 pgroup-ID(请参阅 setpgid
)。
如果命令在forgeground中运行(没有 &
),控制终端就会分配给它这个特定的 pgid 。按信号生成键之一,例如 CTRL-C 或 CTRL-\,将相应的信号(在这些情况下为 SIGINT
和 SIGQUIT
)发送到pgroup,使用内部 killpg
或等效的。这会将信号发送给 pgroup 的每个成员。
(后台进程只是*咳嗽*收回控制tty上的pgid,然后重新启动管道中的进程。不过要实现这一点并不那么简单,如此处的“重新启动”所示。)
这里问题的可能根源是交互式程序会将控制终端置于cbreak或raw模式并禁用某些或所有来自键盘按键的信号,例如,CTRL-C 不再导致内核的 tty 模块发送信号。相反,如果您看到某个键应该导致挂起 (CTRL-Z) 或终止,则程序必须自行挂起或终止。程序员有时会认为这只是暂停或终止,但由于整个管道从未收到有问题的信号,所以情况并非如此,除非整个 shell 管道仅由交互式程序组成。 p>
解决方法是让程序在对控制终端进行任何必要的清理(临时或永久)后,将信号发送到其自己的 pgroup。
关于shell - 从交互式 go cli 终止 shell 管道,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65515667/