1. Introduction

For many reasons, we might want to halt the execution of a script and wait for a single keypress. While a tool for this exists in Windows as the pause batch command, Linux has no direct equivalent.

In this tutorial, we discuss what options Linux users have to emulate or simulate the batch pause command with Bash. First, we check how Bash scripts run and what control we have over their flow. Next, we discuss user input mechanics and how to use those to get the behavior of the batch pause. Finally, we explore an option to develop a tool under Linux that functions in the same way.

We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.

2. Running Bash Scripts

Under most operating systems, Linux included, we can run a script in many ways:

  • one-liner in a shell
  • script file in a shell
  • script file from the graphical user interface (GUI)
  • one-liner or script file from a third-party application

The first two methods preserve the context shell unless commands we execute kill it:

$ echo 'Output 1.'; echo 'Output 2.'
Output 1.
Output 2.
$
$ echo "echo 'Output.'" > script.sh
$ bash script.sh
Output.
$

On the other hand, the latter two often spawn a shell just for running our script. After that, the shell may exit and disappear.

Moreover, some ways of running scripts disable interactive input. Indeed, this is crucial if we want to modify the execution flow. When would we want to do that?

3. Flow Control in Bash

Left unattended, many scripts perform their tasks and quit. Others need user input, depending on conditions. Still, others are more convenient when users control their execution speed.

Let’s examine the following simple script longfast.sh:

for row in {1..1000}; do
  echo $row
done

The code above prints the numbers from one to a hundred on consecutive rows. Notably, there are two potential usability problems here.

Firstly, depending on the screen and setup, the user might not be able to see all output at once. Secondly, depending on the way the script is run, users could end up not even seeing anything, as the script normally concludes too fast.

In fact, because of these shortcomings, tools like more, less, and most detect when the text reaches the end of the screen. Crucially, once they do, output stops by default until the user presses some specific keys:

$ bash longfest.sh | more
1
2
[...]
65
66
--More--

While we can’t easily change the behavior in these tools, we could write our own. What happens when we want to tell Bash any key is acceptable in order to continue?

4. Bash User Input

Of course, of the many ways to get input, not all are interactive. Since we want to enable user flow control, let’s look at some options that are.

4.1. read Command

Indeed, Linux provides the versatile read built-in. In its most basic form, it allows us to assign input to a variable:

$ read var
value
$ echo $var
value

Here, we assigned any input up to the default delimiter IFS to $var. However, we can also add a prompt with the -p flag, specify a timeout with -t and limit the number of characters via -N.

4.2. /dev/tty and /dev/console

A more targeted way to get input is by using redirection. Specifically, redirecting to or from a tty driver:

$ read var < /dev/tty
value
$ echo $var
value

Furthermore, we could combine the approach with other tools like dd (Data Duplicator). In particular, we can duplicate data from /dev/tty or /dev/console to get input.

How do we then employ the above to replicate the pause we’re after?

5. The Bash Batch pause

Variants of the methods discussed allow us to pick the optimal solution on a case-by-case basis. Let’s explore our pausing options.

5.1. Pure read

Of course, we can always just use the simplest form of read and request the user to press “Return” in order to continue:

$ read
any input until delimiter
$

Apart from simplicity, we don’t gain much else:

  • there is no prompt
  • users can press any number of keys
  • the screen echoes pressed keys
  • to continue, one must press Return or a termination signal key combination
  • there is a newline before the next prompt

How can we make this better?

5.2. Enhanced read

As an enhancement to the basic read version, we can add a prompt with -p, use -s to silence output, and -r to receive raw input:

$ read -rsp $'Press enter to continue...\n'
Press enter to continue...
$

Notice how there is no newline before the next prompt because we don’t output keypresses.

Now read is more user-friendly but still requires “Return” to continue. To remedy this, we can use the -d flag:

$ read -rsp $'Press escape to continue...\n' -d $'\e'
Press escape to continue...
$

Note that -d sets the delimiter at which input should end. Alternatively, we can set the number of characters to allow before cutting off input.

5.3. Any Key read

Indeed, permitting only a single character to be buffered will unpause on any key:

$ read -rsp $'Press any key to continue...\n' -n 1
Press any key to continue...
$ 

This version simulates a Windows pause very closely by using the -n flag. Still, there are other ways.

5.4. Using /dev/tty and dd to Pause

Employing /dev/tty, we can trap all EXIT conditions, reacting with the stty tool:

$ ( trap "stty $(stty -g;stty -icanon;)" EXIT
  stty -echo
  echo 'Press any key to continue...'
  LC_ALL=C dd bs=1 count=1 >/dev/null 2>&1
) </dev/tty
Press any key to continue...
$

First, we trap the script EXIT, ensuring all tty settings are correctly restored. After that, we use stty -echo to prevent echoing characters we press to the terminal. Next, we present a prompt to the user. Finally, we direct any input from /dev/tty to dd for a single character.

6. Implementing pause and Buffering

Linux allows us to develop our own Batch pause tool. However, there is one big catch: line buffering.

Because of the default buffers, there is practically no canonical way to prevent further input after a single character. Hence, our use of icanon above. This flag handles the switch between POSIX modes of input.

Indeed, in non-canonical mode, we have more control over when input concludes. This means we can work with the raw characters one by one.

So, to simulate our pause, we need to:

  • enter non-canonical mode in the terminal
  • work with a library that supports this mode and its buffering
  • use a function that simulates pause

Let’s see what we have at our disposal.

7. Using getch() to Pause

There are a couple of non-standard compiler libraries which contain a function called getch(), which is also not part of the C standard. The getch() function awaits and terminates after a single keypress, returning the associated character code.

In particular, we can find it in the conio.h and curses.h header files. While the former is mostly only available under MS-DOS and Win32, the latter has ports on multiple platforms.

Using getch() in curses.h under Linux, we can implement our Batch pause easily as the pause.c source:

#include <curses.h>

int main() {
  filter();
  initscr();
  cbreak();
  printw("Press any key to continue...");
  getch();
  endwin();
  return 0;
}

First, we need to install the ncurses library packages, which is done with apt-get install libncurses5-dev libncursesw5-dev in Debian. After that, we compile with gcc pause.c -o pause -lncurses.

However, this is where the simplicity ends, because there is a known bug with this approach. For us to use ncurses, the screen has to be cleared. This defeats part of the purpose to use pausing in the first place: being able to check the current screen contents.

Overcoming this issue is not within the scope of the tutorial, but the solution involves similar terminal modifications to the ones we saw for Bash above.

8. Summary

In this article, we explored ways to get the behavior of the Windows Batch pause in Linux. Some methods are more straightforward, while others require development.

In conclusion, we can say that there are multiple ways to emulate or simulate the Batch pause tool, but all of them are burdened by the problems of buffering and input modes.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.