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

后端 未结 30 2067
不思量自难忘°
不思量自难忘° 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: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.

提交回复
热议问题