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