How can I make `clear` preserve entire scrollback buffer?
Solution 1:
Analysis
You call the behavior "weird" but it's by design. clear
does not move text out of the screen, it actually clears what's on the screen. Strictly it only tells the terminal (terminal emulator) to do it.
In some terminals clear
effectively clears the entire scrollback buffer. At least clear
in my Kubuntu does it; with -x
I can ask it not to. See man 1 clear
.
My $TERM
is xterm-256color
and this is how clear
works for me:
$ clear | od -c
0000000 033 [ H 033 [ 2 J 033 [ 3 J
0000013
$ clear -x | od -c
0000000 033 [ H 033 [ 2 J
0000007
clear -x
is equivalent to printf '\033[H\033[2J'
,
sole clear
is equivalent to printf '\033[H\033[2J\033[3J'
.
These sequences are ANSI escape codes. The terminal is supposed to interpret them.
Abbr | Name | Effect | |
---|---|---|---|
CSI n ; m H |
CUP | Cursor Position | Moves the cursor to row n , column m . […] |
CSI n J |
ED | Erase in Display | Clears part of the screen. […] |
Note what clear
prints strongly depends on $TERM
. E.g. if I pick a "random" terminal:
$ TERM=ztx clear | od -c
0000000 033 E
0000002
The above clear
does not use ED; it uses CNL:
Abbr | Name | Effect | |
---|---|---|---|
CSI n E |
CNL | Cursor Next Line | Moves cursor to beginning of the line n (default 1 ) lines down. […] |
which does not clear the screen in my terminal. Maybe it does in a real ztx
; or it doesn't, I don't know. Anyway clear
"thinks" the terminal is ztx
and for ztx
CNL is the best choice.
In any case we could build and use an equivalent printf
command. The very point of clear
is it checks $TERM
in the environment and then looks in the terminfo database (see man 5 terminfo
) to determine how to clear the screen. With printf
we would need to do this manually.
It's not impossible some implementation(s) of clear
in some circumstances (i.e. for some value of $TERM
) print a sequence that does what you want.
And then there is the terminal (terminal emulator). Even if clear
prints \033[2J
, the terminal may react by doing what you want. Maybe there is an option that lets you choose. You can write a terminal emulator that reacts this way.
I often use tmux, it implements its own scrollback buffer. Inside it $TERM
expands to screen
; clear
and clear -x
are both equivalent to printf '\033[H\033[J'
. This sequence does what you want when interpreted by tmux. But outside of tmux (in my case with scrollback buffer provided by Konsole) the same sequence replicates your problem. I deduce tmux is a terminal emulator that reacts the way you want.
My point is: clear
prints different things, depending on what it "thinks" your terminal is; then the terminal interprets these things and it may react in whatever way. E.g. your clear
seems to do what my clear -x
does: it does not clear the entire scrollback buffer, only the visible part. So maybe your clear
does not print \033[3J
; or maybe it does but your terminal does not clear the buffer fully anyway.
Maybe you could find a different value of $TERM
so your clear
consulting your terminfo database will be able to make your terminal emulator do what you want. Even if you could do this, I wouldn't say it's the Right Thing in general. Changing $TERM
can break other things.
Solution 1: tmux
Use tmux and its scrollback buffer. My tests indicate it behaves the way you want (at least when clear
sees $TERM
as screen
). The tool is a terminal multiplexer, I use it routinely because of its other features and I had to explicitly get out of it to write some parts of this answer.
It may be a major change in how you work in terminals. In my opinion it's worth it. Note using the scrollback buffer of tmux is in many aspects different than using the scrollback buffer of a terminal emulator.
Consider at least trying tmux.
Solution 2: custom clear
as a shell function
It seems you don't want to clear anything; you want to move text out of the screen and place the prompt at the top. So let's do this:
clear() (
if [ "$#" -ne 0 ]; then
command clear "$@"
exit
fi
h="$(tput lines 2>/dev/null)"
if [ "$?" -eq 0 ]; then
until [ "$h" -le 1 ]; do
printf '\n'
h=$((h-1))
done
fi
command clear -x
)
The function overrides the clear
command when invoked without arguments; it falls back to the command otherwise. When it overrides, the function prints just the right number of newlines to move the previous content out of the screen; then regular clear -x
is invoked to clear the newlines and place the prompt at the top.
Notes:
-
tput lines
is not portable. If there is any problem with this command then the function will only callcommand clear -x
. -
command clear -x
assumes yourclear
executable supports-x
like mine does. Judging by the question you don't need-x
, your soleclear
does not clear the entire buffer (maybe because yourclear
is different; maybe because your terminal(s) are different). It may be enough to callprintf '\033[H\033[2J'
instead. On the other handcommand clear …
consults the terminfo database, it's a feature one may want from the function as well. Adjust the function to your needs. - If the terminal emulator is resized when the function works then the function may misbehave. If you need to resize, do it before or after calling
clear
, not during. - The function can be used inside tmux. It's absolutely not needed there (because tmux itself is a solution) but as far as I can tell it doesn't break anything. So e.g. you can place the function in your
~/.bashrc
in case you use Bash outside of tmux, and still the things should work fine when you use Bash inside tmux. - Any shell function is defined in a shell. Our code can be converted to a script (wrapper) that overrides
clear
regardless of the shell.
Solution 2:
From the man page, clear -x
does not clear the scrollback buffer.
Solution 3:
Short, portable answer:
tput indn $LINES
... will scroll the current terminal "up" by the amount of lines it has.
The effect is a "clear" of the screen, without loss of any scroll buffer content.
And as shown elsewhere:tput indn $LINES | od -t x1z
... will display what exactly is happening (in hex, with a character interpretation here).
man terminfo
for more options to tput.
This is perfectly possible to put in an alias or even a bash function.
function scroll_up { tput indn $LINES }
NOTE: shopt -s checkwinsize
(more info; man bash) should be in your $HOME/.bashrc
or $HOME/.bash_aliases
- just as the scroll_up
function above.
One more NOTE:
To extend portability make sure to instead of shopt ...
useLINES=$(tput lines)