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

How do I prompt for Yes/No/Cancel input in a Linux shell script?

提问人:Myrddin Emrys 提问时间:10/23/2008 最后编辑:almacelesteMyrddin Emrys 更新时间:9/20/2023 访问量:1321790

问:

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

Linux Bash Shell 脚本 posix ncurses sh ksh dash busybox

评论

40赞 Tyzoid 5/31/2019
请注意:提示的约定是这样的,如果您提供一个选项,则大写的选项是默认的,即 默认为“yes”,默认为“no”。查看 ux.stackexchange.com/a/40445/43532[yn][Yn][yN]
4赞 smac89 12/20/2019
任何从 ZSH 来到这里的人,请参阅此答案,了解如何使用命令进行提示read
2赞 Cadoiz 10/15/2021
你也可以考虑我在 U&L.SE 上关于暂停的规范方式的相关问答。提供的结果可以很容易地转移。bash

答:

360赞 Pistos 10/23/2008 #1
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"

评论

35赞 Myrddin Emrys 9/23/2016
我不同意,因为它只实现了 DOS 中“是、否、取消”对话框的一部分功能。它未能实现的部分是输入检查......循环,直到收到有效答案。
4赞 Pistos 7/10/2017
(原来的题目是“如何在 Linux shell 脚本中提示输入?
8赞 Myrddin Emrys 9/6/2017
但原始问题描述保持不变,并且始终要求对“是/否/取消”提示做出响应。标题已更新为比我原来的标题更清晰,但问题描述总是很清楚(在我看来)。
47赞 SumoRunner 10/23/2008 #2
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?"

...

评论

2赞 Jouni K. Seppänen 10/23/2008
在每行的开头放置四个空格以保留代码的格式。
10赞 Myrddin Emrys 10/23/2008
如果大小写开关是硬编码的,为什么我们提供 'y' 和 'n' 作为 inquire() 的参数?那只是要求滥用。它们是固定参数,不可更改,因此第 2 行的回声应为:echo -n “$1 [Y/N]?它们无法更改,因此不应提供。
1赞 Mateusz Piotrowski 3/27/2016
@MyrddinEmrys 你能详细说明你的评论吗?或者发布一篇文章或几个关键字的链接,这样我就可以自己做研究。
4赞 Myrddin Emrys 3/27/2016
@MateuszPiotrowski 自从我发表评论以来,答案已经过编辑和改进。您可以点击上面的“12 月 23 日编辑”链接查看此答案的所有过去版本。早在 2008 年,代码就大不相同。
2027赞 Myrddin Emrys 10/23/2008 #3

在 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

您无需对输入进行清理 - 它会显示可用的选项,然后您键入与您的选择相对应的数字。它还会自动循环,因此如果它们提供无效输入,则无需重试循环。selectwhile 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精彩回答

评论

40赞 Trey Piepmeier 12/3/2009
在 OS X Leopard 中使用 Bash,当我选择“否”时,我更改为防止关闭选项卡。exitbreak
1赞 Shawn 2/29/2012
这如何适用于比“是”或“否”更长的选项?在 case 子句中,你是否写了这样的话:安装程序,之后什么都不做) make install;破;
1赞 Myrddin Emrys 3/1/2012
@Shawn 当然,使用 read 可以使用 bash shell 的 switch 语句支持的任何 glob 或正则表达式模式。它只是将键入的文本与您的模式匹配,直到找到匹配项。使用 select,将选项列表提供给命令,并将它们显示给用户。您可以根据需要将列表中的项目设置为长度或长度。我建议检查他们的手册页以获取全面的使用信息。
4赞 Jayen 6/16/2014
为什么 if there is no loop 中有 a?breakselect
1赞 Travesty3 1/4/2018
FWIW,我使用此示例创建了一个脚本,我打算通过远程 SSH 会话触发该脚本。我的 SSH 命令如下所示:.以这种方式执行时,不会显示命令的提示文本。但是,该命令的输出确实如此。所以对我来说,更好的解决方案是先用后跟 .ssh my-server 'path/to/myscript.sh'read -pechoecho -n "Do something? "read yn
5赞 Osama Al-Maadeed 10/23/2008 #4

我建议你使用对话框...

Linux Apprentice:使用对话框改进 Bash Shell 脚本

dialog 命令允许在 shell 脚本中使用窗口框,使其使用更具交互性。

