82

I can ask the user to press Enter by using read, and have him wait by calling sleep. But I can’t think of a way of doing both at the same time. I would like the user to be given the choice:

Press Ctrl+C to Cancel, Enter to continue or just wait 10 seconds

How can I do that?

codeforester
  • 39,467
  • 16
  • 112
  • 140
qdii
  • 12,505
  • 10
  • 59
  • 116

4 Answers4

166

In bash(1), the read command has a -t option where you can specify a timeout. From the man page:

read [-ers] [-u fd] [-t timeout] [-a aname] [-p prompt] [-n nchars] [-d delim] [name ...]

-t timeout: cause read to time out and return failure if a complete line of input is not read within timeout seconds. This option has no effect if read is not reading input from the terminal or a pipe.

Transcript below (without hitting ENTER):

$ date ; read -t 10 -p "Hit ENTER or wait ten seconds" ; echo ; date
Tue Feb 28 22:29:15 WAST 2012
Hit ENTER or wait ten seconds
Tue Feb 28 22:29:25 WAST 2012

Another, hitting ENTER after a couple of seconds:

$ date ; read -t 10 -p "Hit ENTER or wait ten seconds" ; date
Tue Feb 28 22:30:17 WAST 2012
Hit ENTER or wait ten seconds
Tue Feb 28 22:30:19 WAST 2012

And a final one, hitting CTRL-C:

$ date ; read -t 10 -p "Hit ENTER or wait ten seconds" ; echo ; date
Tue Feb 28 22:30:29 WAST 2012
Hit ENTER or wait ten seconds

(1) If you're doing this in a script, make sure that it's running under bash rather than some other shell.

