【问题标题】:bash: choose default from case when enter is pressed in a "select" promptbash:在“选择”提示中按下回车时从案例中选择默认值
【发布时间】:2017-08-04 23:35:00
【问题描述】:

我在这样的 bash 脚本中提示问题:

optionsAudits=("Yep" "Nope")
    echo "Include audits?"
    select opt in "${optionsAudits[@]}"; do
        case $REPLY in
            1) includeAudits=true; break ;;
            2) includeAudits=false; break ;;
            "\n") echo "You pressed enter"; break ;; # <--- doesn't work
            *) echo "What's that?"; exit;;
        esac
    done

按回车键时如何选择默认选项? "\n" 大小写不捕获回车键。

【问题讨论】:

  • 对未来读者的补充:select 语句的主体是不寻常的,因为它检查原始用户输入 - $REPLY - 而不是 select 语句的输出变量 $opt (其中包含所选选项的 text,而不是 index)。

标签: bash user-input interactive


【解决方案1】:

更新答案:

echo "Include audits? 1) Yep, 2) Nope"
read ans
case $ans in
    Yep|1  )  echo "yes"; includeAudits=true; v=1 ;;
    Nope|2 )  echo "no"; includeAudits=false; v=2 ;;
    ""     )  echo "default - yes"; includeAudits=true; v=1 ;;
    *      )  echo "Whats that?"; exit ;;
esac

这接受"Yep""1""enter" 选择yes,接受"Nope""2" 否,并丢弃任何其他内容。它还将 v 设置为 1 或 2,具体取决于用户是否想要是或否。

【讨论】:

  • 您尚未展示如何处理ans 的默认值。
  • @chepner,嗯? *) 通过 *) 案例处理空字符串,这正是 OP 所要求的。虽然它不完全等同于select,因为它不会写出提示或将数字映射到字符串结果,所以肯定有改进/扩展的空间。
  • * 处理除YepNope 之外的所有内容,包括无效输入。如果空字符串确实是输入,这实际上也不会为 ans 分配默认值。
  • *) 之后有一个退出,这意味着之后 $ans 不会被使用...(这又是 OP 所追求的...)
  • 抱歉我的问题不太清楚。我正在寻找与我发布的功能相同的功能(能够选择“1”或“2”)以及按 Enter 的附加选项,在这种情况下,我自己会为用户选择选项 1 或 2。
【解决方案2】:

您的问题是由于select 将忽略空输入。对于您的情况,read 会更合适,但您将失去select 提供的用于自动创建菜单的实用程序。

要模仿select 的行为,您可以这样做:

#!/bin/bash
optionsAudits=("Yep" "Nope")
while : #infinite loop. be sure to break out of it when a valid choice is made
do
    i=1
    echo "Include Audits?"
    #we recreate manually the menu here
    for o in  "${optionsAudits[@]}"; do
        echo "$i) $o"
        let i++
    done

    read reply
    #the user can either type the option number or copy the option text
    case $reply in
        "1"|"${optionsAudits[0]}") includeAudits=true; break;;
        "2"|"${optionsAudits[1]}") includeAudits=false; break;;
        "") echo "empty"; break;;
        *) echo "Invalid choice. Please choose an existing option number.";;
    esac
done
echo "choice : \"$reply\""

【讨论】:

  • 你也可以使用这个workabout来代替循环“for”:“echo foo | select foo in "${optionsAudits[@]}"; do break; done" 它产生更多的列菜单,如果你喜欢...
【解决方案3】:

补充Aserre's helpful answer,它解释了您的代码问题并提供了一种有效的解决方法,具有背景信息和允许空输入的通用、可重复使用的自定义select 实现


背景资料

明确说明:select本身忽略空输入(只需按Enter)并重新提示 - 用户代码甚至没有响应运行。

事实上,select 使用空字符串向用户代码发出信号表明输入了无效选项
也就是说,如果输出变量 - $opt,在这种情况下 - 在 select 语句中为 empty,则意味着用户键入了无效的选择索引。

输出变量接收所选选项的文本——在本例中为'Yep''Nope'——不是索引 由用户输入。

(相比之下,您的代码检查 $REPLY 而不是输出变量,它包含用户键入的内容,如果有效, 索引选择,但可能包含额外的前导和尾随空格)。

请注意,如果您不想允许空输入,您可以 只需在提示文本中向用户表明 ^C (Ctrl+C) 可用于中止提示


通用自定义 select 函数也接受空输入

以下函数与select 的功能非常相似,同时也允许空输入(只需按Enter)。请注意,该函数会拦截无效输入,打印警告并重新提示:

# Custom `select` implementation that allows *empty* input.
# Pass the choices as individual arguments.
# Output is the chosen item, or "", if the user just pressed ENTER.
# Example:
#    choice=$(selectWithDefault 'one' 'two' 'three')
selectWithDefault() {

  local item i=0 numItems=$# 

  # Print numbered menu items, based on the arguments passed.
  for item; do         # Short for: for item in "$@"; do
    printf '%s\n' "$((++i))) $item"
  done >&2 # Print to stderr, as `select` does.

  # Prompt the user for the index of the desired item.
  while :; do
    printf %s "${PS3-#? }" >&2 # Print the prompt string to stderr, as `select` does.
    read -r index
    # Make sure that the input is either empty or that a valid index was entered.
    [[ -z $index ]] && break  # empty input
    (( index >= 1 && index <= numItems )) 2>/dev/null || { echo "Invalid selection. Please try again." >&2; continue; }
    break
  done

  # Output the selected item, if any.
  [[ -n $index ]] && printf %s "${@: index:1}"

}