它简单易用,还有一个名为 gdialog 的 GNOME 版本,它采用完全相同的参数,但在 X 上显示 GUI 样式。

评论

0赞 Stephan 12/3/2014
对于普通读者,请使用此处的对话框命令查看代码片段:stackoverflow.com/a/22893526/363573
127赞 Steven Huwig 10/23/2008 #5

Bash 为此目的选择了。以下是在脚本中使用它的方法:

select result in Yes No Cancel
do
    echo $result
done

这是它使用的样子:

$ bash examplescript.sh
1) Yes
2) No
3) Cancel
#? 1
Yes
#? 2
No
#? 3
Cancel
#?

评论

24赞 kaiser 2/28/2013
+1 非常简单的解决方案。唯一的事情:这将提示和promt和提示...直到您添加内部:)exit
7赞 Zorawar 4/23/2014
(kaiser:要摆脱它,只需输入 EOT: 。但是,当然,使用它的真实代码需要在正文中休息或退出。Ctrl-D
19赞 djjeck 9/11/2014
但是,这不允许您输入 y 或 n。您可以通过输入 1、2 或 3 进行选择。
8赞 wranvaud 11/7/2016
exit将一起退出脚本,只会退出您所在的循环(如果您在 OR 循环中)breakwhilecase
66赞 serg 2/3/2011 #6
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

评论

0赞 Yuji 'Tomita' Tomita 5/24/2023
这是最清晰的。+1
178赞 yPhil 5/13/2011 #7

您可以使用内置的 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 ~/

评论

6赞 Ken Sharp 2/23/2015
应该注意的是,这是您选择的变量名称,并且是使用命令提示符的答案设置的。因此,如果您要运行,例如,将打开该文件。FILEPATHvlc "$FILEPATH"vlc
0赞 JBallin 11/12/2018
第二个例子(简单的是/否)有什么好处?-e
0赞 JBallin 11/12/2018
有什么理由使用而不是?-e -p-ep
1赞 yPhil 11/13/2018
如果没有标志/选项,您可能(取决于实现)无法键入“y”,然后改变主意并将其替换为“n”(或其他任何与此相关的内容);在记录命令时,除其他原因外,单独列出选项更易于阅读/清晰。-e
0赞 Cadoiz 10/15/2021
只是一个提示:注意真正使用,而不是像U&L.SE上的这个答案中描述的那样。bashsh
23赞 ThatLinuxGuy 5/9/2012 #8

使用以下命令:read

echo Would you like to install? "(Y or N)"

read x

# now check if $x is "y"
if [ "$x" = "y" ]; then
    # do something here!
fi

然后是你需要的所有其他东西

45赞 mpen 8/31/2012 #9

这是我整理的东西:

#!/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

我是初学者,所以对此持保留态度,但它似乎有效。

评论

9赞 jchook 7/25/2013
如果更改为 then,则可以使用第二个参数 Y 或 N 作为默认值。case $yn incase ${yn:-$2} in
1赞 rubo77 4/7/2020
或更改为默认值为 yescase $yncase "${yn:-Y}"
0赞 winklerrr 12/9/2020
您的代码直接与语句一起使用真是太好了!但它似乎在使用的脚本中不起作用。你有什么解决方法吗?ifset -e
0赞 mpen 12/9/2020
@winklerrr 你必须详细说明。似乎可以正常工作。您确定脚本中的其他位置没有错误吗?set -e
0赞 winklerrr 12/9/2020
在我的解决方案中,我直接在我尝试用子壳捕获的函数中呼应了答案是或否,如下所示:.我认为这导致了“错误”。answer="$(promptyn "is the sky blue?")"
3赞 jlettvin 9/24/2013 #10
yn() {
  if [[ 'y' == `read -s -n 1 -p "[y/n]: " Y; echo $Y` ]];
  then eval $1;
  else eval $2;
  fi }
yn 'echo yes' 'echo no'
yn 'echo absent no function works too!'

评论

0赞 tripleee 5/21/2014
这似乎既复杂又脆弱。就这样怎么样yn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
21赞 Dennis 11/15/2013 #11

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

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

评论

2赞 Dennis 4/2/2014
@Jav回声会在您的响应后打印一个换行符。如果没有它,下一个要打印的内容将立即出现在您在同一行的响应之后。尝试删除自己看看。echo
5赞 Ernest A 12/29/2013 #12

多项选择版本:

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
$

它将设置为(在脚本内)。REPLYy

21赞 Thorsten Staerk 4/6/2014 #13

要获得一个漂亮的类似 ncurses 的输入框,请使用如下命令对话框

#!/bin/bash
if (dialog --title "Message" --yesno "Want to do something risky?" 6 25)
# message box will have the size 25x6 characters
then 
    echo "Let's do something risky"
    # do something risky
else 
    echo "Let's stay boring"
fi

默认情况下,至少在 SUSE Linux 中会安装该对话框包。看来:the "dialog" command in action

评论

0赞 WiMantis 7/29/2021
还有一个参数用于确保默认情况下选择“否”选项。--defaultno
16赞 user9869932 5/21/2014 #14

就我而言,我需要从下载的脚本中读取,即

curl -Ss https://example.com/installer.sh | sh

在这种情况下,该行允许它读取输入。read -r yn </dev/tty

printf "These files will be uploaded. Is this ok? (y/N) "
read -r yn </dev/tty

if [ "$yn" = "y" ]; then
   
   # Yes
else
   
   # No
fi

评论

0赞 Myrddin Emrys 5/22/2014
其中的一个重要部分是输入验证。我认为将我的第一个示例改编为像您一样接受输入对您来说也是如此,并且还会循环使用错误的输入(想象一下缓冲区中的几个字符;您的方法将迫使用户始终选择“否”)。tty
5赞 Miguel 1/8/2015 #15

受到 @Mark 和 @Myrddin 的答案的启发,我为通用提示创建了这个函数

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"
693赞 F. Hauri - Give Up GitHub 1/10/2015 #16

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

根据

  • 符合 标准:可以在具有通用 环境的不良系统上工作
  • :使用所谓的 bashisms

如果你愿意

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

1. POSIX通用解决方案

您可以使用以下命令,后跟:readif ... then ... else

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

if [ "$answer" != "${answer#[Yy]}" ] ;then 
    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 [ "$answer" != "${answer#[Yy]}" ];then
    echo Yes
else
    echo No
fi

注意:这是在 下测试的!

相同,但明确等待或: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 [ "$answer" != "${answer#[Yy]}" ];then
    echo Yes
else
    echo No
fi

使用专用工具

有许多工具是使用 、 或其他图形库构建的。例如,使用:libncurseslibgtklibqtwhiptail

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

其中 是对话框的高度(以行数表示),是对话框的宽度。这些工具都具有几乎相同的语法。2060

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

我更喜欢使用,这样我甚至可以在需要时进行测试......caseyes | ja | si | oui

符合单键功能

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

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

在 bash 下,command 接受一个 timeout 参数,这可能很有用。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

带倒计时的超时:

i=6 ;while ((i-->1)) &&
! read -sn 1 -t 1 -p $'\rIs this a good question (Y/n)? '$i$'..\e[3D' answer;do
  :;done ;[[ $answer == [nN] ]] && answer=No || answer=Yes ;echo "$answer "

3. 专用工具的一些技巧

更复杂的对话框,超越了简单的“是-否”目的:

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" ) || break
    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

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

评论

6赞 Jonathan Leffler 2/7/2015
请注意,它提供了以下选项:。这将完全恢复它们找到的设置,这可能与 相同,也可能不同。stty-gold_stty=$(stty -g); stty raw -echo; …; stty "$old_stty"stty -sane
7赞 vlfig 3/31/2017
read answer将解释空格和换行符之前的反斜杠,否则会剥离它们,这很少是故意的。请按照SC2162代替使用。read -r answer
1赞 Bruno Bronosky 10/19/2017
“使用 readline 的历史记录”方法对于 OP 的问题非常不合适。无论如何,我很高兴你把它包括在内。我有几十个更复杂的脚本,我打算用这种模式进行更新!
7赞 Adam Katz 4/11/2018
您可以用于 POSIX 和 bash(使用通配符条件而不是 bash 子字符串:),但我更喜欢使用条件语句,而不是您的 .它更可移植(一些非 GNU grep 无法正确实现),并且它没有系统调用。 使用参数扩展从 的开头删除 或 ,当存在任何一个时,都会导致不等式。这适用于任何 POSIX shell(dash、ksh、bash、zsh、busybox 等)。casecase $answer in; [Yy]* ) echo Yes ;;[ "$answer" != "${answer#[Yy]}" ]echo "$answer" | grep -iq ^y-q${answer#[Yy]}Yy$answer
3赞 F. Hauri - Give Up GitHub 4/2/2020
@CarterPape 是的,这是个笑话!但是在这个粗略的答案中,您可能会发现很多提示(第二点至少提供了 3 种不同的方法)!和。。。从大约 5 年开始,您是第一个讲述我的计数方法的人!;-))
3赞 Joshua Goldberg 4/9/2015 #17

