How to start XTerm with prompt at the bottom?
Solution 1
If using bash
, the following should do the trick:
TOLASTLINE=$(tput cup "$LINES")
PS1="\[$TOLASTLINE\]$PS1"
Or (less efficient as it runs one tput
command before each prompt, but works after the terminal window has been resized):
PS1='\[$(tput cup "$LINES")\]'$PS1
To prevent tput
from changing the exit code, you can explicitly save and reset it:
PS1='\[$(retval=$?;tput cup "$LINES";exit $retval)\]'$PS1
Note that the variable retval
is local; it doesn't affect any retval
variable you might have defined otherwise in the shell.
Since most terminals cup
capability is the same \e[y;xH
, you could also hardcode it:
PS1='\[\e[$LINES;1H\]'$PS1
If you want it to be safe against later resetting of PS1, you can also utilize the PROMPT_COMMAND
variable. If set, it is run as command before the prompt is output. So the effect can also be achieved by
PROMPT_COMMAND='(retval=$?;tput cup "$LINES";exit $retval)'
Of course, while resetting PS1
won't affect this, some other software might also change PROMPT_COMMAND
.
Solution 2
I have been using @Thomas Dickey's solution of including \[\033[1000H\]
in PS1
for a while and it has been frustrating me ever since as it breaks vi mode editing on multi-line commands, which is something that comes up quite often.
After investigating the problem several times over the past few months, I finally found a trivial change that prevents the issue: instead of including the ANSI escape sequence in PS1
, it should be directly printed to stdout
from PROMPT_COMMAND
(in ~/.bashrc
or another configuration file):
PROMPT_COMMAND=prompt
prompt() {
r="${?}"
printf '\033[1000H'
return "${r}"
}
printf
and return
are both builtins (see builtins(1)
), so no processes are spawned. The exit status of the previous command is saved in r
, cleared with printf
, and restored with return
. If PROMPT_COMMAND
is already being used, this can simply be added to it.
This solution works with multi-line commands, but it has two minor limitations: first, clearing the screen with the clear-screen
readline command (bound to Control-L
by default) causes the prompt to go to the top of the screen, and second, increasing the size of a new terminal leaves the prompt where it was. In both cases, once the prompt is printed (e.g. by pressing enter) it goes back to the bottom of the screen (when bash runs PROMPT_COMMAND
), which is an improvement over a single tput cup 1000
or similar at the start of ~/.bashrc
as suggested by @phil pirozhkov.
Further investigation shows that the clear
program works correctly because it prints terminal escape sequences directly (observable with clear | xxd
or similar), which forces Bash to run PROMPT_COMMAND
again. The clear-screen
readline command, on the other hand, allows readline (used by Bash) to intercept the clear screen request and to simply re-display the previous prompt without rerunning PROMPT_COMMAND
. Since the \033[1000H
escape sequence is no longer in the prompt itself and rather is a side effect of running PROMPT_COMMAND
, the prompt stays where it is.
The most general solution would be to patch Bash to run PROMPT_COMMAND
when the terminal is resized or clear-screen
is run. However, I opted for a more direct solution: patch the clear-screen
command to print \033[1000H
to stdout
directly. For clarification, Bash's default source configuration includes its own copy of readline
, but on my system (Gentoo Linux) the default Bash configuration is to use the system readline
, and patching the system readline
works for any other programs using it as well.
In any case, the simplest solution is to download Bash and apply the following patch with patch -p1
in the Bash source directory:
--- 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 ();
Note that this patch works for readline 8.1, included with Bash 5.1. The patch does not apply to readline 8.0 because it does not include the clrscr
parameter to the _rl_clear_screen
function, but changing the patch for this is straight forward.
While I was at it, I decided to make another improvement: instead of deleting the screen contents with the clear-screen
command, I wanted to scroll the contents up so that I could access them later in my terminal's scroll back buffer. In order to achieve this, the following patch can be applied on top of the previous one:
--- 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);
With this the terminal and shell finally work exactly as I want them to. The only remaining problem is that increasing the size of the terminal before you have entered the first command leaves the prompt where it is, until you enter the first command. However, this is essentially a non-issue. I wrote both of the patches here and I hereby release them into the public domain under the CC0 Public Domain Disclosure (in case someone would like to incorporate them into future projects).
Edit: As requested by @Toby Speight, this can be adapted to other terminals. For PROMPT_COMMAND
, simply save the output of tput cup 1000
in a variable in ~/.bashrc
and print it instead of \033[1000H
, as is done in @Thomas Dickey's answer.
For the patches, I directly embedded the \033[1000H
escape sequence for speed (so as to only print one escape sequence to adjust the cursor position) and because only I was using it. A more portable solution is easily possible with the other readline functions in the same file (display.c
), specifically the _rl_move_vert
function. Unfortunately this is complicated by the fact that Bash/readline does not seem to accurately know its current vertical cursor position (stored in _rl_last_v_pos
) in most cases.
As far as I can tell the current cursor position is not queried upon startup, meaning it doesn't know where on the screen the prompt is starting. The _rl_move_vert
function itself moves down with simply putc ('\n', rl_outstream)
, meaning that if it "moves down" when the cursor is already on the last line of the terminal it will actually scroll the terminal buffer.
The _rl_clear_screen
function also doesn't update _rl_last_v_pos
at all, meaning it doesn't know where the cursor is after the screen is cleared with the clear-screen
command, however this can be fixed (presumably) by setting it to zero in _rl_clear_screen
. readline could presumably be extended to use escape sequences to move the cursor position, similar to its select use of _rl_term_clrpag
(to clear the screen) and other related escape sequences. However, I'm unsure of how exactly this works and where these escape sequences are initialized.
In any case, I wrote a new patch using the _rl_move_vert
function that still works. It's different from the previous patch in that the scroll back saving doesn't always scroll a full page, and instead only scrolls as much as it needs to (which may be desirable) -- at least, I think that's what it does:
--- 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 ();
Solution 3
The answers using $LINES
are unnecessarily non-portable. As done in resize
, you can simply ask xterm
to set the position to an arbitrarily large line number, e.g.,
tput cup 9999 0
(assuming that you have a window smaller than 10 thousand lines, disregarding scrollback).
Because the string will not change as a side-effect of resizing the window, you can compute this once, and paste it into your prompt-string as a constant, e.g.,
TPUT_END=$(tput cup 9999 0)
and later
PS1="${TPUT_END} myprompt: "
according to your preferences.
As for other processes modifying PS1
: you will have to recompute PS1
after those changes, to ensure that it looks as you want. But there's not enough detail in the question to point out where to make the changes.
And finally: the behavior for tab-completion doesn't mesh with this sort of change, due to bash's assumptions.
l0b0
Author, The newline Guide to Bash Scripting (https://www.newline.co/courses/newline-guide-to-bash-scripting). Hobby (https://gitlab.com/victor-engmark) & work software developer.
Updated on September 18, 2022Comments
-
l0b0 almost 2 years
When starting XTerm the prompt starts at the first line of the terminal. When running commands the prompt moves down until it reaches the bottom, and from then on it stays there (not even Shift-Page Down or the mouse can change this). Rather than have the start of the terminal lifetime be "special" the prompt should always be at the bottom of the terminal. Please note that I have a multi-line prompt.
Of course, it should otherwise work as before (resizeable, scrollable, no unnecessary newlines in the output, and no output mysteriously disappearing), so
PROMPT_COMMAND='echo;echo;...'
or similar is not an option. The solution ideally should not be shell-specific.Edit: The current solution, while working in simple cases, has a few issues:
- It's Bash specific. An ideal solution should be portable to other shells.
- It fails if other processes modify
PS1
. One example is virtualenv, which adds(virtualenv)
at the start ofPS1
, which then always disappears just above the fold. - Ctrl-l now removes the last page of history.
Is there a way to avoid these issues, short of forking XTerm?
-
SHW almost 10 yearsHow
tput
differ from manyecho
commands ? (Asking out of curiosity) -
Stéphane Chazelas almost 10 years@l0b0, probably doesn't deserve a separate answer. I hope celtschk won't mind I edited his/her answer.
-
l0b0 almost 10 years
'\[$(tput cup "$LINES")\]'
works beautifully. Thanks! -
l0b0 almost 10 years
-
celtschk almost 10 years@l0b0: I've now added a version using
tput
that preserves the exit code. -
Eric Hodonsky about 8 yearsThis is not working when I put it in my .bashrc file... :/
-
celtschk almost 8 years@Relic: Maybe something else changes
PS1
later. I've now added another alternative independent ofPS1
. -
l0b0 almost 8 yearsWhat do you mean by "the behavior for tab-completion doesn't mesh with this sort of change"?
-
l0b0 almost 8 yearsI think you mean
PS1="${TPUT_END} myprompt: "
, or evenPS1="${TPUT_END}${PS1}"
-
Marius almost 8 yearsFor the latter - right (typo from thinking of makefiles). For former, I have in mind that bash's command-editing relies on being able to reprint the line (with prompt), and that you can get some odd behavior when this combines with scrolling due to command-completion.
-
l0b0 over 2 yearsWow, fantastic work! Would you be willing to submit your patches to the readline project? I'd be super stoked to have this as an actual built-in feature, even if it ends up being behind a feature flag.
-
user17549713 over 2 yearsI'm all for it, but they would have to be configured by user options from
inputrc
since the default top terminal should remain, and that's more work than I'm interested in at the moment. As they are released into the public domain anyone is welcome to do so, however, or to send a link to this answer to their mailing list. For the time being, downloading Bash and applying the patches to its builtin readline is still easy enough even for people who aren't experienced with it. Anyways, thanks for the appreciation! -
l0b0 over 2 yearsThanks for the feedback! I've suggested it to the readline maintainer directly, as I couldn't find any relevant mailing lists.
-
Toby Speight over 2 yearsCan this be made so that it adapts properly to
$TERM
, rather than assuming the whole world is a VT-220 (or XTerm, or whatever)? It's bloody irritating to get a screen filled with^[
crap because someone assumed that all terminals are identical... -
user17549713 over 2 years@TobySpeight At the risk of making an unnecessarily detailed answer even more unnecessarily detailed, I added a brand spanking new edit section hot off the shelves including a new version of the patch that will probably work for any terminal.