Skip to content

Instantly share code, notes, and snippets.

@tdjordan
Last active March 24, 2017 17:54
Show Gist options
  • Save tdjordan/57300efde45f6d24ab7f to your computer and use it in GitHub Desktop.
Save tdjordan/57300efde45f6d24ab7f to your computer and use it in GitHub Desktop.

How do I prompt for input in a Linux shell script?

Edit 2015-10-29: Adding readline's history capacity Depending on posix compliant: could work on poor system with generic shell environments bash specific: using so called bashisms and if you want simple in line question / answer (generic solutions) pretty formated interfaces, like ncurses or more graphical using libgtk or libqt... use powerful readline history capability (new oct 2015)

Posix generic solutions

With poor language features, you could use 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
    echo Yes
else
    echo No
fi

Posix, but single key feature

If you want user not 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 # Care 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 is a bunch of tools which were built, using 'libncurses, libgtk, libqt' or any graphical library, for this kind of goal:

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

Depending on which distribution you're using, you could replace whiptail by:

$  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 dialog box.

Of course this values does not matter for graphical interfaces like gdialog and kdialog, but all these tools have to use the same syntax.

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

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

For this, under bash, we just have to specify the length of attended input for read command is 1

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

Under bash, read command accept 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

Using dedicated tools

Of course, all graphical tools work same under bash:

if whiptail --yesno "Is this a good question" 20 60 ;then
    echo Yes
else
    echo No
fi
Some tricks about dedicated tools
  1. If using dialog seems easy to use for simple yes/no purpose, using them for a more sophisticated dialog box may be hard to use:
whiptail --menu "Is this a good question" 20 60 12 y Yes n No m Maybe
  1. Storing answer into a variable is sometimes tricky:

The standard output is for interface drawing and the answer is printed on the error output:

answer=$($DIALOG --menu "Is this a good question" \
    20 60 12 y Yes n No m Maybe 2>&1 >/dev/tty)

or under bash:

read answer < <($DIALOG 2>&1 >/dev/tty --menu \
   "Is this a good question" 20 60 12 y Yes n No m Maybe)

or

read answer < <($DIALOG 2>&1 >/dev/tty --passwordbox "Enter pass" 20 60)
echo "Your pass is: $answer"
  1. 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
  1. Using readline's history

More than words, look (or try) this sample:

#!/bin/sh
set -i

sub myread() {
    HISTFILE=~/.myscript.history
    history -c
    history -r $HISTFILE
    read -e -p '> ' $1
}

while myread line;do
    [ "$line" = "exit" ] && break
    echo >>$HISTFILE "$line"
    echo "Doing something with '$line'"
done

This will create a file .myscript.history in your $HOME directory.

Then you could use readline's history commands, like Up, Down, Ctrl+r and others.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment