The man
command closes when you press q and restores the console to like you previously had it.
What is this called?
How could you make another program behave in this manner?
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It only takes a minute to sign up.
Sign up to join this communityman
is using a pager program, less
, to provide this functionality. You can do the same thing by piping a command's standard output and standard error streams to less
like so:
my_command_here arg1 arg2 |& less
Whatever my_command_here
spits out will be put into an easily scroll-able screen that you can exit out of with q. You can get a good feel for how well it works by trying ip address help |& less
- you can scroll with the arrow keys and with PgUp and PgDn, and exit with q.
Bash's |&
also redirects error output (stderr) to the pipe, unlike the the plain |
which would just redirect normal output (stdout). With |
, any errors would cause messy output, as they'd appear on the terminal, but wouldn't be scrollable in less
. (In sh
, you'd use the standard ... 2>&1 | ...
instead of ... |& ...
).
|&
does as opposed to just |
in your command?
Jun 27, 2022 at 7:19
|&
is a Bashism. It does not work with /bin/sh
(normally). If you want this in a portable shell script, use 2>&1
and a normal pipe instead.
less
exiting; the program feeding data to less
notices a broken or closed pipe -- that is, a write error on its standard output -- and can react to that any way it likes.
Jun 28, 2022 at 13:37
|&
is a bit funny in that the redirection of stderr takes place after any (non-pipe) redirections in the left-hand command, unlike the normal pipe redirection which happens first. E.g. (echo output; echo error >&2) 2>/dev/null |& cat
still prints both output
and error
.
I think less
and htop
and other tools with similar behaviour use tools from the 'ncurses' library.
Anyway, compiling a program and using ncurses is a way to make a program do what you want. There are also other versions of the 'curses' library.
NCURSES Programming HOWTO
Introduction
1.1. What is NCURSES?
You might be wondering, what the import of all this technical gibberish is. In the above scenario, every application program is supposed to query the terminfo and perform the necessary stuff (sending control characters etc.). It soon became difficult to manage this complexity and this gave birth to 'CURSES'. Curses is a pun on the name "cursor optimization". The Curses library forms a wrapper over working with raw terminal codes, and provides highly flexible and efficient API (Application Programming Interface). It provides functions to move the cursor, create windows, produce colors, play with mouse etc. The application programs need not worry about the underlying terminal capabilities.
So what is NCURSES? NCURSES is a clone of the original System V Release 4.0 (SVr4) curses. It is a freely distributable library, fully compatible with older version of curses. In short, it is a library of functions that manages an application's display on character-cell terminals. In the remainder of the document, the terms curses and ncurses are used interchangeably.
A detailed history of NCURSES can be found in the NEWS file from the source distribution. The current package is maintained by Thomas Dickey. You can contact the maintainers at bug-ncurses@gnu.org.
1.2. What we can do with NCURSES
NCURSES not only creates a wrapper over terminal capabilities, but also gives a robust framework to create nice looking UI (User Interface)s in text mode. It provides functions to create windows etc. Its sister libraries panel, menu and form provide an extension to the basic curses library. These libraries usually come along with curses. One can create applications that contain multiple windows, menus, panels and forms. Windows can be managed independently, can provide 'scrollability' and even can be hidden.
Menus provide the user with an easy command selection option. Forms allow the creation of easy-to-use data entry and display windows. Panels extend the capabilities of ncurses to deal with overlapping and stacked windows.
These are just some of the basic things we can do with ncurses. As we move along, We will see all the capabilities of these libraries.
Thanks Raffa, you helped us find out how to make this happen also in shellscripts: use tput
:-)
tput smcup
to save screen contents
tput rmcup
to restore screen contents
I found an include indicating a curses library for htop
I found an include indicating a curses library for tput
Raffa found steps indicating that less
uses a curses library too
See this link:
https://github.com/openbsd/src/blob/master/usr.bin/tput/tput.c
#include <curses.h>
#include <termcap.h>
i.e. termcap() ... and the comment on line 1343 about using tputs() ... so less as well curses inside
ldd $(which less)
and ldd $(which htop)
on my system (Arch) does show libncursesw.so.6
for both, so yes, your assumption about them using ncurses
is correct. Whether they do it directly, or indirectly, via another library, is a different matter I can't determine with ldd
.
How could you make another program behave in this manner?
In Bash
You read what is entered at the prompt into a variable i
with read
and make it return after reading 1 character -n 1
and disable echoing the character in the terminal -s
like so:
read -s -n 1 i
Then use it in a while
loop like so:
#!/bin/bash
while read -s -n 1 i; do
case "$i" in
q) exit
;;
*) echo "Enter q to exit or any other key to print this message again."
;;
esac
done
or like so:
#!/bin/bash
while read -s -n 1 i; do
if [ "$i" == "q" ]; then
exit
else
echo "Enter q to exit or any other key to print this message again."
fi
done
The above code will restore the console if you run it as a program i.e. from a script file but if you paste and run it directly in the terminal, then the exit
call will close your terminal and you don't want that ... so use a break
call instead of exit
in this case like so:
while read -s -n 1 i; do
if [ "$i" == "q" ]; then
break
else
echo "Enter q to exit or any other key to print this message again."
fi
done
Alternatively, if you need to use read
itself for reading other input ... then, you can use it with bash's built in bind
like so:
#!/bin/bash
# Bind the "q" key to run "quit_function" when pressed.
bind_q () { bind -x '"q": quit_function' 2> /dev/null; }
# Unbind the "q" key.
unbind_q () { bind -r "q" 2> /dev/null; }
# Run "unbind_q" then exit.
quit_function () { unbind_q; exit; }
# Start the key binding.
bind_q
while read -e -p "Enter two numbers separated by space to calculate their sum or \"q\" to quit: " num1 num2; do
if [[ $num1 =~ ^[0-9]+$ ]] && [[ $num2 =~ ^[0-9]+$ ]]; then
echo "The sum of $num1 + $num2 is: $(($num1+$num2))"
else
echo "You entered $num1 $num2"
fi
done
Interestingly, @sudodus(Thank you @sudodus) pointed out an aspect that I honestly totally missed ... which is literally "restoring the console" i.e. to it's previous state before the program was run ... to help satisfy this condition, one might look into an interesting utility called tput
which is provided by the ncurses-bin package that you can use to start a new/secondary terminal screen like so:
tput smcup
Then run whatever program/command you want ... and when finished remove the new/secondary screen(with all its contents) to go back to your original terminal screen(as you left it) like so:
tput rmcup
This can be used while quitting on q in a while loop like so(close enough, I hope):
#!/bin/bash
tput smcup # Start a new screen and hide the original one.
while read -s -n 1 i; do
if [ "$i" == "q" ]; then
tput rmcup # Remove the new screen and show the original one.
exit
else
echo "You typed $i"
echo "Enter q to exit or any other key to print this message again."
fi
done
This is, however, getting a bit complicated and sort of defy the purpose of simplifying things ... so let us break it down with one more example that mimics some aspects of the command-line text editor less
(No less
inside though) ... The following script should be able to read a text file 10 lines at a time and enable you to move forward and backward i.e. scan the file in both directions 10 lines at a time ... For the purpose of this example I will use the /var/lib/dpkg/status
file(Yeah I like reading it in my free time) but, you can choose another text file that you like.
Read the total number of lines in the file into a variable like so(can be done in many ways e.g. cat file | wc -l
):
tlnum=$(awk 'END {print NR}' /var/lib/dpkg/status)
Set the maximum line number limit(so you don't exceed the total lines in the file) like so:
mlnum=$((tlnum-10))
Add logic and put it all in a script like so(Keep in mind this is just a quickly brewed example just for science so suggesting improvements is much appreciated):
#!/bin/bash
file="/var/lib/dpkg/status"
info () {
clear -x
echo "You typed $i"
echo "Enter o to open the file."
echo "Enter n for next 10 lines."
echo "Enter p for previous 10 lines"
echo "Enter q to exit or any other key to print this message again."
}
tput smcup
info
while read -e -s -n 1 i; do
case "$i" in
o) tlnum=$(awk 'END {print NR}' "$file")
mlnum=$((tlnum-10))
clnum=1
info
echo "---------- Line number $clnum"
awk -v clnum="$clnum" 'NR >= clnum && NR <= clnum+10 {print }' "$file"
echo "----------"
;;
n) [ "$clnum" -lt "$mlnum" ] && clnum=$((clnum+10))
info
echo ">>>>>>>>>> Line number $clnum"
awk -v clnum="$clnum" 'NR >= clnum && NR <= clnum+10 {print }' "$file"
echo "----------"
;;
p) [ "$clnum" -ge 10 ] && clnum=$((clnum-10))
info
echo "<<<<<<<<<< Line number $clnum"
awk -v clnum="$clnum" 'NR >= clnum && NR <= clnum+10 {print }' "$file"
echo "----------"
;;
q) tput rmcup
exit
;;
*) info
;;
esac
done
less
and htop
and other tools with similar behaviour use tools from the 'ncurses' library.
exit
call will exit the current shell i.e. close the terminal :) ... in this case a break
call is what you want instead of exit
less
or htop
, for example less ~/.bashrc
What is this called?
This is known as the 'alternate screen'.
This can be used from a Bash script; see answers to Using the "alternate screen" in a bash script from StackOverflow.
man
is likely using there isless
, a "pager" program.