在 Bash 脚本中询问是/否问题

作者:Mitch Frazier

为了避免这种常见的错误,我经常让我的 shell 脚本在继续之前提示我回答“是”或“否”。 这里描述的函数就是用来做这件事的:提出一个问题并验证答案。

该函数非常简单,它接受几个选项,其余参数被认为是问题文本。它提示用户输入答案,并验证答案是否为“yes”、“y”、“no”或“n”之一。答案被转换为小写,因此接受任何大小写组合。

可用的选项有--timeout N这会导致提示在 N 秒后超时,以及--default ANS这为超时的提示提供了一个默认答案。它还允许用户按 ENTER 并接受默认答案。 函数代码如下

#!/bin/bash.sh
#


#####################################################################
# Print warning message.

function warning()
{
    echo "$*" >&2
}

#####################################################################
# Print error message and exit.

function error()
{
    echo "$*" >&2
    exit 1
}


#####################################################################
# Ask yesno question.
#
# Usage: yesno OPTIONS QUESTION
#
#   Options:
#     --timeout N    Timeout if no input seen in N seconds.
#     --default ANS  Use ANS as the default answer on timeout or
#                    if an empty answer is provided.
#
# Exit status is the answer.

function yesno()
{
    local ans
    local ok=0
    local timeout=0
    local default
    local t

    while [[ "$1" ]]
    do
        case "$1" in
        --default)
            shift
            default=$1
            if [[ ! "$default" ]]; then error "Missing default value"; fi
            t=$(tr '[:upper:]' '[:lower:]' <<<$default)

            if [[ "$t" != 'y'  &&  "$t" != 'yes'  &&  "$t" != 'n'  &&  "$t" != 'no' ]]; then
                error "Illegal default answer: $default"
            fi
            default=$t
            shift
            ;;

        --timeout)
            shift
            timeout=$1
            if [[ ! "$timeout" ]]; then error "Missing timeout value"; fi
            if [[ ! "$timeout" =~ ^[0-9][0-9]*$ ]]; then error "Illegal timeout value: $timeout"; fi
            shift
            ;;

        -*)
            error "Unrecognized option: $1"
            ;;

        *)
            break
            ;;
        esac
    done

    if [[ $timeout -ne 0  &&  ! "$default" ]]; then
        error "Non-zero timeout requires a default answer"
    fi

    if [[ ! "$*" ]]; then error "Missing question"; fi

    while [[ $ok -eq 0 ]]
    do
        if [[ $timeout -ne 0 ]]; then
            if ! read -t $timeout -p "$*" ans; then
                ans=$default
            else
                # Turn off timeout if answer entered.
                timeout=0
                if [[ ! "$ans" ]]; then ans=$default; fi
            fi
        else
            read -p "$*" ans
            if [[ ! "$ans" ]]; then
                ans=$default
            else
                ans=$(tr '[:upper:]' '[:lower:]' <<<$ans)
            fi 
        fi

        if [[ "$ans" == 'y'  ||  "$ans" == 'yes'  ||  "$ans" == 'n'  ||  "$ans" == 'no' ]]; then
            ok=1
        fi

        if [[ $ok -eq 0 ]]; then warning "Valid answers are: yes y no n"; fi
    done
    [[ "$ans" = "y" || "$ans" == "yes" ]]
}

if [[ $(basename "$0" .sh) == 'yesno' ]]; then
    if yesno "Test bad timeout value? "; then
        yesno --timeout none "Hello? "
    fi
    if yesno "Test timeout without default value? "; then
        yesno --timeout 10 "Hello? "
    fi
    if yesno "Test bad default value? "; then
        yesno --default none "Hello? "
    fi

    if yesno "Yes or no? "; then
        echo "You answered yes"
    else
        echo "You answered no"
    fi
    if yesno --default yes "Yes or no (default yes) ? "; then
        echo "You answered yes"
    else
        echo "You answered no"
    fi
    if yesno --default no "Yes or no (default no) ? "; then
        echo "You answered yes"
    else
        echo "You answered no"
    fi
    if yesno --timeout 5 --default no "Yes or no (timeout 5, default no) ? "; then
        echo "You answered yes"
    else
        echo "You answered no"
    fi
    if yesno --timeout 5 --default yes "Yes or no (timeout 5, default yes) ? "; then
        echo "You answered yes"
    else
        echo "You answered no"
    fi
fi


# vim: tabstop=4: shiftwidth=4: noexpandtab:
# kate: tab-width 4; indent-width 4; replace-tabs false;

代码以几个函数开始,这些函数打印警告和错误消息。主函数检查参数,然后循环直到收到有效答案。请注意,如果指定了超时并且输入了任何答案(有效或无效),则超时将被关闭。该函数的最后一行测试答案,查看其值是否为“yes”或“y”,从而设置函数的退出状态。

文件末尾的代码仅在您直接调用文件而不是将其源到 shell 脚本中时执行。 直接运行的输出如下

$ sh yesno.sh
Test bad timeout value? n
Test timeout without default value? n
Test bad default value? n
Yes or no? yep
Valid answers are: yes y no n
Yes or no? yes
You answered yes
Yes or no (default yes) ? <ENTER>
You answered yes
Yes or no (default no) ? <ENTER>
You answered no
Yes or no (timeout 5, default no) ? You answered no
Yes or no (timeout 5, default yes) ? You answered yes

请注意最后几行,没有提供答案,因此在超时后使用了默认值。 这就是为什么响应文本与问题出现在同一行上的原因。

Mitch Frazier 是 Emerson Electric Co. 的一名嵌入式系统程序员。自 2000 年代初期以来,Mitch 一直是 Linux Journal 的贡献者和朋友。

加载 Disqus 评论