我想暂停shell脚本中的输入,并提示用户进行选择。标准的是、否或取消类型问题。如何在典型的bash提示符中完成此操作?


当前回答

此解决方案读取单个字符,并在yes响应时调用函数。

read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    do_something      
fi

其他回答

受到@Mark和@Myrdin的回答的启发,我创建了一个通用提示的函数

uniprompt(){
    while true; do
        echo -e "$1\c"
        read opt
        array=($2)
        case "${array[@]}" in  *"$opt"*) eval "$3=$opt";return 0;; esac
        echo -e "$opt is not a correct value\n"
    done
}

这样使用:

unipromtp "Select an option: (a)-Do one (x)->Do two (f)->Do three : " "a x f" selection
echo "$selection"

可以在POSIX shell中处理区域设置感知的“是/否选项”;通过使用LC_MESSAGES语言环境类别的条目,witch提供了与输入匹配的现成RegEx模式,以及本地化Yes No的字符串。

#!/usr/bin/env sh

# Getting LC_MESSAGES values into variables
# shellcheck disable=SC2046 # Intended IFS splitting
IFS='
' set -- $(locale LC_MESSAGES)

yesexpr="$1"
noexpr="$2"
yesstr="$3"
nostr="$4"
messages_codeset="$5" # unused here, but kept as documentation

# Display Yes / No ? prompt into locale
echo "$yesstr / $nostr ?"

# Read answer
read -r yn

# Test answer
case "$yn" in
# match only work with the character class from the expression
  ${yesexpr##^}) echo "answer $yesstr" ;;
  ${noexpr##^}) echo "answer $nostr" ;;
esac

编辑:正如@Urhixidur在评论中提到的:

不幸的是,POSIX只指定了前两个(yeexpr和noexpr)。在Ubuntu 16上,yestr和nostr为空。

参见:https://www.ee.ryerson.ca/~courses/ele709/susv4/xrat/V_4_xbd_chap07.html#tag_21_07_03_06

LC_消息yestr和nostr语言环境关键字以及yesstr和nostr语言信息项以前用于匹配用户的肯定和否定响应。在POSIX.1-2008中,yeexpr、noexpr、yesexpr和noexpr扩展正则表达式已取代它们。应用程序应该使用通用的基于语言环境的消息传递工具来发出包含所需响应示例的提示消息。

或者使用Bash方式的区域设置:

#!/usr/bin/env bash

IFS=$'\n' read -r -d '' yesexpr noexpr _ < <(locale LC_MESSAGES)

printf -v yes_or_no_regex "(%s)|(%s)" "$yesexpr" "$noexpr"

printf -v prompt $"Please answer Yes (%s) or No (%s): " "$yesexpr" "$noexpr"

declare -- answer=;

until [[ "$answer" =~ $yes_or_no_regex ]]; do
  read -rp "$prompt" answer
done

if [[ -n "${BASH_REMATCH[1]}" ]]; then
  echo $"You answered: Yes"
else
  echo $"No, was your answer."
fi

使用语言环境提供的regexp来匹配答案。

要翻译剩余的消息,请使用bash--dump po string scriptname输出po字符串以进行本地化:

#: scriptname:8
msgid "Please answer Yes (%s) or No (%s): "
msgstr ""
#: scriptname:17
msgid "You answered: Yes"
msgstr ""
#: scriptname:19
msgid "No, was your answer."
msgstr ""

绝对最简单的解决方案是这一行,没有巧妙的技巧:

read -p "press enter ..." y

它让人想起了经典的DOS点击任意键继续,除了它等待Enter键,而不仅仅是任何键。

的确,这并没有为您提供“是”“否”“取消”的三个选项,但在您接受control-C作为“否”的情况下,这很有用。在简单脚本中取消,例如:

#!/bin/sh
echo Backup this project
read -p "press enter ..." y
rsync -tavz . /media/hard_to_remember_path/backup/projects/yourproject/

因为您不需要记住难看的命令和路径,但也不需要记住运行速度过快的脚本,而不需要在您决定它不是您想要运行的脚本之前停止。

我为回答是/否问题编写了一个小脚本:https://github.com/optimistiCli/getans

例子:

#!/bin/bash

if ! getans.sh 'Shall we proceed?' y ; then
    echo "User said “NO”"
    exit 1
fi

echo "User said “YES”"
# do something usefull
exit 0

直接链接:https://github.com/optimistiCli/getans/raw/main/getans.sh

多选版本:

ask () {                        # $1=question $2=options
    # set REPLY
    # options: x=..|y=..
    while $(true); do
        printf '%s [%s] ' "$1" "$2"
        stty cbreak
        REPLY=$(dd if=/dev/tty bs=1 count=1 2> /dev/null)
        stty -cbreak
        test "$REPLY" != "$(printf '\n')" && printf '\n'
        (
            IFS='|'
            for o in $2; do
                if [ "$REPLY" = "${o%%=*}" ]; then
                    printf '\n'
                    break
                fi
            done
        ) | grep ^ > /dev/null && return
    done
}

例子:

$ ask 'continue?' 'y=yes|n=no|m=maybe'
continue? [y=yes|n=no|m=maybe] g
continue? [y=yes|n=no|m=maybe] k
continue? [y=yes|n=no|m=maybe] y
$

它会将REPLY设置为y(在脚本中)。