29

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?

7
  • 10
    The particular program that man is likely using there is less, a "pager" program.
    – muru
    Jun 26, 2022 at 5:59
  • 4
    Can someone explain why they're downvoting this question? It's a good question, it has a good answer, and it might help other people who want or need similar functionality.
    – ArrayBolt3
    Jun 26, 2022 at 6:06
  • 4
    Presumably because asking how to code some functionality in some unspecified programming language is (a) a very poor question, and (b) off-topic even if the language were specified
    – muru
    Jun 26, 2022 at 6:10
  • 2
    I agree with @ArrayBolt3, please do not close this question. Raffa shows how it can even by applied in bash shellscripts.
    – sudodus
    Jun 26, 2022 at 12:55
  • 4
    @TannerSwett OP did tag it "programming", so it seems to me the do want to modify and existing program's code to do so
    – muru
    Jun 26, 2022 at 14:28

4 Answers 4

31

man 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 ... |& ...).

6
  • 12
    Could you explain what |& does as opposed to just | in your command? Jun 27, 2022 at 7:19
  • 12
    @BrunoPérel it allows the standard error to pass through the pipe as well (3.2.3 Pipelines)
    – mrbolichi
    Jun 27, 2022 at 11:17
  • 8
    Nore that |& 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.
    – iBug
    Jun 28, 2022 at 5:23
  • 4
    One should perhaps add that pressing "q", as an immediate consequence, only leads to 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
  • 3
    |& 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.
    – ilkkachu
    Jun 29, 2022 at 9:12
18

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.

Edit:

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>
5
  • @Raffa, I made a demo by polishing your shellscript (see the end of my answer). I intend to remove it from here, if/when you copy it into your answer in order to give you the full credit for it, because you took the crucial step by suggesting tput. Test it first, and improve it if you think necessary. Thanks :-)
    – sudodus
    Jun 26, 2022 at 12:04
  • 1
    With pleasure ... I might as well put some effort and make a practical full blown working example ... I just had my coffee break :)
    – Raffa
    Jun 26, 2022 at 13:15
  • 1
    Please see less source code comments on lines 12 and 13 ... include on line 59 #include <termcap.h> i.e. termcap() ... and the comment on line 1343 about using tputs() ... so less as well curses inside
    – Raffa
    Jun 27, 2022 at 6:45
  • Thanks for those steps confirming that less is also using curses.
    – sudodus
    Jun 27, 2022 at 7:37
  • 1
    Running 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.
    – jaskij
    Jun 27, 2022 at 11:24
10

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
11
  • 1
    +1; This exits by 'q' but does not restore the console. So it is actually only half of the answer. I think less and htop and other tools with similar behaviour use tools from the 'ncurses' library.
    – sudodus
    Jun 26, 2022 at 9:57
  • @sudodus It restores the console if you run it as a program as the OP wants i.e. from a script file as it will exit the active shell back to the console/terminal ... You probably pasted the code directly in the terminal and in this case the 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
    – Raffa
    Jun 26, 2022 at 10:11
  • I think we misunderstand each other. I often do what you explain (in my shellscripts), and it works well for me, but whatever what written by the program (shellscript) will still be there in the terminal window after exit. This is often good and enough, but not the same as what I think the OP is asking for. Please compare with less or htop, for example less ~/.bashrc
    – sudodus
    Jun 26, 2022 at 10:20
  • @sudodus That way yes, your totally correct ... I did misunderstand your comment ... apologies :)
    – Raffa
    Jun 26, 2022 at 10:34
  • 1
    Thanks again, I think your answer is informative and useful now :-) Other users may want to wrap some program into a shellscript based on yours in order to get a clean terminal window afterwards.
    – sudodus
    Jun 26, 2022 at 14:29
7

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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .