首页 文章

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

提问于
浏览 813
1178

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

27 回答

  • 1342

    我注意到没有人发布一个答案显示多行回显菜单这样简单的用户输入所以这是我的去处:

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

    发布此方法是希望有人可能觉得它有用且节省时间 .

  • 404

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

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

    你想:

    • 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
    
  • 138

    使用 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
    

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

  • 98

    一种简单的方法是使用 xargs -p 或gnu parallel --interactive .

    我更喜欢xargs的行为,因为它像其他交互式unix命令一样在提示之后立即执行每个命令,而不是收集在末尾运行的yes . (通过你想要的那些之后你可以用Ctrl-C . )

    例如 . ,

    echo *.xml | xargs -p -n 1 -J {} mv {} backup/
    
  • 53
    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?"
    
    ...
    
  • 31

    仅限单键按键

    这是一个更长但可重复使用的模块化方法:

    • 返回 0 =是和 1 =否

    • 无需按Enter键 - 只需一个字符

    • 可以按Enter键接受默认选项

    • 可以禁用默认选项以强制选择

    • 适用于 zshbash .

    按Enter键时默认为“no”

    请注意 N 是大写的 . 按Enter键,接受默认值:

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

    另请注意, [y/N]? 已自动附加 . 接受默认"no",因此不会回显任何内容 .

    Re-prompt until a valid response is given:

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

    按Enter键时默认为“是”

    请注意 Y 是大写的:

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

    上面,我只是按了回车键,所以命令运行了 .

    无输入默认值 - 要求y或n

    $ 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
    

    这里返回了 1 或false . 请注意,使用此较低级别的功能,您需要提供自己的 [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
    }
    
  • 27

    要获得一个很好的类似ncurses的输入框,请使用命令 dialog ,如下所示:

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

  • 24

    回应他人:

    您不需要在BASH4中指定大小写,只需使用',,'来制作var小写 . 另外,我强烈不喜欢将代码放入读取块内部,获取结果并在读取块IMO之外处理它 . 还包括退出IMO的'q' . 最后为什么键入'yes'只需使用-n1并按y键 .

    示例:用户可以按y / n,也可以按q退出 .

    ans=''
    while true; do
        read -p "So is MikeQ the greatest or what (y/n/q) ?" -n1 ans
        case ${ans,,} in
            y|n|q) break;;
            *) echo "Answer y for yes / n for no  or q for quit.";;
        esac
    done
    
    echo -e "\nAnswer = $ans"
    
    if [[ "${ans,,}" == "q" ]] ; then
            echo "OK Quitting, we will assume that he is"
            exit 0
    fi
    
    if [[ "${ans,,}" == "y" ]] ; then
            echo "MikeQ is the greatest!!"
    else
            echo "No? MikeQ is not the greatest?"
    fi
    
  • 21

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

    从BASH4开始,您现在可以使用 -i 来建议答案,因此用户只需按 return 即可输入:

    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 ~/
    
  • 17

    很抱歉在这么老的帖子上发帖 . 几周前我遇到了类似的问题,在我的情况下,我需要一个解决方案,它也可以在一个在线安装程序脚本中工作,例如: curl -Ss https://raw.github.com/_____/installer.sh | bash

    使用 read yesno < /dev/tty 对我来说很好:

    echo -n "These files will be uploaded. Is this ok? (y/n) "
    read yesno < /dev/tty
    
    if [ "x$yesno" = "xy" ];then
    
       # Yes
    else
    
       # No
    fi
    

    希望这有助于某人 .

  • 16

    更通用的是:

    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
    }
    
  • 12
    read -e -p "Enter your choice: " choice
    

    -e 选项使用户可以使用箭头键编辑输入 .

    如果您想使用建议作为输入:

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

    -i 选项打印一个暗示输入 .

  • 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
    

    我是一个初学者,所以带上一粒盐,但似乎有效 .

  • 7

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

    另外,请查看F. Hauri的excellent answer .

  • 5

    在这种情况下,我曾多次使用 case 语句,使用案例陈述是一种很好的方法 . 利用布尔条件对 case 块进行封装的 while 循环可以实现,以便更好地控制程序,并满足许多其他要求 . 在满足所有条件后,可以使用 break 将控制权传递回程序的主要部分 . 此外,为了满足其他条件,当然可以添加条件语句以伴随控制结构: case 语句和可能的 while 循环 .

    使用 case 语句来满足您的请求的示例

    #! /bin/sh 
    
    # For potential users of BSD, or other systems who do not
    # have a bash binary located in /bin the script will be directed to
    # a bourne-shell, e.g. /bin/sh
    
    # NOTE: It would seem best for handling user entry errors or
    # exceptions, to put the decision required by the input 
    # of the prompt in a case statement (case control structure), 
    
    echo Would you like us to perform the option: "(Y|N)"
    
    read inPut
    
    case $inPut in
        # echoing a command encapsulated by 
        # backticks (``) executes the command
        "Y") echo `Do something crazy`
        ;;
        # depending on the scenario, execute the other option
        # or leave as default
        "N") echo `execute another option`
        ;;
    esac
    
    exit
    
  • 4
    echo "Please enter some input: "
    read input_variable
    echo "You entered: $input_variable"
    
  • 4

    我建议你use dialog ......

    Linux Apprentice:使用Dialog改进Bash Shell脚本使用dialog命令可以在shell脚本中使用窗口框,使其更具交互性 .

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

  • 3

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

    read -p "<Your Friendly Message here> : y/n/cancel" CONDITION;
    
    if [ "$CONDITION" == "y" ]; then
       # do something here!
    fi
    

    if 只是一个例子:由你来决定如何处理这个变量 .

  • 3

    是/否/取消

    功能

    #!/usr/bin/env bash
    @confirm() {
      local message="$*"
      local result=''
    
      echo -n "> $message (Yes/No/Cancel) " >&2
    
      while [ -z "$result" ] ; do
        read -s -n 1 choice
        case "$choice" in
          y|Y ) result='Y' ;;
          n|N ) result='N' ;;
          c|C ) result='C' ;;
        esac
      done
    
      echo $result
    }
    

    用法

    case $(@confirm 'Confirm?') in
      Y ) echo "Yes" ;;
      N ) echo "No" ;;
      C ) echo "Cancel" ;;
    esac
    

    使用干净的用户输入确认

    功能

    #!/usr/bin/env bash
    @confirm() {
      local message="$*"
      local result=3
    
      echo -n "> $message (y/n) " >&2
    
      while [[ $result -gt 1 ]] ; do
        read -s -n 1 choice
        case "$choice" in
          y|Y ) result=0 ;;
          n|N ) result=1 ;;
        esac
      done
    
      return $result
    }
    

    用法

    if @confirm 'Confirm?' ; then
      echo "Yes"
    else
      echo "No"
    fi
    
  • 3

    多选版本:

    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 (在脚本中) .

  • 3

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

    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=;
    

    写的longform,它的工作方式如下:

    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=;
    
  • 2

    为此,Bash有select .

    select result in Yes No Cancel
    do
        echo $result
    done
    
  • 2

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

    取决于

    符合

    • posix:可以在具有通用shell环境的不良系统上运行

    • bash具体:使用所谓的bashisms

    如果你想要的话

    • simple``inline''问题/答案(通用解决方案)

    • 非常格式化的接口,如ncurses或更多图形使用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”;然后

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

    (感谢Adam Katz's comment:将上面的测试替换为更便携的测试并避免使用一个分叉:)

    POSIX,但是单一的关键功能

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

    Edited: 正如@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
    

    Note: 这是在shbashkshdashbusybox下测试的!

    相同,但明确等待y或n:

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

    更多样品?看看Using whiptail for choosing USB deviceUSB removable storage selector: 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的历史命令,如Up,Down,Ctrl r等 .

  • 2
    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

    受到@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"
    
  • 0
    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
    

相关问题