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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bash只读取用户输入的第一个字符的超时相关的知识,希望对你有一定的参考价值。

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

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

我在Linux Read - Timeout after x seconds *idle*上找到了类似请求的解决方案(每个类型字符后超时)。不过,这不是我想要的功能,所以我开发了如下两行解决方案:

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

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

What is your name? > (timeout)

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

What is your name? > Oliver
Your name is Oliver

但是,有以下警告:第一个字符在输入后不可编辑,而所有其他字符都可以编辑(退格和重新键入)。

您是否有任何解决警告的想法,或者您对请求的行为有另一个简单的解决方案吗?

答案

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

# 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 -n1 -t 10 -p "Enter Name : " name && echo -en "\r" &&
read -e -i "$name" -p "Enter Name : " name || echo "(timeout)"

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

另一答案

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

我创建了一个函数来解决你不能编辑的第一个字母的警告,如下所示。我只用我的本地linux服务器对它进行了测试,并且我没有假设这可以在其他地方使用或者使用更新/更旧版本的BASH(或者读取这个问题,但是我无法分辨我运行的版本)

__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
}

您可以调用此函数,类似于以下内容:

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所以,你有一个警告,我修复了,也许你(或我们的朋友)可以解决这个问题。输入的任何未包含在$rg REGEX变量中的字符将被视为BACKSPACE。这意味着你的用户可以点击F7=\,或者除了在$rg中指定的字符以外的任何字符,它将被视为退格

另一答案

Short Answer:

在第一个读取命令上添加-s选项,在第二个读取命令上添加-ei选项:

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

或者更好地处理空输入:

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:

在@chepner的答案的帮助下(感谢-ei选项!)和@ paul-hodges的评论,这引导我发表了一篇推广-s阅读选项的文章,我能够创建一个非常类似于我原创的工作解决方案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)"

有些人可能会喜欢相同功能的更复杂版本:

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

说明:

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

我已经测试过了,这些选项的组合似乎可以解决问题。

用空输入解决一个警告

但是,仍然有一个小警告:如果用户只键入<enter>:第二个读命令将等待输入,直到用户第二次按<enter>。这可以修复如下:

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

在双线的风格,这将给我们一个三线,如下:

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秒内没有做任何事情,输出将产生
What is your name? (timeout)
  • 如果用户写Oliver<enter>,输出将是
What is your name? Oliver
Your name is "Oliver"
  • 如果用户开始写“Oliver”,然后认为他想要被称为“Michael”,他可以用退格键完全删除“Oliver”并相应地替换它。输出将是:
What is your name? Oliver

输入“Oliver”这个名字后。然后,按下退格键6次或更多次后:

What is your name?

进入Michael<enter>之后:

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

希望有所帮助。

以上是关于bash只读取用户输入的第一个字符的超时的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Bash 读取超时?

While 循环在 Bash 的第一行之后停止读取

bash脚本编程之用户交互

如何在 shell 脚本中只读取一个字符

bash-read使用方法

bash-read使用方法