一种简单的方法是使用 或 gnu 。xargs -pparallel --interactive

我更喜欢 xargs 的行为,因为它像其他交互式 unix 命令一样在提示后立即执行每个命令,而不是收集 yesses 以在最后运行。(完成所需的选项后,您可以按 Ctrl-C 组合键。

例如,

echo *.xml | xargs -p -n 1 -J {} mv {} backup/

评论

0赞 Myrddin Emrys 4/9/2015
不错,但仅限于“是”或“否”。只要这就是你所需要的,就足够了,但我最初的问题给出了一个有三种可能结果的例子。不过,我真的很喜欢它是可流式的;许多常见方案将受益于其管道传输能力。xargs --interactive
0赞 Joshua Goldberg 6/8/2015
明白了。我的想法是,“取消”意味着简单地停止所有进一步的执行,这可以通过 Ctrl-C 支持,但如果您需要在取消(或不)上做一些更复杂的事情,这是不够的。
12赞 Jahid 4/19/2015 #18
read -e -p "Enter your choice: " choice

该选项使用户能够使用箭头键编辑输入。-e

如果要使用建议作为输入:

read -e -i "yes" -p "Enter your choice: " choice

-ioption 打印提示性输入。

评论

0赞 Jahid 5/11/2015
是的,不要在 sh(Bourne shell)中工作,但这个问题专门标记为 bash..-e-i
3赞 ccDict 6/5/2015 #19

作为单行命令的朋友,我使用了以下内容:

while [ -z $prompt ]; do read -p "Continue (y/n)?" choice;case "$choice" in y|Y ) prompt=true; break;; n|N ) exit 0;; esac; done; prompt=;