你可以这样称呼它:

# Print the prompt message and call the custom select function.
echo "Include audits (default is 'Nope')?"
optionsAudits=('Yep' 'Nope')
opt=$(selectWithDefault "${optionsAudits[@]}")

# Process the selected item.
case $opt in
  'Yep') includeAudits=true; ;;
  ''|'Nope') includeAudits=false; ;; # $opt is '' if the user just pressed ENTER
esac

可选阅读:原始代码的更惯用版本

注意:此代码不能解决问题,但显示了select 语句的更惯用用法;与原始代码不同,如果做出了无效选择,此代码会重新显示提示:

optionsAudits=("Yep" "Nope")
echo "Include audits (^C to abort)?"
select opt in "${optionsAudits[@]}"; do
    # $opt being empty signals invalid input.
    [[ -n $opt ]] || { echo "What's that? Please try again." >&2; continue; }
    break # a valid choice was made, exit the prompt.
done

case $opt in  # $opt now contains the *text* of the chosen option
  'Yep')
     includeAudits=true
     ;;
  'Nope') # could be just `*` in this case.
     includeAudits=false
     ;;
esac

注意:

  • case 语句已从 select 语句中移出,因为后者现在保证只能进行有效输入。

  • case 语句测试 输出变量 ($opt) 而不是原始用户输入 ($REPLY),并且该变量包含选项 text,而不是它的 index

【讨论】:

  • 我几乎不知道从哪里开始 :) 你能解释一下从函数返回所选值的最后一行吗?
  • [[ -n $index ]] 测试变量$index 的值是否为“非空”(-n);如果它是非空的(即有一个值),则评估 &amp;&amp; 控制运算符的 RHS。 printf %s "${@: index:1}" 打印索引为$index 的位置参数(参数):$@ 是所有位置参数的数组(传递给函数的参数,即选项)。切片语法${&lt;array-var&gt;[@] &lt;index&gt;:&lt;length&gt;} 提取感兴趣的元素。请注意,特殊数组 $@ - 与常规 Bash 数组不同 - 基于 1
  • 我能否将选择选项作为数组传递给函数,以便我也可以将哪个选项作为默认选项传递给它?
  • 没有技巧,你不能将数组 as such 传递给函数。将诸如"${optionsAudits[@]}" 之类的数组(请参阅我的更新)传递给函数实际上将其元素作为单独的参数(位置参数)传递。添加一个机制来指定默认元素会使函数复杂化,我不确定它是否值得做。如果您确实想解决它:将第一个或最后一个位置参数指定为默认值,或者为默认元素添加一个唯一后缀以将其标记为此类(函数需要解析和删除)。
  • 是的,我明白了,这似乎是 bash 中的一个难题。还有一个问题:为什么“回声”在这个函数中不起作用?为什么需要 ">&2" 才能将内容打印到 stdout/stderr?
【解决方案4】:

这将满足您的要求。

options=("option 1" "option 2");
while :
do
    echo "Select your option:"
    i=1;
    for opt in  "${options[@]}"; do
        echo "$i) $opt";
        let i++;
    done

    read reply
    case $reply in
        "1"|"${options[0]}"|"")
          doSomething1();
          break;;
        "2"|"${options[1]}")
          doSomething2();
          break;;
        *)
          echo "Invalid choice. Please choose 1 or 2";;
    esac
done

【讨论】:

    【解决方案5】:

    假设您的默认选项是Yep

    #!/bin/bash
    optionsAudits=("Yep" "Nope")
    while : #infinite loop. be sure to break out of it when a valid choice is made
    do
        i=1
        echo "Include Audits?: "
        #we recreate manually the menu here
        for o in  "${optionsAudits[@]}"; do
            echo "  $i) $o"
            let i++
        done
    
        read -rp "Audit option: " -iYep
        #the user can either type the option number or copy the option text
        case $REPLY in
            "1"|"${optionsAudits[0]}") includeAudits=true; break;;
            "2"|"${optionsAudits[1]}") includeAudits=false; break;;
            "") includeAudits=true; break;;
            *) echo "Invalid choice. Please choose an existing option number.";;
        esac
    done
    echo "choice : \"$REPLY\""
    echo "includeAudits : \"$includeAudits\""
    

    注意到这一行:

        read -rp "Audit option: " -eiYep
    

    我还将$reply 拉到$REPLY,以便更好地进行案件判决。

    点击 ENTER 后输出现在看起来像这样:

    Include Audits?: 
      1) Yep
      2) Nope
    Audit option: Yep
    choice : ""
    includeAudits : "true"
    # 
    

    作为对select 的增强,read -eiYep 将预先将Yep 的默认值提供给输入缓冲区。

    将默认值放在前面的唯一缺点是必须按几次退格键才能输入自己的答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-28
      相关资源
      最近更新 更多