如何在 Linux shell 脚本中提示是/否/取消输入?

新手上路,请多包涵

我想暂停 shell 脚本中的输入,并提示用户选择。

标准 YesNoCancel 类型问题。

如何在典型的 bash 提示符中完成此操作?

原文由 Myrddin Emrys 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

在 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 循环重试。

此外, Léa Gris她的回答 中演示了一种使请求语言不可知的方法。调整我的第一个示例以更好地服务于多种语言可能如下所示:

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

while true; do
    read -p "Install (${yesword} / ${noword})? " yn
    if [[ "$yn" =~ $yesexpr ]]; then make install; exit; fi
    if [[ "$yn" =~ $noexpr ]]; then exit; fi
    echo "Answer ${yesword} / ${noword}."
done

显然,其他通信字符串在此处仍未翻译(安装、回答),这需要在更完整的翻译中解决,但在许多情况下,即使是部分翻译也会有所帮助。

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

原文由 Myrddin Emrys 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

根据

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

如果你想

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

1. POSIX 通用解决方案

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

 printf 'Is this a good question (y/n)? '
read answer

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

 if [ "$answer" != "${answer#[Yy]}" ] ;then # this grammar (the #[] operator) means that the variable $answer where any Y or y in 1st position will be dropped if they exist.
    echo Yes
else
    echo No
fi

(感谢 Adam Katz 的评论:将上面的测试替换为更便携且避免使用分叉的测试:)

POSIX,但单键功能

但是,如果您不希望用户必须点击 Return ,您可以编写:

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

 printf '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

注意: 这是在 shbashkshdashbusybox 下测试的!

相同,但明确等待 yn

 #/bin/sh
printf '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

3. 专用工具 的一些技巧

更复杂的对话框,超越简单 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

更多样品?看看 使用whiptail选择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 等。

原文由 F. Hauri - Give Up GitHub 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题