写长篇,它的工作原理是这样的:

while [ -z $prompt ];
  do read -p "Continue (y/n)?" choice;
  case "$choice" in
    y|Y ) prompt=true; break;;
    n|N ) exit 0;;
  esac;
done;
prompt=;

评论

0赞 Myrddin Emrys 6/5/2015
你能澄清一下提示变量的用法吗?在我看来,它就像一个衬里之后被擦掉了,那么你如何使用这条线做任何事情呢?
0赞 ccDict 6/5/2015
提示在 while 循环后擦除。因为我希望之后初始化提示变量(因为我更频繁地使用该语句)。只有在 y|键入 Y 并在 n|键入 N 或重复要求输入其他所有内容。
4赞 Alexander Löfqvist 7/29/2015 #20

更通用的是:

function menu(){
    title="Question time"
    prompt="Select:"
    options=("Yes" "No" "Maybe")
    echo "$title"
    PS3="$prompt"
    select opt in "${options[@]}" "Quit/Cancel"; do
        case "$REPLY" in
            1 ) echo "You picked $opt which is option $REPLY";;
            2 ) echo "You picked $opt which is option $REPLY";;
            3 ) echo "You picked $opt which is option $REPLY";;
            $(( ${#options[@]}+1 )) ) clear; echo "Goodbye!"; exit;;
            *) echo "Invalid option. Try another one.";continue;;
         esac
     done
     return
}
34赞 oHo 12/18/2015 #21

你想要:

  • 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

评论

0赞 Vladimir Yakovenko 10/19/2022
似乎有错别字:> 如果 [[ $do_xxxx =~ ^(y|Y|)$ ]] 应为:if [[ $do_xxxx =~ ^(y|Y)$ ]]
28赞 Apurv Nerlekar 3/4/2016 #22

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

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

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

这只是一个例子:如何处理这个变量取决于你。if

7赞 Yokai 12/8/2016 #23

我注意到没有人发布一个答案,显示这种简单的用户输入的多行回声菜单,所以这是我的选择:

#!/bin/bash

function ask_user() {    

echo -e "
#~~~~~~~~~~~~#
| 1.) Yes    |
| 2.) No     |
| 3.) Quit   |
#~~~~~~~~~~~~#\n"

read -e -p "Select 1: " choice

if [ "$choice" == "1" ]; then

    do_something

elif [ "$choice" == "2" ]; then

    do_something_else

