Score:0

How to read with timeout without making bash consider it an error?

us flag

I have a read_user_input.sh script:

#!/bin/bash

# set -e

prompt="bla? [Y/n] "

while true; do
    read -p "$prompt" -n 1 -s -t 3 reply
    case $reply in
        ""|Y|y) echo "bla!!!"; break;;
        N|n) echo "no bla :( you suck"; break;;
        *) ;;
    esac
done

Works as I expected - i.e.

  • User inputs "Y" -> bla
  • User inputs "y" -> bla
  • User hits Enter -> bla
  • Timeout -> bla

However, when adding set -e - the read ends with error > 128.

From read --help:

Exit Status:

The return code is zero, unless end-of-file is encountered, read times out (in which case it's greater than 128), a variable assignment error occurs, or an invalid file descriptor is supplied as the argument to -u.

What would be the best way to overcome this?

  1. adding || true seems not right, as it would hide any real errors.
  2. I also don't want to remove the set -e.
  3. One other thing I thought about was handling it in trap but that seems like an overkill
Score:2
tm flag

Instead of adding || true, set the $reply to y if there was a timeout:

    read -p "$prompt" -n 1 -s -t 3 reply || {
        err=$?
        if (( $err > 128 )) ; then
            reply=y
        else
            exit $err
        fi
    }
CIsForCookies avatar
us flag
I used it a bit differently so it would match my switch case
tm flag
I updated the answer so that the exit code corresponds to the original one, not the one coming from `$? > 128`.
CIsForCookies avatar
us flag
I think my answer gives the same outcome
tm flag
Try changing `read` to `XXXread` and see what exit code you get from both the versions.
CIsForCookies avatar
us flag
my version exists, which is ideal
tm flag
Yes, but with code 1 instead of 127.
Score:0
cn flag

Here is a hack where is used command substitution which forks a subshell where -e is not enabled.

#!/bin/bash

set -e

prompt="bla? [Y/n] "

while true; do
    case $(read -p "$prompt" -n 1 -s -t 3 reply; echo $reply) in
        ""|Y|y) echo "bla!!!"; break;;
        N|n) echo "no bla :( you suck"; break;;
        *) ;;
    esac
done
tm flag
It hides other errors similarly to `|| true`, try running it with `< /dev/null`.
Score:0
us flag

Using @choroba answer, I changed my switch case into:

#!/bin/bash

set -e

prompt="bla? [Y/n] "

while true; do
    read -p "$prompt" -n 1 -s -t 3 reply || (( $? > 128 ))
    case $reply in
        ""|Y|y) echo "bla!!!"; break;;
        N|n) echo "no bla :( you suck"; break;;
        *) ;;
    esac
done
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.