11

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 of PS1, 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?

  • Somehow, we need to introduce blank characters in Xterm's scrollbar buffer. – SHW Sep 1 '14 at 8:31
  • 3
    Actually, the prompt can be easily moved back to the top at any time by executing clear command. – werediver Sep 1 '14 at 8:49
  • @SHW I was hoping there was a setting for this rather than a hack. Terminal hacks have a tendency to introduce very subtle bugs in my experience. – l0b0 Sep 1 '14 at 11:37
  • @werediver But I never want it to be on top. – l0b0 Sep 1 '14 at 11:38
  • 1
    Since only the shell knows when it outputs a prompt, any solution must be in the context of the shell. Even forking XTerm won't help because XTerm doesn't know whether what it is asked to output is a prompt or not. For the terminal, the shell prompt is just another character sequence, no different from any other character sequence it might receive. – celtschk Sep 12 '14 at 15:13
11

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.

  • How tput differ from many echo commands ? (Asking out of curiosity) – SHW Sep 1 '14 at 11:40
  • @l0b0, probably doesn't deserve a separate answer. I hope celtschk won't mind I edited his/her answer. – Stéphane Chazelas Sep 1 '14 at 11:45
  • '\[$(tput cup "$LINES")\]' works beautifully. Thanks! – l0b0 Sep 4 '14 at 7:37
  • There is an issue with tput: It seems to reset $exit_code. Fixed by using \[\e[$LINES;1H\]. – l0b0 Sep 8 '14 at 22:40
  • 1
    @l0b0: I've now added a version using tput that preserves the exit code. – celtschk Sep 11 '14 at 17:28
4

As a slight simplification to the previous answer, I found it easier to just run:

tput cup $LINES

in the beginning of .bashrc or .zshrc. It just does the job.

Pros:

  • it only prints once, when you start your shell

Cons:

  • when clearing screen with ^L, it doesn't print and aliasing clear to clear; tput ... doesn't help;
  • prompt moves elsewhere when terminal is resized
2

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.

  • What do you mean by "the behavior for tab-completion doesn't mesh with this sort of change"? – l0b0 Jul 28 '16 at 10:14
  • I think you mean PS1="${TPUT_END} myprompt: ", or even PS1="${TPUT_END}${PS1}" – l0b0 Jul 28 '16 at 10:15
  • For 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. – Thomas Dickey Jul 28 '16 at 22:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.