Is there a way to make my script wait for the user to press any key and then continue without them having to press Enter? I want to make it work in Bourne shell (sh), not Bash.
- 81,893
- 31
1 Answers
Preliminary notes
Nowadays sh is not necessarily the legacy Bourne shell. Nowadays sh is a shell that supports at least the features required by POSIX (assuming a correct implementation). POSIX specifies the sh utility and the Shell Command Language.
I assume you want your code to work in a POSIX shell and to be portable in general.
Issues
dd ibs=1 count=… is a POSIX way to read an exact number of bytes. It seems to be the only portable command line utility that can do the job reliably. But dd reads bytes, while read -n in Bash reads characters. POSIX allows multi-byte characters in locales other than POSIX. Even if you run the entire script with LC_ALL=POSIX, the terminal (terminal emulator) may still generate a multi-byte sequence upon some single keystroke. Such sequence may not be a multi-byte character; it may be an escape sequence (e.g. for F1, also see this answer).
After you read just one byte, the rest will remain and it will be read by anything that tries to read from the terminal later (it may be a part of your script or the interactive shell you run the script from).
Additionally if you run sole dd ibs=1 count=1 then you will most likely discover that nothing gets to dd until you exceed {MAX_INPUT} or {MAX_CANON}, or press Enter or Ctrl+D (and in case of count being more than 1, if you want to provide less bytes then you may need to press Ctrl+D twice).
To deal with these issues you need the non-canonical mode. Use stty -icanon. In general starting with stty raw looks like a good idea.
Code
The following example is a proof of concept. Because it manipulates line settings of the terminal and reads from the terminal you must not paste it into an interactive shell, this won't work. Paste it into a file and run the file.
#!/bin/sh
echo "Press any key to continue."
saveterm="$(stty -g)" # save terminal state
stty raw
stty -echo -icanon min 1 time 0 # prepare to read one byte
dd ibs=1 count=1 >/dev/null 2>/dev/null # read one byte
stty -icanon min 0 time 0 # prepare to read lefotvers
while read none; do :; done # read leftovers
stty "$saveterm" # restore terminal state
Notes
The script doesn't check if its stdin is a terminal, but in general it should (
[ -t 0 ]).In a sane setup a modifier key (e.g. Shift), when pressed alone, sends no input to whatever reads from the terminal; therefore our code will not register such key as "any key".
The "read leftovers" trick is taken from this answer.
Thanks to
stty rawCtrl+C or Ctrl+Z can be "any key" as well, instead of sendingSIGINTorSIGSTOPrespectively. Still the shell interpreting the script may get a signal from elsewhere, so in general it may happen it exits before it gets tostty "$saveterm"; so it may happen it leaves the terminal in a state not suitable for interactive use. You may want to trap relevant signals and restore the initial state of the terminal anyway.It's true one should double-quote variables in general. Here$savetermis deliberately not quoted becausestty -ggenerates output that is unspecified. Implementations ofsttyare allowed to generate output with spaces and later expect the shell to split it and pass multiple arguments. What is specified is a constraint that the output ofstty -gmust be safe when unquoted, it must not trigger word expansion in a shell. For portability unquoted$savetermis better.Edit: that was a defect in the POSIX specification. The above code has been fixed.
To know what byte was read you need to save the output of
dd, either to a regular file (>some_file) that you will examine later, or to a variable (variable="$(dd …)"). But:- A terminal is able to generate a null byte (commonly with Ctrl+@) but most (all?) implementations of
shcannot store a null byte in a variable. Storing in a file is OK, if only you can examine/manipulate its content without reading it into a variable. ddfrom the above code will give you only one byte, it may not be enough to tell the character or the key. OTOH a single byte should be enough to tell apart e.g. Q from anything else, soPress Q to quit or any other key to continue.orP - proceed; B - back; Q - quit; H - helpseem possible to implement without analyzing more bytes.
- A terminal is able to generate a null byte (commonly with Ctrl+@) but most (all?) implementations of
What about more bytes?
The above should be fine for "press any key". A real equivalent of read -n should read one byte at a time and decode the sequence until it gets the desired number of characters. I'm not going to try to build it here.
- 81,893