You can do that in many ways such as by using a #! shebang line of some type, specifying the shell when you run the command, or making sure your system will run bash by default for scripts.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 7
    I absolutely love it when detailed answers like this are given. Kudos to you! – Brian Feb 28 '12 at 20:13
  • ubuntu. -t - illegal option – ses May 14 '14 at 20:01
  • @ses, make sure you're using the bash builtin, not some external tool. – paxdiablo May 15 '14 at 03:10
  • 1
    If you're writing a shell script, make sure that it starts with a `#!/bin/bash` instead of `#!/bin/sh`. The latter may or may not work depending which binary `/bin/sh` actually is. You're allowed to write `#!/bin/sh` if and *only if* your script is POSIX compatible, which it pretty much sure is not. – Mikko Rantalainen Apr 18 '16 at 10:15
  • When using this, make sure your script is `#!/bin/bash`, not `#!/bin/sh`, because the `-t` option is not POSIX compatible (i.a.w. the script may not work on every machine) – Marian May 02 '17 at 14:31
  • @Marian, since the question is actually *tagged* `bash`, it's probably not necessary to mention that :-) – paxdiablo May 02 '17 at 22:46
  • @paxdiablo, not for the person who asked, but for others who might stumble upon this. ( Plus, many people seem to use bashisms with a `sh` shebang, so it's good to mention it every now and then :) ) – Marian May 03 '17 at 11:33
  • Right on! Thank you. I found this answer in Google - Nov 2017 – SDsolar Nov 30 '17 at 21:11
  • For the shebang, it may be useful to first use the `which bash` command to find the exact string to us eon your system. – SDsolar Dec 02 '17 at 01:09
  • The bash man page also mentions the **TMOUT** shell variable. – ksridhar Nov 19 '19 at 04:21
  • To add to the POSIX comment by @MikkoRantalainen - you can use a nice little tool called ShellCheck to test your scripts for syntax errors and POSIX compatibility. https://www.shellcheck.net/ – Peter Kionga-Kamau Apr 14 '22 at 07:59
  • 1
    @Peter, even though my answer always stated `bash` and never had `/bin/sh` anywhere in it, I'm succumbing to the pressure after a decade or so to explicitly state the requirement :-) – paxdiablo Apr 14 '22 at 08:07
  • 1
    I think it's actually safer to bet that `bash` is in `/bin/bash` than the `env` being in `/usr/bin/env`. In case of e.g. `python`, it's the other way around. As such, your shebang should be `#!/bin/bash` instead of `#!/usr/bin/env bash`. – Mikko Rantalainen Apr 14 '22 at 09:10
  • 1
    @Mikko, see https://www.cyberciti.biz/tips/finding-bash-perl-python-portably-using-env.html or (better) https://unix.stackexchange.com/questions/29608/why-is-it-better-to-use-usr-bin-env-name-instead-of-path-to-name-as-my. I don't think I've ever seen a system where `env` is not located there but I have seen places where people wanted a custom `bash`. For example, I made some modifications to `bash` many moons ago and wanted to be able to log in as a specific user to run my changed one (for the test scripts, not the login one). – paxdiablo Apr 14 '22 at 09:39
  • @paxdiablo Okay, I agreet that if you want to support changing `PATH` to allow using customized `bash` version then having `/usr/bin/env` would make sense. My point was that if you don't trust that `bash` is installed in `/bin/bash` you cannot trust that `env` is installed in `/usr/bin/env` either. As for the answer, the important part is `bash` vs `sh`, not using path to `bash` vs `env bash`. For scripting, I'll continue to use `#!/bin/bash` because I write my scripts to work with any version of `bash` and dropping the `env` saves you one `execve()` in the background. – Mikko Rantalainen Apr 14 '22 at 11:12
  • For details about using `env` or not, see https://unix.stackexchange.com/a/29620/20336 – Mikko Rantalainen Apr 14 '22 at 11:15
  • 1
    @MikkoRantalainen, added the other option to the answer. – paxdiablo Apr 14 '22 at 12:03
  • @MikkoRantalainen the trust levels are not the same - we have only convention to trust that `bash` is in `/bin` because there is no standard about even having `bash` as part of the base system - but we have every reason to trust that any system in use today has `env` (because it's in POSIX) as part of the base system. So already we have reason to expect `env` to exist *in a standard system directory* even if `bash` doesn't exist or only exists somewhere under `/opt` or `/usr/local/bin`. – mtraceur May 14 '23 at 16:40
  • According to POSIX, you indeed have `env` but you cannot know it's path. And you cannot use shebang (`!#"`) line without an absolute path so you must know the full path. According to POSIX, you should have installation method that modifies the shebang line to match the local system (https://stackoverflow.com/a/53116875/334451) but if you don't want to have that, go with `#!/usr/bin/env bash` if you want `PATH` environment variable to affect the selection, or `#!/bin/bash` if you want the system installed `bash` with optimal performance (using `env` causes extra `stat()` + `exec()` syscalls). – Mikko Rantalainen May 15 '23 at 11:20
  • All this discussion of shebang lines is interesting but actually have nothing to do with the question or answer. I put it in the answer simply as an example of ensuring you're running the script under `bash` rather than some other shell. I'm going to make that clearer. – paxdiablo May 15 '23 at 22:48
18

The read builtin has a timeout.

read -t 10

will do it

Johannes Weiss
  • 52,533
  • 16
  • 102
  • 136
5

Building on the thoughtful answers above, the return value from the read is useful to distinguish between an empty response from the user (for example "Press Enter" for the default action) and a timeout.

read -t 5 -p "Prompt " RESP
if [[ $? -gt 128 ]] ; then
    echo -e "\nTimeout"
else
    echo "Response = \"$RESP\""  # adding quotes so empty strings are obvious
fi

Another useful tidbit is that the -p "prompt " is written to stderr (not stdout) so if you're redirecting stderr, the prompt will not be displayed. An example of this would be logging an execution trace to a log file for later analysis. To use read -p Prompt in this case you can redirect stderr to the user for just the read statement.

set -x
exec 2>logfile
read -t 5 -p "Prompt " RESP 2>/dev/tty
user2070305
  • 445
  • 5
  • 9
1

From the bash reference manual :

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt][-t timeout][-u fd] [name ...]

Benoit
  • 76,634
  • 23
  • 210
  • 236