如何在Linux shell脚本中提示用户输入Yes/No/Cancel?

1861
我想暂停shell脚本中的输入,并提示用户进行选择,例如标准的“是”、“否”或“取消”类型的问题。在典型的bash提示符下,如何实现这一点?

40
请注意:提示符的惯例是如果您提供了 [yn] 选项,则大写字母是默认值。即[Yn] 默认为“yes”,[yN] 默认为“no”。请参见https://ux.stackexchange.com/a/40445/43532 - Tyzoid
4
如果有从ZSH过来的人,请参考这个答案以了解如何使用read命令进行提示。 - smac89
2
你也可以考虑查看我在 U&L.SE 上的相关问答,了解在 bash 中暂停的规范方式。提供的结果可以轻松转移。 - Cadoiz
39个回答

3
yn() {
  if [[ 'y' == `read -s -n 1 -p "[y/n]: " Y; echo $Y` ]];
  then eval $1;
  else eval $2;
  fi }
yn 'echo yes' 'echo no'
yn 'echo absent no function works too!'

这似乎很复杂且脆弱。不如只使用 yn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure - tripleee

3

一个简单的方法是使用xargs -p或gnu parallel --interactive

我更喜欢使用xargs,因为它的行为更好,它像其他交互式Unix命令一样立即在提示符后执行每个命令,而不是在最后收集确认来运行。(你可以在完成你想要的操作后按Ctrl-C。)

例如:

echo *.xml | xargs -p -n 1 -J {} mv {} backup/

还不错,但是 xargs --interactive 只能回答是或否。只要这就是你所需要的,那么这就足够了,但是我的原始问题给出了三种可能的结果。我真的很喜欢它可以流式传输;许多常见的场景都可以从它的管道能力中受益。 - Myrddin Emrys
我明白了。我的想法是,“取消”意味着简单地停止所有进一步的执行,这通过Ctrl-C得到支持,但如果您需要在取消(或不取消)时执行更复杂的操作,则这不足够。 - Joshua Goldberg

3
我曾多次在这种情况下使用过case语句,使用case语句是一个不错的方法。可以实现一个while循环来封装case块,并利用布尔条件来控制程序,以满足更多的要求。当所有条件都满足时,可以使用break将控制权传回程序的主要部分。此外,为了满足其他条件,当然可以添加条件语句来配合控制结构:case语句和可能的while循环。
以下是使用case语句来满足您的请求的示例:
#! /bin/sh 

# For potential users of BSD, or other systems who do not
# have a bash binary located in /bin the script will be directed to
# a bourne-shell, e.g. /bin/sh

# NOTE: It would seem best for handling user entry errors or
# exceptions, to put the decision required by the input 
# of the prompt in a case statement (case control structure), 

echo Would you like us to perform the option: "(Y|N)"

read inPut

case $inPut in
    # echoing a command encapsulated by 
    # backticks (``) executes the command
    "Y") echo `Do something crazy`
    ;;
    # depending on the scenario, execute the other option
    # or leave as default
    "N") echo `execute another option`
    ;;
esac

exit

2

回复其他人:

BASH4中不需要指定大小写,只需使用',,'将变量转换为小写即可。此外,我强烈反对在read块内放置代码,最好在read块外处理结果。还可以包含一个“q”以退出程序。最后,为什么要输入“yes”,只需使用-n1并按y键即可。

示例:用户可以按y / n键,也可以按q键退出。

ans=''
while true; do
    read -p "So is MikeQ the greatest or what (y/n/q) ?" -n1 ans
    case ${ans,,} in
        y|n|q) break;;
        *) echo "Answer y for yes / n for no  or q for quit.";;
    esac
done

echo -e "\nAnswer = $ans"

if [[ "${ans,,}" == "q" ]] ; then
        echo "OK Quitting, we will assume that he is"
        exit 0
fi

if [[ "${ans,,}" == "y" ]] ; then
        echo "MikeQ is the greatest!!"
else
        echo "No? MikeQ is not the greatest?"
fi

1

使用 PyInquirer 实现的一个Python单行代码替代方案

python3 -c 'import PyInquirer; print(PyInquirer.prompt([{"type":"confirm", "message":"Do you want to continue?", "name":"r"}]).get("r"))'

该程序支持是/否/取消 (按键intr, CTRL+C)。

输入图像描述


1
您可以编写一个函数进行测试:
confirm() {
  local ans IFS=;
  while read -rp "$1" -n1 ans;
  do printf '\n';
    case $ans in [Yy]) return 0;;
      [Nn]) return 1;;
    esac;
  done;
}; ## Usage: if confirm "Are you sure? "; then ...

