启动 XTerm 时,提示符从终端的第一行开始。当运行命令时,提示符向下移动直到到达底部,从那时起它就停留在那里(甚至不会Shift-Page Down或者鼠标可以改变这一点)。而不是让终端生命周期的开始变得“特殊”提示应始终位于终端的底部。请注意,我有一个多行提示。
当然,它应该像以前一样工作(可调整大小,可滚动,输出中没有不必要的换行符,并且没有输出神秘消失),所以PROMPT_COMMAND='echo;echo;...'
或类似不是一个选项。理想情况下,解决方案不应特定于 shell。
编辑:这当前解决方案在处理简单情况时,存在一些问题:
- 它是Bash 特定的。理想的解决方案应该可移植到其他 shell。
- 它如果其他进程修改则失败
PS1
。一个例子是 virtualenv,它添加(virtualenv)
到开始的PS1
,然后它总是在折叠上方消失。 - Ctrl-l现在删除历史的最后一页。
除了分叉 XTerm 之外,有没有办法避免这些问题?
答案1
如果使用bash
,以下应该可以解决问题:
TOLASTLINE=$(tput cup "$LINES")
PS1="\[$TOLASTLINE\]$PS1"
或者(效率较低,因为它tput
在每次提示之前运行一个命令,但在调整终端窗口大小后运行):
PS1='\[$(tput cup "$LINES")\]'$PS1
为了防止tput
更改退出代码,您可以显式保存并重置它:
PS1='\[$(retval=$?;tput cup "$LINES";exit $retval)\]'$PS1
请注意,该变量retval
是局部变量;它不会影响retval
您在 shell 中以其他方式定义的任何变量。
由于大多数终端cup
功能是相同的\e[y;xH
,您也可以对其进行硬编码:
PS1='\[\e[$LINES;1H\]'$PS1
如果您希望它能够安全地防止以后重置 PS1,您也可以使用该PROMPT_COMMAND
变量。如果设置,它将作为命令运行前输出提示符。所以也可以达到这样的效果
PROMPT_COMMAND='(retval=$?;tput cup "$LINES";exit $retval)'
当然,虽然重置PS1
不会影响这一点,但其他一些软件也可能会改变PROMPT_COMMAND
。
答案2
作为对之前答案的稍微简化,我发现运行更容易:
tput cup $LINES
.bashrc
在或的开头.zshrc
。它只是完成工作。
优点:
- 当你启动 shell 时,它只打印一次
缺点:
- 当使用^L清除屏幕时,它不会打印并且别名
clear
没有clear; tput ...
帮助; - 调整终端大小时提示移动到其他地方
答案3
我已经使用 @Thomas Dickey 的 include \[\033[1000H\]
in解决方案PS1
有一段时间了,从那以后它一直让我感到沮丧,因为它破坏了多行命令的 vi 模式编辑,这是经常出现的情况。
在过去几个月多次调查该问题后,我终于发现了一个可以防止该问题的微小更改:不应将 ANSI 转义序列包含在 中PS1
,而应直接打印到stdout
from PROMPT_COMMAND
(在~/.bashrc
或另一个配置文件中):
PROMPT_COMMAND=prompt
prompt() {
r="${?}"
printf '\033[1000H'
return "${r}"
}
printf
和return
都是内置函数(请参阅 参考资料builtins(1)
),因此不会生成任何进程。上一个命令的退出状态保存在 中r
,用 清除printf
,用 恢复return
。如果PROMPT_COMMAND
已经在使用,则可以简单地将其添加到其中。
clear-screen
该解决方案适用于多行命令,但它有两个小限制:首先,使用readline 命令(默认情况下绑定)清除屏幕Control-L
会导致提示符转到屏幕顶部,其次,增加新终端将提示符保留在原来的位置。在这两种情况下,一旦打印提示(例如按 Enter 键),它就会返回到屏幕底部(当 bash 运行时),这比@phil pirozhkov 建议的PROMPT_COMMAND
单个tput cup 1000
或类似的开头有所改进。~/.bashrc
进一步的调查表明该clear
程序工作正常,因为它直接打印终端转义序列(可通过clear | xxd
或类似的方式观察),这迫使 Bash 再次运行PROMPT_COMMAND
。clear-screen
另一方面,readline 命令允许 readline(由 Bash 使用)拦截清屏请求并简单地重新显示先前的提示符,而无需重新运行PROMPT_COMMAND
。由于\033[1000H
转义序列不再位于提示符本身中,而是 running 的副作用PROMPT_COMMAND
,因此提示符保持在原来的位置。
最通用的解决方案是修补 Bash,以便PROMPT_COMMAND
在调整终端大小或clear-screen
运行时运行。但是,我选择了更直接的解决方案:修补命令以直接clear-screen
打印。需要澄清的是,Bash 的默认源配置包括它自己的 副本,但在我的系统(Gentoo Linux)上,默认的 Bash 配置是使用系统,并且修补系统也适用于使用它的任何其他程序。\033[1000H
stdout
readline
readline
readline
无论如何,最简单的解决方案是下载 Bash 并patch -p1
在 Bash 源目录中应用以下补丁:
--- a/lib/readline/display.c 2021-12-20 10:00:33.370809888 +0000
+++ b/lib/readline/display.c 2021-12-20 09:59:14.920808045 +0000
@@ -3186,11 +3186,17 @@
ScreenClear ();
ScreenSetCursor (0, 0);
#else
+ static char const cup[]={'\033', '[', '1', '0', '0', '0', 'H'};
+ size_t i;
+
if (_rl_term_clrpag)
{
tputs (_rl_term_clrpag, 1, _rl_output_character_function);
if (clrscr && _rl_term_clrscroll)
tputs (_rl_term_clrscroll, 1, _rl_output_character_function);
+
+ for (i=0; i < sizeof(cup); i++)
+ putc (cup[i], rl_outstream);
}
else
rl_crlf ();
请注意,此补丁适用于 Bash 5.1 中包含的 readline 8.1。该补丁不适用于 readline 8.0,因为它不包含函数clrscr
的参数_rl_clear_screen
,但为此更改补丁很简单。
当我这样做时,我决定进行另一项改进:clear-screen
我不想使用命令删除屏幕内容,而是想向上滚动内容,以便稍后可以在终端的回滚缓冲区中访问它们。为了实现这一目标,可以在前一个补丁的基础上应用以下补丁:
--- a/lib/readline/display.c 2021-12-20 23:48:23.253322032 +0000
+++ b/lib/readline/display.c 2021-12-20 23:48:41.813321757 +0000
@@ -3191,6 +3191,15 @@
if (_rl_term_clrpag)
{
+ if (!clrscr)
+ {
+ for (i=0; i < sizeof(cup); i++)
+ putc (cup[i], rl_outstream);
+
+ for (i=0; i < _rl_screenheight; i++)
+ putc ('\n', rl_outstream);
+ }
+
tputs (_rl_term_clrpag, 1, _rl_output_character_function);
if (clrscr && _rl_term_clrscroll)
tputs (_rl_term_clrscroll, 1, _rl_output_character_function);
有了这个,终端和 shell 最终完全按照我想要的方式工作。唯一剩下的问题是,在输入第一个命令之前增加终端的大小会使提示符保留在原来的位置,直到您输入第一个命令。然而,这本质上不是问题。我在这里编写了这两个补丁,并特此根据 CC0 公共领域披露将它们发布到公共领域(以防有人想将它们合并到未来的项目中)。
编辑:根据@Toby Speight 的要求,这可以适应其他终端。对于PROMPT_COMMAND
,只需将 的输出保存tput cup 1000
在变量 in 中~/.bashrc
并打印它\033[1000H
,而不是像 中那样@托马斯·迪基的回答。
对于补丁,我直接嵌入了\033[1000H
转义序列以提高速度(以便仅打印一个转义序列来调整光标位置)并且因为只有我在使用它。使用同一文件中的其他 readline 函数可以轻松实现更可移植的解决方案(display.c
),特别是_rl_move_vert
功能。不幸的是,由于 Bash/readline 似乎无法准确地知道其当前垂直光标位置(存储在_rl_last_v_pos
)在多数情况下。
据我所知,启动时不会查询当前光标位置,这意味着它不知道提示在屏幕上的哪个位置开始。该_rl_move_vert
函数本身简单地向下移动putc ('\n', rl_outstream)
,这意味着如果当光标已经位于终端的最后一行时它“向下移动”,它实际上会滚动终端缓冲区。
这_rl_clear_screen
功能也根本不更新_rl_last_v_pos
,这意味着在使用命令清除屏幕后它不知道光标在哪里clear-screen
,但是可以通过在_rl_clear_screen
. readline 大概可以扩展为使用转义序列来移动光标位置,类似于它的选择使用_rl_term_clrpag
(清除屏幕)和其他相关转义序列。但是,我不确定它到底是如何工作的以及这些转义序列是在哪里初始化的。
无论如何,我使用_rl_move_vert
仍然有效的功能编写了一个新补丁。它与之前的补丁不同,因为向后滚动保存并不总是滚动整页,而是仅滚动所需的量(这可能是可取的)——至少,我认为这就是它的作用:
--- a/lib/readline/display.c 2021-12-21 09:57:55.932774006 +0000
+++ b/lib/readline/display.c 2021-12-21 09:19:59.109474783 +0000
@@ -3186,11 +3186,25 @@
ScreenClear ();
ScreenSetCursor (0, 0);
#else
+ int i;
+
if (_rl_term_clrpag)
{
+ if (!clrscr)
+ {
+ i = _rl_screenheight-_rl_last_v_pos;
+ _rl_move_vert(_rl_screenheight);
+
+ for (; i < _rl_screenheight; i++)
+ putc ('\n', rl_outstream);
+ }
+
tputs (_rl_term_clrpag, 1, _rl_output_character_function);
if (clrscr && _rl_term_clrscroll)
tputs (_rl_term_clrscroll, 1, _rl_output_character_function);
+
+ _rl_last_v_pos = 0;
+ _rl_move_vert(_rl_screenheight);
}
else
rl_crlf ();
答案4
使用的答案$LINES
不必要地不可移植。正如所做的那样resize
,你可以简单地问xterm
将位置设置为任意大的行号,例如,
tput cup 9999 0
(假设你的窗口小于 10000 行,忽略回滚)。
因为调整窗口大小时字符串不会发生变化,所以您可以计算一次,然后将其作为常量粘贴到提示字符串中,例如,
TPUT_END=$(tput cup 9999 0)
然后
PS1="${TPUT_END} myprompt: "
根据您的喜好。
至于其他进程修改PS1
:您将必须PS1
在这些更改后重新计算,以确保它看起来像您想要的那样。但问题中没有足够的细节来指出在哪里进行更改。
最后:由于 bash 的假设,制表符补全的行为与此类更改并不相符。