我使用less
来捕获实时程序的输出。使用F
可激活跟随模式,显示实时输出。但是,进入跟随模式后(或者更一般地说,滚动浏览缓冲输出,例如使用G
),我无法less
再进行控制。
Ctrl-c
在这里可以工作,但不幸的是它会杀死其他程序。
less
滚动过缓冲区后,有没有办法重新获得控制权?
答案1
简单情况
在我的 Debian 9 中,如果我运行less
读取常规文件,例如:
less /etc/fstab
然后通过F
(即Shift+ F)然后我将看到
Waiting for data... (interrupt to abort)
这表明中断在设计上是“撤消”的正确方法F
。Ctrl+确实C做到了这一点。
你的情况
我正在使用它
less
来捕获实时程序的输出。
我猜是这样的:
program1 | less
# or
program1 | program2 | … | less
现在问题出现了(Ctrl+C杀死了另一个程序),因为发生了以下情况:
- shell 将管道的每个部分(即
less
和每个programN
)放置在一个进程组中,其 PGID(进程组 ID)等于第一个命令(即 )的 PID(进程 ID)program1
。 - shell 通知终端此 PGID 现在是前台进程组。
- 当您点击Ctrl+时C,终端会发送
SIGINT
到前台进程组。 - 有效
less
和每次programN
收到SIGINT
。
一些非常基本的 shell(例如posh
)可能会运行一切在 shell 本身的进程组中。这不会影响问题(less
和每次programN
接收SIGINT
),但可能会使某些解决方案无效。您的 shell 很可能“足够复杂”。在我的 Debian 中,甚至sh
来自也busybox
足够复杂。
解决方案
您想要less
且less
仅能收到SIGINT
。可能性:
SIGINT
仅发送至less
:kill -s SIGINT $pid_of_less
其中$pid_of_less
表示正确less
进程的 PID(您需要以某种方式找到它)。如果您确定less
只有正确进程才less
允许您发送信号,或者您不介意向其他less
进程发送信号,那么这是最简单的方法:
killall -s SIGINT less
此解决方案应该始终有效。它不需要您事先修改管道。
您可以在单独的 shell(单独的控制台)中运行kill
(或)或者(如果您的 shell 允许这样做)使用+killall
暂停管道,运行您需要的任何内容,最后使用 将管道带回前台。CtrlZfg
使一切都
less
不受免疫SIGINT
。几种可能性:sh -c 'trap "" SIGINT; program1' | less sh -c 'trap "" SIGINT; exec program1' | less sh -c 'trap "" SIGINT; program1 | program2 | …' | less ( trap "" SIGINT; program1 ) | less ( trap "" SIGINT; exec program1 ) | less ( trap "" SIGINT; program1 | program2 | … ) | less
注意事项和怪癖:
示例
sh -c …
需要额外的引号。如果原始行中有引号,则可能会不方便。如果主 shell 中有需要扩展的内容,则单引号不能包含它 (外部引号很重要)可能会变得复杂。sh -c …
需要与 兼容的语法的示例sh
。如果您的原始行使用了sh
无法理解的语法或工具(如内置函数),那么您应该考虑其他可能性。带有子 shell 的示例 (
( … )
) 使用您使用的任何 shell。它很可能会理解trap "" SIGINT
并按预期工作,但通常可能不会。每个都
programN
可以单独注册一个处理程序SIGINT
。注意less
,我们甚至可以从忽略的 shell 启动它SIGINT
:( trap "" SIGINT; program1 | less ) # similarly with other examples
并且它仍然能够对Ctrl+做出反应C。但如果另一个程序再次使自己易受攻击,那么它的目的就失败了。
programN
当您退出时,Any可能不会终止less
。这可能是因为- 它还不知道它写入的管道已经关闭,因为自从管道关闭以来它还没有尝试写入任何东西,所以
SIGPIPE
还没有生成(这是正常的,比较我的这个答案(英文): - 或者根据设计它不会退出
SIGPIPE
; - 或者它是一个循环,运行在 上退出的命令
SIGPIPE
,但循环并不关心并一遍又一遍地运行它们。
在这种情况下,最简单的恢复方法是按Ctrl+ C,但显然如果你
programN
对它免疫,SIGINT
那么这对它不起作用。你需要用SIGTERM
或其他信号杀死它。请注意,只杀死子 shell 或附加sh
shell 不会杀死其子 shell。向 (子) shell 的整个进程组发出信号是一种好方法,如下所示:kill -- -$pgid
其中
$pgid
表示正确的进程组ID(并且在指示目标组而不仅仅是一个进程-
之前)。如果子shell首先在管道中,则将是(子)shell的PID。kill
$pgid
如果在交互式 shell 中启用了作业控制,则无需知道任何 ID。例如,在 Bash 中按Ctrl+Z然后按
kill %+
。这应该会终止整个作业。- 它还不知道它写入的管道已经关闭,因为自从管道关闭以来它还没有尝试写入任何东西,所以
将所有内容放置
less
在前台进程组之外。和
setsid
:setsid -w program1 | less # or setsid -w sh -c 'program1 | program2 | …' | less
和以前一样,any在退出
programN
时可能不会终止less
。和以前一样,如果发生这种情况,您将需要使用信号将其终止。不同之处在于这次SIGINT
可以是信号。另一方面,技巧kill %-
将不起作用。使用进程替换(如果您的 shell 支持)。我将详细说明 Bash。
# in Bash less -f <(program1) # or less -f <(program1 | program2 | …)
不幸的是,当您退出时,任何进程
programN
都可能无法终止less
。但请注意, 中的任何进程都<( )
在原始 shell 的进程组中启动,因此,只要 shell 将其自己的进程组再次注册为前台组,您就可以使用+将其发送SIGINT
给它。在脚本中,这可能是一个问题,但在交互式 shell 中,在退出后收到提示时按+就足够了。是的,在这种情况下, +将发送给一些似乎在后台工作的进程(但它们属于当前前台进程组,因此正式来说它们在前台)。CtrlCCtrlCless
CtrlCSIGINT
您可以自动执行此操作:将向其自己的进程组发出信号,因此只需在 shell 的进程组中
kill 0
运行即可,这非常容易。就像这样:kill
# in Bash less -f <(program1); kill -s SIGINT 0 # or less -f <(program1 | program2 | …); kill -s SIGINT 0
上述解决方案非常优雅。请记住,
kill
此处仅用于指示否则会保留的程序。如果您的程序自行退出,则您可能kill
根本无法运行。另一方面,如果有其他shell 进程组中的程序(例如你所做的exec 3< <(whatever)
)kill 0
也会向它们发出信号。关于 Zsh 的注意事项:我的测试表明,在 Zsh 中你可以使用
less -f <( … )
并阻止Ctrl+C发送SIGINT
给程序,就像在 Bash 中一样;但 Zsh 会在完后还有进程组(而不是 shell 的进程组),因此用的技巧kill 0
将不起作用。还请注意这将不是工作:
# not a solution less < <(program1) # or less < <(program1 | program2 | …)
因为这一次里面的所有内容都
<( )
将正式从其进程组降级less
并共享,该进程组将是前台进程组,它将SIGINT
在Ctrl+时接收C。之所以会从其正式降级,less
是因为less
最初是作为子 shell 启动的,它为自己设置重定向,然后才用其替换自身less
(这是 shell 运行程序的正常方式)。因此,在进程树中,它将显示为less
已生成 shell,该 shell 已生成program1
等(比较这个问题在哪里sshd
据称生成sleep
)。虽然在这种情况下Ctrl+C可以到达 each
programN
,但是当您不使用+programN
退出时 any 可能仍不会终止。那么 with 的技巧将不起作用。完全不是解决方案。less
CtrlCkill 0
使用单独的调用。
less
以某种方式单独调用,使其成为前台进程组中的唯一命令,并且其他任何进程都不会SIGINT
在Ctrl+时接收C。您需要一个命名管道或常规文件来将数据从 传递program1 …
到less
。创建文件:
mkfifo myfile # of the type named fifo # or : > myfile # of the type regular file
运行
program1 …
并让其写入文件
在单独的 shell 或单独的终端中(不是您要运行的终端
less
):program1 … >> myfile
或者在你要运行的终端的后台
less
(注意,对于运行一切在 shell 本身的进程组中 - 还记得吗,真的是基本的 shell:program1 … >> myfile &
我使用了
>>
,而不是>
。对于 fifo 来说,这无关紧要。对于常规文件,它允许您稍后截断文件而不会出现问题(比较这个答案)。
跑步
less
:less -f myfile # or less < myfile
(
-f
如果是常规文件则不需要)。现在此终端中的Ctrl+C只会影响
less
。如果你退出
less
,其余部分仍在运行,并且你希望它停止,那么
- 如果它在单独的终端中运行,Ctrl+C在那里;
- 如果它在同一个终端的后台运行: -
fg
,则Ctrl+ C, - 或kill -s SIGINT %+
,如果支持(如果需要,指定不同的信号), - 或kill -s SIGINT -- -"$!"
(如果需要,指定不同的信号)。
删除文件:
rm myfile
笔记
program1
在后台或内部运行<( )
会将其标准输入与终端分离。如果这会产生问题,less
那么当程序尝试同时从终端读取数据时,您就会遇到问题。您报告没有问题(或者已经处理并program1
从终端分离),因此显然程序不会从终端读取数据。因此在后台或内部运行<( )
不会破坏任何东西。- 在我的测试中
less
,在成功“撤消”F
而不终止任何操作(任何解决方案都适用)后,最终会停止读取数据。这会阻塞管道的其余部分(通常;写入programN
可能设计为不等待,它可能会退出或其他)。这是意料之中的,我提到这一点是为了经验不足的用户。要解除阻塞,我需要F
再次通过。但是如果我使用常规文件作为和之间的接口programN
,less
那么程序就不会因为less
(文件系统可能会变满)而阻塞。