这已经困扰我很多年了。尽管我使用现代终端仿真器并且大多数 CLI 程序(例如less
和vim
)都能够充分使用备用屏幕(IE在启动时输入它并在退出时保留它),有些人不愿意这样做。流氓程序包括top
,screen
和dialog
。
当它们启动时,这些程序会清除窗口的内容(IE最后 N 行,其中 N 是终端窗口的高度)。当他们退出时,一些 ( top
) 保留其最后的状态可见,而另一些 ( screen
) 再次清除屏幕。在所有情况下,回滚缓冲区的最后 N 行都已被覆盖。
随着时间的推移,我在各种机器上以及各种终端模拟器中检查了这一点,包括xcfce4-terminal
、urxvt
和screen
(使用 正确启用了其备用屏幕:altscreen on
)。因此,我不认为这是我的终端的问题,我宁愿相信这是这些程序的内置行为(至少未打补丁,因为它们分布在 Archlinux 中)。
所以我的问题是:
- 为什么这些程序会有这样的行为?我想这有充分的理由吗?
我该如何解决这个问题?目前,我正在使用如下所示的愚蠢包装脚本,但也许有一种更干净的方法?
# save this as: ~/bin/top if [ -t 1 ]; then # only use alt screen if output is a terminal tput smcup # toggle alt screen on /usr/bin/top "$@" tput rmcup # toggle alt screen off else /usr/bin/top "$@" fi
答案1
我们可以编写一个简短的 C 程序,而不是尝试使用 shell 脚本换行,它允许我们在程序停止时切换回正常屏幕:
#define _GNU_SOURCE 1
#include <stdbool.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/* Quick hack; todo: use terminfo instead */
#include <stdlib.h>
static void enter_alt_screen(void)
{
system("tput smcup");
}
static void leave_alt_screen(void)
{
system("tput rmcup");
}
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "Usage: %s command args...", argv[0]);
return 1;
}
if (!isatty(fileno(stdout))) {
/* not a terminal; act normally */
execvp(argv[1], argv+1);
}
enter_alt_screen();
const pid_t child = fork();
switch (child) {
case -1:
leave_alt_screen();
perror("fork");
return 1;
case 0:
/* child */
execvp(argv[1], argv+1);
leave_alt_screen();
perror("exec");
return 1;
}
int status;
while (waitpid(child, &status, WCONTINUED) == child) {
if (WIFSTOPPED(status)) {
leave_alt_screen();
} else if (WIFCONTINUED(status)) {
enter_alt_screen();
} else if (WIFSIGNALED(status)) {
leave_alt_screen();
signal(WTERMSIG(status), signal(SIGTERM, SIG_DFL));
raise(WTERMSIG(status));
} else if (WIFEXITED(status)) {
leave_alt_screen();
return WEXITSTATUS(status);
}
}
return 1;
}