if confirm "Does everything look ok...reboot now? [Y/n]"; then
  echo "rebooting..."
  sleep 5
  reboot
fi

1

0
这是另一种方法,使用一个返回0表示成功,1表示失败的函数。没有重新提示;输入y/Y确认,其他任何输入都会中止。
#!/usr/bin/env bash

set -eu -o pipefail

function prompt() {
    read -p "$* [Y/n]: " yn
    if [[ $yn = "y" || $yn = "Y" ]]; then
        return 0
    else 
        return 1
    fi
}

prompt "one liner?" && echo "YES" || echo "NO"

if prompt "in an if?"; then
    echo "YES"
else
    echo "NO"
fi

0

我创建了这个函数,以便由我所有需要是yes/no/true/false/default响应的bash脚本调用,因为我厌倦了每次需要响应时都重新创建代码。

它具有可设置的问题和可设置的默认答案,对于默认响应也有错误处理(意味着您可以更改默认答案),同时捕获未被引号包围的问题。我意识到这比原始帖子要求的更详细,但在任何bash 4+中,它应该是完全可移植的。它也可以作为if语句中的命令使用,因为它返回0或1,并详细说明了答案以进行调试。

用法:confirm -d n "是否是答案?"

返回输出:

user@ubuntu# confirm -d n "Is this the answer?"
Is this the answer? [Y/n:default=N]: 
default no

这是函数:

confirm(){
  errorMsg='
  Too many arguments.
  use "-d" as the first option to set default value.
  If providing a question, remember to quote the line to prevent each word from being an argument.
  example: confirm -d n "Is this the answer?"'
  defSet=false
  while [ "$#" -ge 1 ]; do
    if [ "$#" -ge 2 ]; then
      confirmArg=$1
      if [[ "$confirmArg" = '-d' ]]; then
        confirmArgCheck=$2
        errorMsg2="
        '${confirmArgCheck}' is not a valid default return value.
        Must be [Yy]es/[Nn]o or [Tt]rue/[Ff]alse"
        case $confirmArgCheck in
          [Yy]*|[Tt]*)
            regex1="[Yy][Ee]?[Ss]?$"
            regex2="[Tt][Rr]?[Uu]?[Ee]?$"
            if [[ "$confirmArgCheck" =~ $regex1 ]]; then
              unset regex1 && unset regex2
              confirmArgVal=Y
            elif [[ "$confirmArgCheck" =~ $regex2 ]]; then
              unset regex1 && unset regex2
              confirmArgVal=Y
            else
              unset regex1 && unset regex2
              echo "$errorMsg2"
              return 1
            fi
            ;;
          [Nn]*|[Ff]*)
            regex1="[Nn][Oo]?$"
            regex2="[Ff][Aa]?[Ll]?[Ss]?[Ee]?$"
            if [[ "$confirmArgCheck" =~ $regex1 ]]; then
              unset regex1 && unset regex2
              confirmArgVal=N
            elif [[ "$confirmArgCheck" =~ $regex2 ]]; then
              unset regex1 && unset regex2
              confirmArgVal=N
            else
              unset regex1 && unset regex2
              echo "$errorMsg2"
              return 1
            fi
            ;;
          *)
            echo "$errorMsg2"
            return 1
            ;;
        esac
        defSet=true
        shift 2
      else
        if [[ ! "$*" == *"-d"* ]]; then
          shiftmore="$#"
          quest="$@"
          questSet=true
          shift "$shiftmore"
        else
          echo "$errorMsg"
          return 1
        fi
      fi
    else
      quest=$1
      questSet=true
      shift 1
    fi
  done
  if [[ ! $defSet = true ]]; then
    confirmArgVal=Y
  fi
  if [[ ! $questSet = true ]]; then
    quest='Answer?'
  fi
  while true; do
    read -r -p "${quest} [Y/n:default=$confirmArgVal]: " yn
    case $yn in
      [Yy]*)
        unset quest
        unset yn
        unset confirmArgVal
        echo "answered yes"
        return 0
        ;;
      [Nn]*)
        unset quest
        unset yn
        unset confirmArgVal
        echo "answered no"
        return 1
        ;;
      "")
        unset quest
        unset yn
        if [[ "$confirmArgVal" = 'Y' ]]; then
          unset confirmArgVal
          echo "default yes"
          return 0
        else
          unset confirmArgVal
          echo "default no"
          return 1
        fi
        ;;
      *)
        echo "Please answer yes or no."
        ;;
    esac
  done
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接