109

How do I stop a bash script until a user has pressed Space?

I would like to have the question in my script

Press space to continue or CTRL+C to exit

and then the script should stop and wait until Space is pressed.

3

9 Answers 9

86

You can use read:

read -n1 -s -r -p $'Press space to continue...\n' key

if [ "$key" = ' ' ]; then
    # Space pressed, do something
    # echo [$key] is empty when SPACE is pressed # uncomment to trace
else
    # Anything else pressed, do whatever else.
    # echo [$key] not empty
fi

Replace ' ' for space at above with '' for Enter key, $'\t' for Tab key.

11
  • 12
    You should add -s to not to print the pressed character out on the terminal. And add a linebreak at the end, or the output will continue directly in the same line like the question. Best would be: read -n1 -rsp $'Press any key to continue or Ctrl+C to exit...\n'
    – rubo77
    Jun 22, 2014 at 9:24
  • 3
    This script doesn't work. Just tested it on Red Hat linux... The else block always runs, even when the spacebar is pressed.
    – robert
    Dec 22, 2015 at 6:56
  • 3
    @robert It's because you don't use bash. It works if you use read _ instead, if you have some other shell than bash. May 2, 2016 at 9:48
  • Should the '' contain a space inside? Nov 29, 2016 at 10:37
  • 2
    This reads from STDIN, which will cause issues given pipeline | script.sh See this answer for a solution.
    – Tom Hale
    Jun 28, 2018 at 5:25
59

The method discussed in this SO Q&A is likely the best candidate for an alternative to the pause behavior that you're accustom to on Windows when doing BAT files.

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

Example

Here I am running the above and then simply pressing any key, in this case the D key.

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

References

5
  • I mean why the $ before the string in here: -rsp $'Press ?
    – rubo77
    Jun 4, 2014 at 22:44
  • 2
    @rubo77 - ah. That's how you can do a literal string with special characters. It's of the form: $' ... '
    – slm
    Jun 5, 2014 at 0:38
  • 1
    @rubo77 - that's different. That's a dollar sign w/ double quotes, I used a dollar w/ single quotes. Please delete that comment, it's wrong.
    – slm
    Jun 5, 2014 at 7:37
  • 1
    Ah, I understand. In case you put escaped sequences inside the prompt-string. see wiki.bash-hackers.org/syntax/quoting#ansi_c_like_strings
    – rubo77
    Jun 5, 2014 at 7:57
  • @rubo77 - yes that notation allows for escape sequences to be included w/o any extra echo -e "..." lines. It's much more compact in those situations.
    – slm
    Jun 5, 2014 at 7:58
11

You could create a pause function for it to use everywhere in your script like:

#!/bin/bash
pause(){
 while read -r -t 0.001; do :; done # dump the buffer
 read -n1 -rsp $'Press any key to continue or Ctrl+C to exit...\n'
}
echo "try to press any key before the pause, it won't work..."
sleep 5
pause
echo "done"
2
  • 1
    if you are new to shell scripting - you need to put the function at the top of your script before using it
    – Richard
    Mar 24, 2016 at 16:54
  • Perfect solution for my purposes! Thanks!
    – TheGeeko61
    Nov 19, 2021 at 23:08
7
hold=' '
printf "Press 'SPACE' to continue or 'CTRL+C' to exit : "
tty_state=$(stty -g)
stty -icanon
until [ -z "${hold#$in}" ] ; do
    in=$(dd bs=1 count=1 </dev/tty 2>/dev/null)
done
stty "$tty_state"

This now prints a prompt without a trailing newline, handles CTRL+C reliably, invokes stty only as often as necessary, and restores the controlling tty to exactly the state in which stty found it. Look into man stty for information on how to explicitly control echoes, control characters and all.

You might also do this:

printf "Press any key to continue or 'CTRL+C' to exit : "
(tty_state=$(stty -g)
stty -icanon
LC_ALL=C dd bs=1 count=1 >/dev/null 2>&1
stty "$tty_state"
) </dev/tty

