问题
I have searched for a simple solution that will read user input with the following features:
- timeout after 10 seconds, if there is no user input at all
- the user has infinite time to finish his answer if the first character was typed within the first 10 sec.
I have found a solution to a similar request (timeout after each typed character) on Linux Read - Timeout after x seconds *idle*. Still, this is not exactly the feature, I was looking for, so I have developed a two line solution as follows:
read -N 1 -t 10 -p "What is your name? > " a
[ "$a" != "" ] && read b && echo "Your name is $a$b" || echo "(timeout)"
In case the user waits 10 sec before he enters the first character, the output will be:
What is your name? > (timeout)
If the user types the first character within 10 sec, he has unlimited time to finish this task. The output will look like follows:
What is your name? > Oliver
Your name is Oliver
However, there is following caveat: the first character is not editable, once it was typed, while all other characters can be edited (backspace and re-type).
Do you have any ideas for a resolution of the caveat or do you have another simple solution to the requested behavior?
回答1:
Enable readline and add $a as the default value for the second read.
# read one letter, but don't show it
read -s -N 1 -t 10 -p "What is your name? > " a
if [ -n "$a" ]; then
# Now supply the first letter and let the user type
# the rest at their leisure.
read -ei "$a" b && echo "Your name is $b"
else
echo "(timeout)"
fi
This still displays a second prompt after the first letter is answered, but I don't think there's a better way to handle this; there's no way to "cancel" a timeout for read. The ideal solution would be to use some command other than read, but you would have to write that yourself (probably as a loadable built-in, in C).
回答2:
This solution may do.
read -n1 -t 10 -p "Enter Name : " name && echo -en "\r" &&
read -e -i "$name" -p "Enter Name : " name || echo "(timeout)"
Note: The second read uses the text captured from the first(-i option) to provide an editable buffer. The carriage return and the same prompt gives the user an impression that he is entering the same value.
回答3:
Test Conditions: GNU bash, version 4.4.19(1)-release Ubuntu 18.04.2 LTS
I created a function to solve your caveat of the first letter not being edittable, as shown below. I have only tested this with my local linux server, and I make no assumptions that this will work elsewhere or with newer/older versions of BASH (or read for that matter, but I was unable to tell what version I was running)
__readInput(){
str="What is your name? > "
tput sc # Save current cursor position
printf "$str"
read -n 1 -t 10 a # Wait 10 seconds for first letter
[[ $? -eq 0 ]] || return 1 # Return ErrorCode "1" if timed_out
while :; do # Infinite Loop
tput rc # Return cursor to saved position
printf "$str$a" # Print string (including what is saved of the user input)
read -n 1 b # Wait for next character
if [[ $? -eq 0 ]]; then
# We had proper user input
if [[ ${#b} -eq 0 ]]; then
# User hit [ENTER]
n=$a$b
break # End our loop
fi
rg="[A-Za-z-]" # REGEX for checking user input... CAVEAT, see below
if ! [[ $b =~ $rg ]] ;then
# We have an unrecognisied character return, assume backspace
[[ ${#a} -gt 0 ]]&&a=${a:0:(-1)} # Strip last character from string
tput rc # Return cursor to saved position
printf "$str$a " # This removes the ^? that READ echoes on backspace
continue # Continue our loop
fi
a=$a$b # Append character to user input
fi
done
}
You can call this function similar to the following:
declare n=""
__readInput
if [[ $? -eq 0 ]] || [[ ${#n} -eq 0 ]] ;then
echo "Your name is $n"
else
echo "I'm sorry, I didn't quite catch your name!"
fi
CAVEAT MENTIONED ABOVE EXPLAINED
So, you have a caveat that I fixed, perhaps you (or our friends) can fix this one. ANY character entered that isn't included in the $rg REGEX variable will be treated as BACKSPACE. This means your user could hit F7, =, \, or literally any character other than those specified in $rg and it will be treated as a backspace
回答4:
Short Answer:
Add a -s option on the first read command and a -ei option on the second read command:
read -s -N 1 -t 10 -p "What is your name? > " a
[ "$a" != "" ] && read -ei "$a" b && echo "Your name is $b" || echo "(timeout)"
Or with better handling of empty input:
read -s -N 1 -t 10 -p "What is your name? > " a || echo "(timeout)" \
&& [ -n "$a" ] && read -ei "$a" b || echo \
&& echo "Your name is \"$b\""
Elaborate Answer:
With the help of @chepner's answer (thanks for the -ei option!) and a comment of @paul-hodges, which has lead me to an article promoting the -s read option, I was able to create a working solution very similar to my original 2-liner:
read -N 1 -t 10 -s -p "What is your name? > " a
[ "$a" != "" ] && read -ei "$a" b && echo "Your name is $b" || echo "(timeout)"
Some of you might like a more elaborate version of the same functionality:
if read -N 1 -t 10 -s -p "What is your name? " FIRST_CHARACTER; then
read -ei "$FIRST_CHARACTER" FULL_NAME
echo "Your name is $FULL_NAME"
else
echo "(timeout)"
fi
Explanation:
- the
-soption in the first read command will make sure the FIRST_CHARACTER is not printed out while typing. - the
-N 1or-n1option will make sure that only the first character is read into the FIRST_CHARACTER variable - the
-eioption will read$FIRST_CHARACTERinto the FULL_NAME before the user continues to write the characters 2 to n. - the user is able to reconsider his answer and he can remove the whole input including the first character with the backspace.
I have testet it, and the combination of those options seems to do the trick.
Resolving a Caveat with empty input
However, there is still a small caveat: if the user just types <enter>: the second read command will wait for an input until the user is pressing <enter> a second time. This can be fixed like follows:
if read -N 1 -t 10 -s -p "What is your name? " FIRST_CHARACTER; then
if [ -n "$FIRST_CHARACTER" ]; then
read -ei "$FIRST_CHARACTER" FULL_NAME
else
echo
fi
echo "Your name is \"$FULL_NAME\""
else
echo "(timeout)"
fi
In the style of the two-liner, this will get us a three-liner as follows:
read -N 1 -t 10 -s -p "What is your name? > " a || echo "(timeout)" \
&& [ -n "$a" ] && read -ei "$a" b || echo \
&& echo "Your name is \"$b\""
Test
The code of both versions (the nested if version and the three-liner) will behave as follows:
- If the user does not do anything for 10 sec, the output will yield
What is your name? (timeout)
- If the user writes
Oliver<enter>the output will be
What is your name? Oliver
Your name is "Oliver"
- if the user starts to write "Oliver", then considers, that he want to be called "Michael", he can completely remove the "Oliver" with the backspace key and replace it accordingly. The output will be:
What is your name? Oliver
after entering the name "Oliver". Then, after pressing the backspace key 6 or more times:
What is your name?
And after entering Michael<enter>:
What is your name? Michael
Your name is "Michael"
Hope that helps.
来源:https://stackoverflow.com/questions/55747232/bash-read-timeout-only-for-the-first-character-entered-by-the-user