linux - script - shell unix




Comment puis-je demander une entrée Oui/Non/Annuler dans un script shell Linux? (17)

Je souhaite suspendre l'entrée dans un script shell et inviter l'utilisateur à faire des choix. La question de type "Oui, Non, ou Annuler". Comment puis-je accomplir cela dans une invite bash typique?


Au moins cinq réponses pour une question générique.

Cela dépend de

  • posix conforme: pourrait fonctionner sur des systèmes pauvres avec des environnements shell génériques
  • bash spécifique: en utilisant ce que l'on appelle des bashismes

et si tu veux

  • simple question / réponse «en ligne» (solutions génériques)
  • Des interfaces assez formatées, comme ncurses ou plus graphiques utilisant libgtk ou libqt ...
  • utiliser une puissante capacité d'historique de readline

1. Solutions génériques POSIX

Vous pouvez utiliser la commande read , suivie de if ... then ... else :

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

# if echo "$answer" | grep -iq "^y" ;then

# (Merci au commentaire d'Adam Katz : Ce test est plus portable et évite une fourchette :)

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

POSIX, mais une seule touche

Mais si vous ne voulez pas que l'utilisateur clique sur Retour , vous pouvez écrire:

( Edité: Comme le suggère à juste titre Jonathan Johnson, sauver la configuration de stty pourrait être mieux que de simplement les forcer à être sains d' esprit .)

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: Ceci a été testé sous sh , bash , ksh , dash et busybox !

Pareil, mais en attendant explicitement y ou 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

Utiliser des outils dédiés

De nombreux outils ont été construits en utilisant libncurses , libgtk , libqt ou d'autres bibliothèques graphiques. Par exemple, en utilisant whiptail :

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

Selon votre système, vous devrez peut-être remplacer le whiptail par un autre outil similaire:

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 est la hauteur de la boîte de dialogue en nombre de lignes et 60 est la largeur de la boîte de dialogue. Ces outils ont tous la même syntaxe.

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

2. Solutions spécifiques Bash

Méthode de base en ligne

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

Je préfère utiliser le case afin que je puisse même tester pour yes | ja | si | oui yes | ja | si | oui yes | ja | si | oui si nécessaire ...

en ligne avec une seule caractéristique clé

Sous bash, nous pouvons spécifier la longueur de l'entrée prévue pour la commande read :

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

Sous bash, la commande read accepte un paramètre timeout , ce qui pourrait être utile.

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

Quelques astuces pour des outils dédiés

Des boîtes de dialogue plus sophistiquées, au-delà du simple yes - no but:

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

Barre de progression:

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
) 

Petite démo:

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

5. Utilisation de l'historique de readline

Exemple:

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

Cela créera un fichier .myscript.history dans votre répertoire $HOME , que vous pourriez utiliser les commandes d'historique de readline, comme Up , Down , Ctrl + r et autres.


Oui / Non / Annuler

Fonction

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

Confirmer avec une entrée utilisateur propre

Fonction

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

Bash a select à cet effet.

select result in Yes No Cancel
do
    echo $result
done

Cette solution lit un seul caractère et appelle une fonction sur une réponse oui.

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

En tant qu'ami d'une commande d'une ligne j'ai utilisé ce qui suit:

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 écrit, cela fonctionne comme ceci:

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

Inspiré par les réponses de @Mark et @Myrddin, j'ai créé cette fonction pour une invite universelle

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
}

Utilisez-le comme ceci:

unipromtp "Select an option: (a)-Do one (x)->Do two (f)->Do three : " "a x f" selection
echo "$selection"

J'ai utilisé l'énoncé de case à plusieurs reprises dans un tel scénario, l'utilisation de la déclaration de cas est un bon moyen de s'y prendre. Une boucle while, qui encapsule le bloc de case , qui utilise une condition booléenne peut être implémentée afin de conserver encore plus de contrôle sur le programme, et de répondre à de nombreuses autres exigences. Après que toutes les conditions aient été remplies, une break peut être utilisée pour renvoyer le contrôle à la partie principale du programme. Aussi, pour répondre à d'autres conditions, bien sûr des instructions conditionnelles peuvent être ajoutées pour accompagner les structures de contrôle: instruction case et possible while loop.

Exemple d'utilisation d'une instruction case pour répondre à votre demande

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


Pour obtenir une belle entrée de type ncurses, utilisez la boîte de dialogue de commande comme ceci:

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

Le paquet de dialogue est installé par défaut au moins avec SUSE Linux.


Une façon simple de le faire est avec xargs -p ou gnu parallel --interactive .

J'apprécie un peu mieux le comportement de xargs car il exécute chaque commande immédiatement après l'invite comme d'autres commandes unix interactives, plutôt que de collecter les réponses pour qu'elles s'exécutent à la fin. (Vous pouvez Ctrl-C après avoir passé ceux que vous vouliez.)

par exemple,

echo *.xml | xargs -p -n 1 -J {} mv {} backup/

Version à choix multiples:

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
}

Exemple:

$ 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
$

Il va définir REPLY à y (à l'intérieur du script).


Voici quelque chose que je mets ensemble:

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

Je suis un débutant, alors prends ça avec un grain de sel, mais ça a l'air de marcher.


plus générique serait:

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
}

La méthode la plus simple et la plus largement disponible pour obtenir une entrée de l'utilisateur à l'invite du shell est la commande read . La meilleure façon d'illustrer son utilisation est une démonstration simple:

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

Une autre méthode, soulignée par Steven Huwig, est la commande select de Bash. Voici le même exemple en utilisant 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

Avec select vous n'avez pas besoin de désinfecter l'entrée - il affiche les choix disponibles, et vous tapez un nombre correspondant à votre choix. Il boucle également automatiquement, il n'y a donc pas besoin d'une boucle while true while pour réessayer si elle donne une entrée invalide.

Aussi, s'il vous plaît vérifier l' excellente réponse de F. Hauri.


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?"

...

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

L'option -e permet à l'utilisateur de modifier l'entrée à l'aide des touches fléchées.

Si vous souhaitez utiliser une suggestion en entrée:

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

-i option -i imprime une entrée suggestive.


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!'




scripting