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

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

答案

在 shell 提示符下获取用户输入的最简单,最广泛使用的方法是read命令。演示其用法的最佳方法是一个简单的演示:

while true; do
    read -p "Do you wish to install this program?" yn
    case $yn in
        [Yy]* ) make install; break;;
        [Nn]* ) exit;;
        * ) echo "Please answer yes or no.";;
    esac
done

Steven Huwig 指出的另一种方法是 Bash 的select命令。这是使用select的相同示例:

echo "Do you wish to install this program?"
select yn in "Yes" "No"; do
    case $yn in
        Yes ) make install; break;;
        No ) exit;;
    esac
done

使用select您不需要清理输入 - 它显示可用的选项,然后键入与您的选择相对应的数字。它还会自动循环,因此如果它们提供了无效的输入,则无需再进行一次while true循环重试。

此外, LEA 格里斯表现出一种方法,使在请求语言无关她的回答 。修改我的第一个示例以更好地服务于多种语言可能看起来像这样:

set -- $(locale LC_MESSAGES)
yesptrn="$1"; noptrn="$2"; yesword="$3"; noword="$4"

while true; do
    read -p "Install (${yesword} / ${noword})? " yn
    case $yn in
        ${yesptrn##^} ) make install; break;;
        ${noptrn##^} ) exit;;
        * ) echo "Answer ${yesword} / ${noword}.";;
    esac
done

显然,这里没有翻译其他通信字符串(安装,回答),这需要通过更完整的翻译来解决,但是在许多情况下,即使是部分翻译也将有所帮助。

最后,请查看F. Hauri出色回答

一个通用问题至少有五个答案。

取决于

  • 兼容:可以在具有通用环境的较差系统上工作
  • 特定于 :使用所谓的bashisms

如果你想

  • 简单的 `` 在线 '' 问题 / 答案(通用解决方案)
  • 漂亮的格式化界面,例如或使用 libgtk 或 libqt 的更多图形化界面...
  • 使用强大的 readline 历史记录功能

1. POSIX 通用解决方案

您可以使用read命令,然后使用if ... then ... else

echo -n "Is this a good question (y/n)? "
read answer

# if echo "$answer" | grep -iq "^y" ;then

if [ "$answer" != "${answer#[Yy]}" ] ;then
    echo Yes
else
    echo No
fi

(感谢Adam Katz 的评论 :用更便携并且避免了一个分叉代替了上面的测试:)

POSIX,但具有单键功能

但是,如果您不希望用户点击Return ,则可以这样写:

编辑:正如 @JonathanLeffler 正确建议的那样, 保存 stty 的配置可能比简单地强制他们保持理智更好。)

echo -n "Is this a good question (y/n)? "
old_stty_cfg=$(stty -g)
stty raw -echo ; answer=$(head -c 1) ; stty $old_stty_cfg # Careful playing with stty
if echo "$answer" | grep -iq "^y" ;then
    echo Yes
else
    echo No
fi

注意:这是在

相同,但是显式等待yn

#/bin/sh
echo -n "Is this a good question (y/n)? "
old_stty_cfg=$(stty -g)
stty raw -echo
answer=$( while ! head -c 1 | grep -i '[ny]' ;do true ;done )
stty $old_stty_cfg
if echo "$answer" | grep -iq "^y" ;then
    echo Yes
else
    echo No
fi

使用专用工具

有许多使用libncurseslibgtklibqt或其他图形库构建的工具。例如,使用whiptail

if whiptail --yesno "Is this a good question" 20 60 ;then
    echo Yes
else
    echo No
fi

根据您的系统,您可能需要用另一个类似的工具来代替whiptail

dialog --yesno "Is this a good question" 20 60 && echo Yes

gdialog --yesno "Is this a good question" 20 60 && echo Yes

kdialog --yesno "Is this a good question" 20 60 && echo Yes

其中20是对话框的高度(以行数为单位), 60是对话框的宽度。这些工具都具有几乎相同的语法。

DIALOG=whiptail
if [ -x /usr/bin/gdialog ] ;then DIALOG=gdialog ; fi
if [ -x /usr/bin/xdialog ] ;then DIALOG=xdialog ; fi
...
$DIALOG --yesno ...

2. 特定于 Bash 的解决方案

基本在线方法

read -p "Is this a good question (y/n)? " answer
case ${answer:0:1} in
    y|Y )
        echo Yes
    ;;
    * )
        echo No
    ;;
esac

我更喜欢使用case ,所以我竟会测试yes | ja | si | oui如果需要...

符合 单键功能

在 bash 下,我们可以为read命令指定预期输入的长度:

read -n 1 -p "Is this a good question (y/n)? " answer

在 bash 下, read命令接受一个超时参数,这可能很有用。

read -t 3 -n 1 -p "Is this a good question (y/n)? " answer
[ -z "$answer" ] && answer="Yes"  # if 'yes' have to be default choice

专用工具的一些技巧

更复杂的对话框,不只是简单的yes - no目的:

dialog --menu "Is this a good question" 20 60 12 y Yes n No m Maybe

进度条:

dialog --gauge "Filling the tank" 20 60 0 < <(
    for i in {1..100};do
        printf "XXX\n%d\n%(%a %b %T)T progress: %d\nXXX\n" $i -1 $i
        sleep .033
    done
)

小样:

#!/bin/sh
while true ;do
    [ -x "$(which ${DIALOG%% *})" ] || DIALOG=dialog
    DIALOG=$($DIALOG --menu "Which tool for next run?" 20 60 12 2>&1 \
            whiptail       "dialog boxes from shell scripts" >/dev/tty \
            dialog         "dialog boxes from shell with ncurses" \
            gdialog        "dialog boxes from shell with Gtk" \
            kdialog        "dialog boxes from shell with Kde" ) || exit
    clear;echo "Choosed: $DIALOG."
    for i in `seq 1 100`;do
        date +"`printf "XXX\n%d\n%%a %%b %%T progress: %d\nXXX\n" $i $i`"
        sleep .0125
      done | $DIALOG --gauge "Filling the tank" 20 60 0
    $DIALOG --infobox "This is a simple info box\n\nNo action required" 20 60
    sleep 3
    if $DIALOG --yesno  "Do you like this demo?" 20 60 ;then
        AnsYesNo=Yes; else AnsYesNo=No; fi
    AnsInput=$($DIALOG --inputbox "A text:" 20 60 "Text here..." 2>&1 >/dev/tty)
    AnsPass=$($DIALOG --passwordbox "A secret:" 20 60 "First..." 2>&1 >/dev/tty)
    $DIALOG --textbox /etc/motd 20 60
    AnsCkLst=$($DIALOG --checklist "Check some..." 20 60 12 \
        Correct "This demo is useful"        off \
        Fun        "This demo is nice"        off \
        Strong        "This demo is complex"        on 2>&1 >/dev/tty)
    AnsRadio=$($DIALOG --radiolist "I will:" 20 60 12 \
        " -1" "Downgrade this answer"        off \
        "  0" "Not do anything"                on \
        " +1" "Upgrade this anser"        off 2>&1 >/dev/tty)
    out="Your answers:\nLike: $AnsYesNo\nInput: $AnsInput\nSecret: $AnsPass"
    $DIALOG --msgbox "$out\nAttribs: $AnsCkLst\nNote: $AnsRadio" 20 60
  done

更多样品?看看使用鞭子选择 USB 设备USB 可移动存储选择器:USBKeyChooser

5. 使用 readline 的历史记录

例:

#!/bin/bash

set -i
HISTFILE=~/.myscript.history
history -c
history -r

myread() {
    read -e -p '> ' $1
    history -s ${!1}
}
trap 'history -a;exit' 0 1 2 3 6

while myread line;do
    case ${line%% *} in
        exit )  break ;;
        *    )  echo "Doing something with '$line'" ;;
      esac
  done

这将在$HOME目录中创建一个文件.myscript.history ,而不是您可以使用 readline 的历史记录命令,例如UpDownCtrl + r等。

echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"

您可以使用内置的read命令;使用-p选项提示用户一个问题。

从 BASH4 开始,您现在可以使用-i建议答案:

read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
echo $FILEPATH

(但是请记住使用 “readline” 选项-e允许使用箭头键编辑行)

如果您想使用 “是 / 否” 逻辑,则可以执行以下操作:

read -e -p "
List the content of your home dir ? [Y/n] " YN

[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/

Bash 为此选择了。

select result in Yes No Cancel
do
    echo $result
done
read -p "Are you alright? (y/n) " RESP
if [ "$RESP" = "y" ]; then
  echo "Glad to hear it"
else
  echo "You need more bash programming"
fi

这是我整理的:

#!/bin/sh

promptyn () {
    while true; do
        read -p "$1 " yn
        case $yn in
            [Yy]* ) return 0;;
            [Nn]* ) return 1;;
            * ) echo "Please answer yes or no.";;
        esac
    done
}

if promptyn "is the sky blue?"; then
    echo "yes"
else
    echo "no"
fi

我是一个初学者,所以可以加一点盐,但是似乎有效。

inquire ()  {
  echo  -n "$1 [y/n]? "
  read answer
  finish="-1"
  while [ "$finish" = '-1' ]
  do
    finish="1"
    if [ "$answer" = '' ];
    then
      answer=""
    else
      case $answer in
        y | Y | yes | YES ) answer="y";;
        n | N | no | NO ) answer="n";;
        *) finish="-1";
           echo -n 'Invalid response -- please reenter:';
           read answer;;
       esac
    fi
  done
}

... other stuff

inquire "Install now?"

...

你要:

  • Bash 内置命令(即便携式)
  • 检查 TTY
  • 默认答案
  • 暂停
  • 有色问题

片段

do_xxxx=y                      # In batch mode => Default is Yes
[[ -t 0 ]] &&                  # If TTY => Prompt the question
read -n 1 -p $'\e[1;32m
Do xxxx? (Y/n)\e[0m ' do_xxxx  # Store the answer in $do_xxxx
if [[ $do_xxxx =~ ^(y|Y|)$ ]]  # Do if 'y' or 'Y' or empty
then
    xxxx
fi

说明

  • [[ -t 0 ]] && read ... => 如果是 TTY,则read调用命令
  • read -n 1 => 等待一个字符
  • $'\e[1;32m ... \e[0m ' => 以绿色打印
    (绿色很好,因为在白色 / 黑色背景上均可读取)
  • [[ $do_xxxx =~ ^(y|Y|)$ ]] => bash 正则表达式

超时 => 默认答案为否

do_xxxx=y
[[ -t 0 ]] && {                   # Timeout 5 seconds (read -t 5)
read -t 5 -n 1 -p $'\e[1;32m
Do xxxx? (Y/n)\e[0m ' do_xxxx ||  # read 'fails' on timeout
do_xxxx=n ; }                     # Timeout => answer No
if [[ $do_xxxx =~ ^(y|Y|)$ ]]
then
    xxxx
fi

用最少的行数来实现此目的的最简单方法如下:

read -p "<Your Friendly Message here> : y/n/cancel" CONDITION;

if [ "$CONDITION" == "y" ]; then
   # do something here!
fi

if只是一个示例:如何处理此变量取决于您。