elif [ "$choice" == "3" ]; then

    clear && exit 0

else

    echo "Please select 1, 2, or 3." && sleep 3
    clear && ask_user

fi
}

ask_user

发布此方法是希望有人会发现它有用且节省时间。

13赞 Tom Hale 6/28/2018 #24

仅单次按键

下面是一个更长但可重用的模块化方法:

  • 返回 =yes 和 =no01
  • 无需按回车键 - 只需一个字符
  • 可以按接受默认选项enter
  • 可以禁用默认选择以强制选择
  • 适用于两者和 .zshbash

按回车键时默认为“否”

请注意,是大写的。这里按回车键,接受默认值:N

$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]?

另请注意,这是自动附加的。 默认的“no”被接受,因此不会回显任何内容。[y/N]?

重新提示,直到给出有效的响应:

$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]? X
Show dangerous command [y/N]? y
rm *

按回车键时默认为“是”

请注意,大写:Y

$ confirm_yes "Show dangerous command" && echo "rm *"
Show dangerous command [Y/n]?
rm *

在上面,我刚刚按了回车键,所以命令运行了。

无默认值 - require 或enteryn

$ get_yes_keypress "Here you cannot press enter. Do you like this [y/n]? "
Here you cannot press enter. Do you like this [y/n]? k
Here you cannot press enter. Do you like this [y/n]?
Here you cannot press enter. Do you like this [y/n]? n
$ echo $?
1

在这里,或返回 false。请注意,使用此较低级别的函数,您需要提供自己的提示。1[y/n]?

法典

# Read a single char from /dev/tty, prompting with "$*"
# Note: pressing enter will return a null string. Perhaps a version terminated with X and then remove it in caller?
# See https://unix.stackexchange.com/a/367880/143394 for dealing with multi-byte, etc.
function get_keypress {
  local REPLY IFS=
  >/dev/tty printf '%s' "$*"
  [[ $ZSH_VERSION ]] && read -rk1  # Use -u0 to read from STDIN
  # See https://unix.stackexchange.com/q/383197/143394 regarding '\n' -> ''
  [[ $BASH_VERSION ]] && </dev/tty read -rn1
  printf '%s' "$REPLY"
}

# Get a y/n from the user, return yes=0, no=1 enter=$2
# Prompt using $1.
# If set, return $2 on pressing enter, useful for cancel or defualting
function get_yes_keypress {
  local prompt="${1:-Are you sure [y/n]? }"
  local enter_return=$2
  local REPLY
  # [[ ! $prompt ]] && prompt="[y/n]? "
  while REPLY=$(get_keypress "$prompt"); do
    [[ $REPLY ]] && printf '\n' # $REPLY blank if user presses enter
    case "$REPLY" in
      Y|y)  return 0;;
      N|n)  return 1;;
      '')   [[ $enter_return ]] && return "$enter_return"
    esac
  done
}

# Credit: http://unix.stackexchange.com/a/14444/143394
# Prompt to confirm, defaulting to NO on <enter>
# Usage: confirm "Dangerous. Are you sure?" && rm *
function confirm {
  local prompt="${*:-Are you sure} [y/N]? "
  get_yes_keypress "$prompt" 1
}    

# Prompt to confirm, defaulting to YES on <enter>
function confirm_yes {
  local prompt="${*:-Are you sure} [Y/n]? "
  get_yes_keypress "$prompt" 0
}

评论

0赞 Ilias Karim 6/30/2018
当我测试这个脚本时,我得到的不是上面的 outut,而是Show dangerous command [y/N]? [y/n]?Show dangerous command [Y/n]? [y/n]?
0赞 Tom Hale 7/1/2018
谢谢@IliasKarim,我刚才解决了这个问题。
13赞 Walter A 12/12/2018 #25

您可以在 上使用默认值,转换为小写,并使用表达式与一组变量进行比较。
该脚本还支持
REPLYreadja/si/oui

read -rp "Do you want a demo? [y/n/c] "

[[ ${REPLY,,} =~ ^(c|cancel)$ ]] && { echo "Selected Cancel"; exit 1; }

if [[ ${REPLY,,} =~ ^(y|yes|j|ja|s|si|o|oui)$ ]]; then
   echo "Positive"
fi
21赞 Léa Gris 8/31/2019 #26

可以在 POSIX shell 中处理可感知区域设置的“是/否选择”;通过使用 locale 类别的条目,witch 提供了现成的正则表达式模式来匹配输入,并为本地化的 Yes No 提供了字符串。LC_MESSAGES