You could do it with ENTER, no [ tests ] and no stty like:

sed -n q </dev/tty
2
  • Your two last solutions seem to work fine with /bin/sh (e.g. on FreeBSD) too, not only with bash.
    – zezollo
    Jun 10, 2020 at 15:24
  • is your answer on SO equivalent, or even better?
    – zezollo
    Jun 10, 2020 at 15:32
6

Here's a way that works in both bash and zsh, and ensures I/O to the terminal:

# Prompt for a keypress to continue. Customise prompt with $*
function pause {
  >/dev/tty printf '%s' "${*:-Press any key to continue... }"
  [[ $ZSH_VERSION ]] && read -krs  # Use -u0 to read from STDIN
  [[ $BASH_VERSION ]] && </dev/tty read -rsn1
  printf '\n'
}
export_function pause

Put it in your .{ba,z}shrc for Great Justice!

6

Here's a simple solution from Press SPACE to continue (not ENTER)

read -r -s -d ' '

This will wait until you press the space bar. Yes, only the space bar, it wouldn't break if you pressed enter.

1
  • That one has the merit to work both in zsh and bash Sep 2, 2021 at 6:25
4

Settings IFS to empty string suppresses read's default behavior of trimming white space.

try_this() {
  echo -n "Press SPACE to continue or Ctrl+C to exit ... "
  while true; do
    # Set IFS to empty string so that read doesn't trim
    # See http://mywiki.wooledge.org/BashFAQ/001#Trimming
    IFS= read -n1 -r key
    [[ $key == ' ' ]] && break
  done
  echo
  echo "Continuing ..."
}
try_this

UPDATE 2018-05-23: We can simplify this by using the REPLY variable, which is not subject to word-splitting:

try_this() {
  echo -n "Press SPACE to continue or Ctrl+C to exit ... "
  while true; do
    read -n1 -r
    [[ $REPLY == ' ' ]] && break
  done
  echo
  echo "Continuing ..."
}
try_this
2

lazy one liner:

echo "Press any key to continue or Ctrl+C to cancel"
read && do_something.sh

the disadvantage is that you lose control when the user press ctrl+c. The script will always exit with code 130 in that case.

1

Many wonderful answers using read -rn1 and that will work beautifully UNTIL you press an arrow key, or a function key, or any other key which lacks an ASCII representation and is translated by the terminal into an escape sequence of multiple characters to read. When that happens, you will have garbage characters waiting in the input buffer which will appear in the next read.

Don't forget to clear the buffer before and after...

    while read -r -t 0.001; do :; done # dump the buffer
    read -r -n1 -p "Press any key to continue..." # wait
    read -r -t 0.001 # dump the rest of any escape sequence

The -t is a timeout specification. The 0.001 is a 1/1000 of one second. Not long enough to notice the pause if no data is available, but the timeout is long enough to extract information from a waiting buffer.

The while loop clears the input buffer before waiting for a keystroke. This while loop prevents the user from being able to hit 5 keys to bypass the next 5 "Press any key to continue" notices. Instead, the user must wait until the notice appears before pressing a key will actually continue.

The last read clears any other keys currently waiting to be read. These keys would be the remainder of an escape sequence made by the one keypress.

Translating the escape sequences of varying lengths into a description of the actual keypress is certainly possible, but there is a standard, and you can test for the actual sequence.

    while read -r -t 0.001; do :; done # dump the buffer
    read -r -n1 -p "Press any key to continue..." # wait
    read -r -t 0.001 zRest # read the entire escape sequence
    RESULT+="$zRest" # create the actual keypress

This appends the remaining buffer onto the result of the read whose keypress may create a multi-character escape sequence. It will work well until someone presses multiple keys at the same time. But if that happens, it is probably better to reject the mess as an unknown keypress anyway.

2
  • I think you are missing to put the first part into RESULT
    – rubo77
    Sep 2, 2021 at 3:01
  • 1
    And I would add -s to disable printing of the output
    – rubo77
    Sep 2, 2021 at 3:07

You must log in to answer this question.

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