Reputation: 1651
I have searched for a simple solution that will read user input with the following features:
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?
Upvotes: 3
Views: 681
Reputation: 1651
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\""
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:
-s
option in the first read command will make sure the FIRST_CHARACTER is not printed out while typing.-N 1
or -n1
option will make sure that only the first character is read into the FIRST_CHARACTER variable-ei
option will read $FIRST_CHARACTER
into the FULL_NAME before the user continues to write the characters 2 to n.I have testet it, and the combination of those options seems to do the trick.
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\""
The code of both versions (the nested if version and the three-liner) will behave as follows:
What is your name? (timeout)
Oliver<enter>
the output will beWhat is your name? Oliver
Your name is "Oliver"
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.
Upvotes: 0
Reputation: 21965
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.
Upvotes: 1
Reputation: 531480
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).
Upvotes: 1
Reputation: 94
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
Upvotes: 0