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

后端 未结 30 1696
不思量自难忘°
不思量自难忘° 2020-11-22 04:52

I want to pause input in a shell script, and prompt the user for choices.
The standard Yes, No, or Cancel type question.
How d

相关标签:
30条回答
  • 2020-11-22 05:00

    As a friend of a one line command I used the following:

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

    Written longform, it works like this:

    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 讨论(0)
  • 2020-11-22 05:00

    Yes / No / Cancel

    Function

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

    Usage

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

    Confirm with clean user input

    Function

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

    Usage

    if @confirm 'Confirm?' ; then
      echo "Yes"
    else
      echo "No"
    fi
    
    0 讨论(0)
  • 2020-11-22 05:01

    You can use the built-in read command ; Use the -p option to prompt the user with a question.

    Since BASH4, you can now use -i to suggest an answer :

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

    (But remember to use the "readline" option -e to allow line editing with arrow keys)

    If you want a "yes / no" logic, you can do something like this:

    read -e -p "
    List the content of your home dir ? [Y/n] " YN
    
    [[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/
    
    0 讨论(0)
  • 2020-11-22 05:02

    At least five answers for one generic question.

    Depending on

    • posix compliant: could work on poor systems with generic shell environments
    • bash specific: using so called bashisms

    and if you want

    • simple ``in line'' question / answer (generic solutions)
    • pretty formatted interfaces, like ncurses or more graphical using libgtk or libqt...
    • use powerful readline history capability

    1. POSIX generic solutions

    You could use the read command, followed by 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
    

    (Thanks to Adam Katz's comment: Replaced the test above with one that is more portable and avoids one fork:)

    POSIX, but single key feature

    But if you don't want the user to have to hit Return, you could write:

    (Edited: As @JonathanLeffler rightly suggest, saving stty's configuration could be better than simply force them to sane.)

    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: This was tested under sh, bash, ksh, dash and busybox!

    Same, but waiting explicitly for y or 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
    

    Using dedicated tools

    There are many tools which were built using libncurses, libgtk, libqt or other graphical libraries. For example, using whiptail:

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

    Depending on your system, you may need to replace whiptail with another similiar tool:

    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
    

    where 20 is height of dialog box in number of lines and 60 is width of the dialog box. These tools all have near same syntax.

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

    2. Bash specific solutions

    Basic in line method

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

    I prefer to use case so I could even test for yes | ja | si | oui if needed...

    in line with single key feature

    Under bash, we can specify the length of intended input for for the read command:

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

    Under bash, read command accepts a timeout parameter, which could be useful.

    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. Some tricks for dedicated tools

    More sophisticated dialog boxes, beyond simple yes - no purposes:

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

    Progress bar:

    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
    ) 
    

    Little demo:

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

    More sample? Have a look at Using whiptail for choosing USB device and USB removable storage selector: USBKeyChooser

    5. Using readline's history

    Example:

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

    This will create a file .myscript.history in your $HOME directory, than you could use readline's history commands, like Up, Down, Ctrl+r and others.

    0 讨论(0)
  • 2020-11-22 05:02

    To get a nice ncurses-like inputbox use the command dialog like this:

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

    The dialog package is installed by default at least with SUSE Linux. Looks like:

    0 讨论(0)
  • 2020-11-22 05:04

    It is possible to handle a locale-aware "Yes / No choice" in a POSIX shell; by using the entries of the LC_MESSAGES locale category, witch provides ready-made RegEx patterns to match an input, and strings for localized Yes No.

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

    EDIT: As @Urhixidur mentioned in his comment:

    Unfortunately, POSIX only specifies the first two (yesexpr and noexpr). On Ubuntu 16, yesstr and nostr are empty.

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

    LC_MESSAGES

    The yesstr and nostr locale keywords and the YESSTR and NOSTR langinfo items were formerly used to match user affirmative and negative responses. In POSIX.1-2008, the yesexpr, noexpr, YESEXPR, and NOEXPR extended regular expressions have replaced them. Applications should use the general locale-based messaging facilities to issue prompting messages which include sample desired responses.

    Alternatively using locales the Bash way:

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

    The answer is matched using locale environment's provided regexps.

    To translate the remaining messages, use bash --dump-po-strings scriptname to output the po strings for localization:

    #: 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 讨论(0)
提交回复
热议问题