#!/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 只指定了前两个(yesexpr 和 noexpr)。在 Ubuntu 16 上,yesstr 和 nostr 是空的。

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

LC_MESSAGES

yesstrnostr locale 关键字以及 和 langinfo 项以前用于匹配用户肯定和否定响应。在 POSIX.1-2008 中,yesexprnoexpr 和扩展正则表达式取代了它们。应用程序应使用基于区域设置的常规消息传递工具来发出提示消息,其中包括所需的响应示例。YESSTRNOSTRYESEXPRNOEXPR

或者,以 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

使用语言环境提供的正则表达式匹配答案。

要翻译剩余的消息,请使用 to 输出用于本地化的 po 字符串:bash --dump-po-strings scriptname

#: 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 ""

评论

0赞 Myrddin Emrys 9/3/2019
我喜欢添加与语言无关的选项。干的好。
1赞 Urhixidur 1/9/2020
不幸的是,POSIX 只指定了前两个(yesexpr 和 noexpr)。在 Ubuntu 16 上,yesstr 和 nostr 是空的。
2赞 Urhixidur 1/10/2020
但是等等!还有更糟糕的消息!bash case 语句表达式不是正则的,它们是文件名表达式。因此,Ubuntu 16 的 yesexpr 和 noexpr(分别为 “^[yY].*” 和 “^[nN].*”) 将因为嵌入周期而完全失败。在正则表达式中,“.*”表示“任何非换行符,零次或多次”。但在 case 语句中,它是一个字面意思的“.”,后跟任意数量的字符。
1赞 Léa Gris 1/10/2020
最后,在 shell 环境中可以做到的最好的事情是在 Bash 的特定正则表达式匹配中使用它yesexprnoexprif [[ "$yn" =~ $yesexpr ]]; then echo $"Answered yes"; else echo $"Answered no"; fi
0赞 foxesque 6/5/2023
我在 SUSE Tumbleweed 下并显示 .有趣的是,这些正则表达式接受 +/- 和 0/1。也许他们希望机器人使用提示。local LC_MESSAGES^[+1yY] ^[-0nN] yes no UTF-8
4赞 Roland 12/3/2020 #27

绝对最简单的解决方案是这个没有聪明技巧的单行:

read -p "press enter ..." y

它让人想起经典的DOS ,除了它等待Enter键,而不仅仅是任何键。Hit any key to continue

诚然,这不会为您提供“是”否取消“的三个选项,但当您接受 control-C 作为”否“时,它很有用。在简单的脚本中取消,例如:

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

因为你不喜欢记住丑陋的命令和路径,但脚本运行得太快,在你决定它不是你打算运行的脚本之前,没有给你停下来的机会。

命令行参数是必需的,并且可以选择用于接收用户在按 Enter 键之前键入的答案,如下所示:ysh

echo You entered $y

您可以省略最后一个参数,只需使用:bash

read -p "press enter ..."

评论

1赞 rych 4/10/2023
y不需要read -p "press enter ..." y
6赞 Samir Kape 3/1/2021 #28

检查这个

read -p "Continue? (y/n): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
10赞 Ctrl-C 10/22/2021 #29

单行:

read -p "Continue? [Enter] → Yes, [Ctrl]+[C] → No."

这假设“否”和“取消”具有相同的结果,因此没有理由区别对待它们。

11赞 Erik Pukinskis 6/21/2022 #30

这个问题有很多很好的答案,但据我所知,它们都不是我的理想,它会:

  1. 简单一点,只需几行外壳
  2. 只需按一次 y/n 键即可工作(无需按回车键)
  3. 如果您只是按回车键,则默认为“是”
  4. 也可以使用大写的 Y/N

这是我的版本,它确实具有这些属性:

read -n1 -p "Continue? (Y/n) " confirm

if ! echo $confirm | grep '^[Yy]\?$'; then
  exit 1
fi

您可以将该条件修改为仅在“yes”上运行(只需删除语句中的 ),或者如果要在两个分支上运行代码,请添加一个。!ifelse

评论

1赞 Myrddin Emrys 6/22/2022
做得好!为一个老问题捐款永远不会太晚。我很欣赏单次按键(y、n 或 enter)答案。我认为您应该包括 else 版本,因为我最初的问题是针对一个完整的三个分支问题(如果您将“取消”视为“退出”;