bash只读取用户输入的第一个字符的超时

bash read timeout only for the first character entered by the user

我已经搜索了一个简单的解决方案,它将读取具有以下功能的用户输入:

  • 10秒后超时,如果完全没有用户输入
  • 如果在前10秒内键入了第一个字符,则用户有无限的时间来完成其回答。

我在Linux上找到了一个类似的请求(每键入一个字符后超时)的解决方案read-timeout after x seconds*idle*。不过,这并不是我想要的功能,因此我开发了一个两行解决方案,如下所示:

1
2
read -N 1 -t 10 -p"What is your name? >" a
["$a" !="" ] && read b && echo"Your name is $a$b" || echo"(timeout)"

如果用户在输入第一个字符前等待10秒,则输出将为:

1
What is your name? > (timeout)

如果用户在10秒内键入第一个字符,他将有无限的时间来完成此任务。输出如下:

1
2
What is your name? > Oliver
Your name is Oliver

但是,需要注意的是:第一个字符一旦输入就不可编辑,而所有其他字符都可以编辑(退格键和重新输入)。

您对警告的解决有什么想法吗,或者您对请求的行为有其他简单的解决方案吗?


这个解决方案可以。

1
2
3
read -n1 -t 10 -p"Enter Name :" name && echo -en"
"
&&
read -e -i"$name" -p"Enter Name :" name || echo"(timeout)"

注:第二个read使用从第一个(-i选项)捕获的文本提供可编辑缓冲区。回车和相同的提示会给用户留下输入相同值的印象。


启用readline并添加$a作为第二次读取的默认值。

1
2
3
4
5
6
7
8
9
10
# 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

在第一个字母被回答后,它仍然显示第二个提示,但我认为没有更好的方法来处理这个问题;没有办法"取消"read的超时。理想的解决方案是使用除read之外的一些命令,但您必须自己编写(可能是作为可加载的内置组件,在C中)。


简短回答:

在第一个read命令中添加一个-s选项,在第二个read命令中添加一个-ei选项:

1
2
read -s -N 1 -t 10 -p"What is your name? >" a
["$a" !="" ] && read -ei"$a" b && echo"Your name is $b" || echo"(timeout)"

或者更好地处理空输入:

1
2
3
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""

详细回答:

在@chepner回答的帮助下(感谢-ei选项!)在@paul hodges的评论中,我提到了一篇关于-sread选项的文章,我创建了一个与原来的2-liner非常相似的工作解决方案:

1
2
read -N 1 -t 10 -s -p"What is your name? >" a
["$a" !="" ] && read -ei"$a" b && echo"Your name is $b" || echo"(timeout)"

你们中的一些人可能会喜欢同样功能的更详细的版本:

1
2
3
4
5
6
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

说明:

  • 第一个read命令中的-s选项将确保在键入时不打印第一个字符。
  • -N 1-n1选项将确保只有第一个字符被读取到第一个字符变量中。
  • 在用户继续将字符2写入n之前,-ei选项将把$FIRST_CHARACTER读入全名。
  • 用户可以重新考虑他的答案,他可以删除整个输入,包括退格键的第一个字符。

我已经测试过了,这些选项的组合似乎起到了作用。

用空输入解决警告

但是,还有一个小警告:如果用户只键入:第二个read命令将等待输入,直到用户第二次按。可以按如下方式修复:

1
2
3
4
5
6
7
8
9
10
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

在双衬板的样式中,这将为我们提供一个三衬板,如下所示:

1
2
3
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""

试验

两个版本的代码(嵌套的if版本和三行程序)的行为如下:

  • 如果用户在10秒内不做任何事情,输出将产生
1
What is your name? (timeout)
  • 如果用户写入Oliver,则输出将为
1
2
What is your name? Oliver
Your name is"Oliver"
  • 如果用户开始写"奥利弗",然后认为他想被称为"迈克尔",他可以用退格键完全删除"奥利弗",并相应地替换它。输出将是:
1
What is your name? Oliver

在输入"奥利弗"的名字后。然后,在按退格键6次或更多次后:

1
What is your name?

进入Michael后:

1
2
What is your name? Michael
Your name is"Michael"

希望有帮助。


试验条件:GNU bash,版本4.4.19(1)-发布Ubuntu 18.04.2升

我创建了一个函数来解决第一个字母不可编辑的警告,如下所示。我只在我的本地Linux服务器上测试过这个版本,并且我没有假设这个版本在其他地方或者在新的/旧的bash版本上可以工作(或者为此进行读取,但是我无法知道我运行的是哪个版本)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
__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
}

您可以调用类似以下的函数:

1
2
3
4
5
6
7
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

上述警告已解释所以,你有一个警告,我修复了,也许你(或我们的朋友)可以修复这个。输入的任何未包含在$rgregex变量中的字符都将被视为退格符。这意味着您的用户可以点击F7=\,或者字面上任何字符,而不是$rg中指定的字符,它将被视为退格。