Tips en Trucs 2010

Ja/Nee vraag in een script

Om te voorkomen dat gebruikers zichzelf in nesten werken, plaats ik in scripts regelmatig de vraag of het script mag doorgaan met zijn onomkeerbare taak (bijvoorbeeld bij het formatteren van een partitie, USB stick, enz.). De hier beschreven functie stelt een vraag en valideert het antwoord.

De functie gebruikt argumenten met een paar opties en de vraag (laatste argument). De functie stelt de vraag aan de gebruiker en controleert of het antwoord "yes", "y", "ja", "j", "no", "nee" of "n" is. Het antwoord wordt eerst omgezet naar kleine letters, zodat elke combinatie met hoofdletters geldig is.

De optie --timeout N breekt de vraag na verloop van N seconden af. De optie --defaults ANS zorgt voor een standaard antwoord na het verlopen van de timeout. Deze optie zorgt ook voor een standaard antwoord bij het drukken van ENTER.

#!/bin/bash
#
#####################################################################
# Waarschuwing tonen.
function warning()
{
    echo "$*" >&2
}
#####################################################################
# Foutmelding tonen en afbreken.
function error()
{
    echo "$*" >&2
    exit 1
}
#####################################################################
# Stel Ja/Nee vraag.
#
# Gebruik: yesno OPTIES VRAAG
#
#   Opties:
#     --timeout N    Afbreken na N seconden zonder invoer.
#     --default ANS  Gebruik antwoord ANS als het standaard antwoord na timeout of
#                    bij het ingeven van een leeg antwoord.
#
# Het script stopt met als uitgangstoestand het antwoord.

function yesno()
{
    local ans
    local ok=0
    local timeout=0
    local default
    local t

    while [[ "$1" ]]
    do
        case "$1" in
        --default)
            shift
            default=$1
            if [[ ! "$default" ]]; then error "Standaard antwoord ontbreekt"; fi
            t=$(tr '[:upper:]' '[:lower:]' <<<$default)

            if [[ "$t" != 'y'  &&  "$t" != 'yes'  &&  "$t" != 'n'  &&  "$t" != 'no' ]]; then
                error "Ongeldig standaard antwoord: $default"
            fi
            default=$t
            shift
            ;;

        --timeout)
            shift
            timeout=$1
            if [[ ! "$timeout" ]]; then error "Timeout ontbreekt"; fi
            if [[ ! "$timeout" =~ ^[0-9][0-9]*$ ]]; then error "Ongeldige timeout: $timeout"; fi
            shift
            ;;

        -*)
            error "Onbekende optie: $1"
            ;;

        *)
            break
            ;;
        esac
    done

    if [[ $timeout -ne 0  &&  ! "$default" ]]; then
        error "Geef bij het gebruik van een timeout en standaard antwoord op"
    fi

    if [[ ! "$*" ]]; then error "Vraag ontbreekt"; fi

    while [[ $ok -eq 0 ]]
    do
        if [[ $timeout -ne 0 ]]; then
            if ! read -t $timeout -p "$*" ans; then
                ans=$default
            else
                # Timeout uitschakelen bij ingave antwoord.
                timeout=0
                if [[ ! "$ans" ]]; then ans=$default; fi
            fi
        else
            read -p "$*" ans
            if [[ ! "$ans" ]]; then
                ans=$default
            else
                ans=$(tr '[:upper:]' '[:lower:]' <<<$ans)
            fi 
        fi

        if [[ "$ans" == 'y' || "$ans" == 'yes' || "$ans" == 'ja' || "$ans" == 'j' || "$ans" == 'n' || "$ans" == 'nee' || "$ans" == 'no' ]]; then
            ok=1
        fi

        if [[ $ok -eq 0 ]]; then warning "Geldige antwoorden zijn: ja j yes y nee no n"; fi
    done
    [[ "$ans" == "y" || "$ans" == "yes" || "$ans" == "ja" || "$ans" == "j" ]]
}

if [[ $(basename "$0" .sh) == 'yesno' ]]; then
    if yesno "Test foutieve timeout waarde? "; then
        yesno --timeout none "Hallo? "
    fi
    if yesno "Test timeout zonder standaard antwoord? "; then
        yesno --timeout 10 "Hallo? "
    fi
    if yesno "Test foutief standaard antwoord? "; then
        yesno --default none "Hallo? "
    fi

    if yesno "Ja of nee? "; then
        echo "Uw antwoord was ja"
    else
        echo "Uw antwoord was nee"
    fi
    if yesno --default yes "Ja of nee (standaard ja) ? "; then
        echo "Uw antwoord was ja"
    else
        echo "Uw antwoord was nee"
    fi
    if yesno --default no "Ja of nee (standaard nee) ? "; then
        echo "Uw antwoord was ja"
    else
        echo "Uw antwoord was nee"
    fi
    if yesno --timeout 5 --default no "Ja of nee (timeout 5, standaard nee) ? "; then
        echo "Uw antwoord was ja"
    else
        echo "Uw antwoord was nee"
    fi
    if yesno --timeout 5 --default yes "Ja of nee (timeout 5, standaard ja) ? "; then
        echo "Uw antwoord was ja"
    else
        echo "Uw antwoord was nee"
    fi
fi

Het script start met een aantal functies die waarschuwingen en foutberichten op het scherm plaatsen. De hoofdfunctie controleert de argumenten en wacht dan op een geldig antwoord. Merk op dat indien een timeout is opgegeven en er wordt een al dan niet geldig antwoord ingegeven de timeout uitgeschakeld wordt. De laatste regel van de functie test het antwoord op "y", "yes", "ja", of "j", waardoor de uitgangstoestand van de functie wordt bepaald.

De code op het einde van het script wordt niet uitgevoerd als je deze scriptregels in uw eigen scripts opneemt, ze worden enkel uitgevoerd als je het script uitvoert zoals in het voorbeeld hieronder.

$ sh yesno.sh
Test foutieve timeout waarde? n
Test timeout zonder standaard antwoord? n
Test foutief standaard antwoord? n
Ja of nee? ok
Geldige antwoorden zijn: ja j yes y nee no n
Ja of nee? ja
Uw antwoord was ja
Ja of nee (standaard ja) ? <ENTER>
Uw antwoord was ja
Ja of nee (standaard nee) ? <ENTER>
Uw antwoord was nee
Ja of nee (timeout 5, standaard nee) ? Uw antwoord was nee
Ja of nee (timeout 5, standaard ja) ? Uw antwoord was ja

Merk op dat bij de laatste twee vragen geen antwoord werd ingegeven, waardoor het standaard antwoord na de timeout werd gebruikt. Daardoor verschijnt de echo-tekst op dezelfde regel als de vraag.