Come posso richiedere l'input Sì / No / Annulla in uno script di shell Linux?


1444

Voglio mettere in pausa l'input in uno script di shell e richiedere all'utente scelte.
La domanda standard Yes, Noo Canceltipo.
Come posso farlo in un tipico prompt di bash?


11
Proprio come una nota: le convenzioni per i prompt sono tali che se si presenta [yn]un'opzione, quella in maiuscolo è di default, ovvero il [Yn]valore predefinito è "sì" e il [yN]valore predefinito è "no". Vedi ux.stackexchange.com/a/40445/43532
Tyzoid

Chiunque venga qui da ZSH, vedere questa risposta su come utilizzare il readcomando per richiedere
smac89

Risposte:


1619

Il metodo più semplice e ampiamente disponibile per ottenere l'input dell'utente al prompt della shell è il readcomando. Il modo migliore per illustrarne l'uso è una semplice dimostrazione:

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

Un altro metodo, indicato da Steven Huwig , è il selectcomando di Bash . Ecco lo stesso esempio usando 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

Con selectte non è necessario disinfettare l'input: visualizza le opzioni disponibili e si digita un numero corrispondente alla propria scelta. Inoltre, esegue un ciclo automatico, quindi non è necessario while trueriprovare un ciclo se forniscono input non validi.

Inoltre, Léa Gris ha dimostrato un modo di rendere agnostica la lingua della richiesta nella sua risposta . Adattare il mio primo esempio per servire meglio più lingue potrebbe apparire così:

set -- $(locale LC_MESSAGES)
yesptrn="$1"; noptrn="$2"; yesword="$3"; noword="$4"

while true; do
    read -p "Install (${yesword} / ${noword})? " yn
    case $yn in
        ${yesptrn##^} ) make install; break;;
        ${noptrn##^} ) exit;;
        * ) echo "Answer ${yesword} / ${noword}.";;
    esac
done

Ovviamente qui restano non tradotte altre stringhe di comunicazione (Installa, Rispondi) che dovrebbero essere affrontate in una traduzione più completa, ma anche una traduzione parziale sarebbe utile in molti casi.

Infine, controlla l' eccellente risposta di F. Hauri .


31
Usando Bash in OS X Leopard, ho cambiato exitper non breakchiudere la scheda quando ho selezionato 'no'.
Trey Piepmeier,

1
Come funziona con opzioni più lunghe di Sì o No? Nella clausola case, scrivi qualcosa del tipo: Installa programma e non fai nulla dopo) make install; rompere;
Shawn,

1
@Shawn Usando read puoi, ovviamente, usare qualsiasi modello glob o regex supportato dall'istruzione switch della shell bash. Abbina semplicemente il testo digitato ai tuoi schemi fino a quando non trova una corrispondenza. Usando select , l'elenco delle scelte viene dato al comando e le mostra all'utente. Puoi avere gli elementi nell'elenco lunghi o corti come desideri. Consiglio di controllare le loro pagine man per informazioni complete sull'uso.
Myrddin Emrys,

3
perché c'è un breaknel selectse non c'è loop?
Jayen,

1
@akostadinov Dovrei davvero leggere di più la mia cronologia delle modifiche. Hai ragione, ho torto e ho introdotto un bug seguendo i consigli di Jayan, hah. Il bug è stato successivamente corretto, ma le modifiche erano così distanti che non ricordavo nemmeno di averlo modificato.
Myrddin Emrys,

527

Almeno cinque risposte per una domanda generica.

A seconda di

  • conforme: potrebbe funzionare su sistemi poveri con generico ambienti
  • specifico: utilizzando i cosiddetti basismi

e se vuoi

  • semplice domanda / risposta `` in linea '' (soluzioni generiche)
  • interfacce piuttosto formattate, come o più grafica usando libgtk o libqt ...
  • utilizzare potenti funzionalità di cronologia readline

1. Soluzioni generiche POSIX

È possibile utilizzare il readcomando, seguito da 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

(Grazie al commento di Adam Katz : sostituito il test sopra con uno che è più portatile ed evita un fork :)

POSIX, ma funzionalità chiave singola

Ma se non vuoi che l'utente debba premere Return, puoi scrivere:

( Modificato: come giustamente suggerisce @JonathanLeffler, salvare la configurazione di stty potrebbe essere meglio che costringerli semplicemente a diventare sani di mente .)

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

Nota: questo è stato testato sotto, , , e !

Lo stesso, ma aspettando esplicitamente yo 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

Utilizzando strumenti dedicati

Ci sono molti strumenti che sono stati costruiti usando libncurses, libgtk, libqto altre librerie grafiche. Ad esempio, usando whiptail:

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

A seconda del sistema in uso, potrebbe essere necessario sostituirlo whiptailcon un altro strumento simile:

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

dove 20è l'altezza della finestra di dialogo in numero di righe ed 60è la larghezza della finestra di dialogo. Tutti questi strumenti hanno quasi la stessa sintassi.

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

2. Soluzioni specifiche per il bash

Metodo di base in linea

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

Preferisco usare in casemodo da poter anche provare yes | ja | si | ouise necessario ...

in linea con la funzione chiave singola

In bash, possiamo specificare la lunghezza dell'input previsto per il readcomando:

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

In bash, il readcomando accetta un parametro di timeout , che potrebbe essere 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

3. Alcuni trucchi per strumenti dedicati

Finestre di dialogo più sofisticate, oltre a semplici yes - noscopi:

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

Barra di avanzamento:

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
) 

Piccola 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

Più campione? Dai un'occhiata all'utilizzo di whiptail per scegliere il dispositivo USB e il selettore di archiviazione rimovibile USB: USBKeyChooser

5. Utilizzo della cronologia di readline

Esempio:

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

Questo creerà un file .myscript.historynella $HOMEdirectory, che è possibile utilizzare i comandi di storia di readline, come Up, Down, Ctrl+ re altri.


4
Si noti che sttyprevede l' -gopzione per l'uso: old_stty=$(stty -g); stty raw -echo; …; stty "$old_stty". Ciò ripristina l'impostazione esattamente come sono state trovate, che può essere o meno la stessa stty -sane.
Jonathan Leffler,

3
read answerinterpreterà le barre rovesciate prima degli spazi e dei feed di linea, altrimenti rimuoverà ciò che è inteso raramente. Utilizzare read -r answerinvece come da SC2162 .
vlfig

1
Il metodo "Utilizzo della cronologia di readline" è terribilmente inappropriato per la domanda del PO. Sono contento che tu l'abbia incluso comunque. Ho dozzine di script molto più complicati che intendo aggiornare con quel modello!
Bruno Bronosky,

5
Puoi usare casePOSIX e bash (usa una condizione jolly piuttosto che una sottostringa bash:) case $answer in; [Yy]* ) echo Yes ;;, ma preferisco invece usare un'istruzione condizionale, preferendo [ "$answer" != "${answer#[Yy]}" ]la tua echo "$answer" | grep -iq ^y. È più portatile (alcuni greps non GNU non implementano -qcorrettamente) e non ha la chiamata di sistema. ${answer#[Yy]}utilizza l'espansione dei parametri per rimuovere Yo ydall'inizio di $answer, causando una disuguaglianza quando uno dei due è presente. Funziona con qualsiasi shell POSIX (trattino, ksh, bash, zsh, busybox, ecc.).
Adam Katz,

1
@CarterPape Sì, era uno scherzo! Ma in questa risposta dettagliata, potresti trovare molti suggerimenti (il secondo punto presenta almeno 3 metodi diversi)! E ... da circa 5 anni ormai, sei il primo a parlare del mio metodo di conteggio! ;-))
F. Hauri

349
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"

25
Non sono d'accordo, perché implementa solo una parte della funzionalità della finestra di dialogo "Sì, No, Annulla" in DOS. La parte che non riesce a implementare è il controllo input ... in loop fino a quando non viene ricevuta una risposta valida.
Myrddin Emrys,

1
(Il titolo della domanda originale era "Come posso richiedere l'input in uno script di shell Linux?")
Pistos

8
Ma la descrizione della domanda originale è rimasta invariata e ha sempre chiesto una risposta al prompt Sì / No / Annulla. Il titolo è stato aggiornato per essere più chiaro del mio originale, ma la descrizione della domanda era sempre chiara (secondo me).
Myrddin Emrys,

165

È possibile utilizzare il comando di lettura integrato ; Utilizzare l' -popzione per richiedere all'utente una domanda.

Da BASH4, ora puoi usare -iper suggerire una risposta:

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

(Ricorda però di utilizzare l'opzione "readline" -eper consentire la modifica delle linee con i tasti freccia)

Se vuoi una logica "sì / no", puoi fare qualcosa del genere:

read -e -p "
List the content of your home dir ? [Y/n] " YN

[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/

6
Va notato che FILEPATHè il nome della variabile che hai scelto ed è impostato con la risposta al prompt dei comandi. Quindi, se dovessi eseguire vlc "$FILEPATH", ad esempio, vlcaprire quel file.
Ken Sharp,

Qual è il vantaggio del -esecondo esempio (semplice sì / no)?
JBallin,

Qualche motivo per usare -e -pinvece di -ep?
JBallin,

1
Senza l' -eopzione / flag, potresti (a seconda dell'implementazione) non essere in grado di digitare "y", quindi cambiare idea e sostituirlo con una "n" (o qualsiasi altra cosa); Quando si documenta un comando, elencare le opzioni separatamente è meglio per leggibilità / chiarezza, tra le altre ragioni.
yPhil

109

Bash ha scelto per questo scopo.

select result in Yes No Cancel
do
    echo $result
done

18
+1 Soluzione genialmente semplice. L'unica cosa: questo verrà richiesto e richiesto e richiesto ... fino a quando non si aggiunge un exitinterno :)
kaiser

5
(Kaiser: Per uscire da esso, basta inserire l'EOT:. Ctrl-DMa ovviamente, il codice reale che lo utilizza avrà bisogno di una pausa o di un'uscita nel corpo.)
Zorawar

12
Ciò non ti consentirà di inserire y o n, ma puoi scegliere inserendo 1 2 o 3.
djjeck

3
exituscirà dallo script tutti insieme, breakuscirà solo dal loop in cui ci si trova (se si è in un whileo caseloop)
wranvaud

57
read -p "Are you alright? (y/n) " RESP
if [ "$RESP" = "y" ]; then
  echo "Glad to hear it"
else
  echo "You need more bash programming"
fi

36

Ecco qualcosa che ho messo insieme:

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

Sono un principiante, quindi prendilo con un granello di sale, ma sembra funzionare.


9
Se cambi case $yn ina case ${yn:-$2} inallora puoi usare il secondo argomento come valore predefinito, Y o N.
jchook

1
o cambia case $ynper case "${yn:-Y}"avere sì come predefinito
rubo77

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

...

1
Inserisci quattro spazi all'inizio di ogni riga per preservare la formattazione del codice.
Jouni K. Seppänen,

10
Perché forniamo 'y' e 'n' come parametri per indagare () se gli switch case sono hardcoded? Questo è solo chiedere un uso improprio. Sono parametri fissi, non modificabili, quindi l'eco sulla riga 2 dovrebbe essere: echo -n "$ 1 [Y / N]?" Non possono essere modificati, quindi non devono essere forniti.
Myrddin Emrys,

1
@MyrddinEmrys Potresti per favore elaborare il tuo commento? Oppure pubblica un link a un articolo o un paio di parole chiave in modo da poter fare ricerche da solo.
Mateusz Piotrowski,

4
@MateuszPiotrowski La risposta è stata modificata e migliorata da quando ho fatto il mio commento. Puoi fare clic sul link "modificato il 23 dicembre" sopra per visualizzare tutte le versioni precedenti di questa risposta. Nel 2008, il codice era abbastanza diverso.
Myrddin Emrys il

27

Tu vuoi:

  • Comandi integrati Bash (ad es. Portatili)
  • Controlla TTY
  • Risposta predefinita
  • Tempo scaduto
  • Domanda colorata

Frammento

do_xxxx=y                      # In batch mode => Default is Yes
[[ -t 0 ]] &&                  # If TTY => Prompt the question
read -n 1 -p $'\e[1;32m
Do xxxx? (Y/n)\e[0m ' do_xxxx  # Store the answer in $do_xxxx
if [[ $do_xxxx =~ ^(y|Y|)$ ]]  # Do if 'y' or 'Y' or empty
then
    xxxx
fi

spiegazioni

  • [[ -t 0 ]] && read ...=> Chiama il comando readse TTY
  • read -n 1 => Attendi un carattere
  • $'\e[1;32m ... \e[0m '=> Stampa in verde
    (il verde va bene perché è leggibile su entrambi gli sfondi bianchi / neri)
  • [[ $do_xxxx =~ ^(y|Y|)$ ]] => bash regex

Timeout => La risposta predefinita è No

do_xxxx=y
[[ -t 0 ]] && {                   # Timeout 5 seconds (read -t 5)
read -t 5 -n 1 -p $'\e[1;32m
Do xxxx? (Y/n)\e[0m ' do_xxxx ||  # read 'fails' on timeout
do_xxxx=n ; }                     # Timeout => answer No
if [[ $do_xxxx =~ ^(y|Y|)$ ]]
then
    xxxx
fi

26

Il modo più semplice per raggiungere questo obiettivo con il minor numero di righe è il seguente:

read -p "<Your Friendly Message here> : y/n/cancel" CONDITION;

if [ "$CONDITION" == "y" ]; then
   # do something here!
fi

Questo ifè solo un esempio: dipende da te come gestire questa variabile.


20

Usa il readcomando:

echo Would you like to install? "(Y or N)"

read x

# now check if $x is "y"
if [ "$x" = "y" ]; then
    # do something here!
fi

e poi tutte le altre cose di cui hai bisogno


17

Questa soluzione legge un singolo carattere e chiama una funzione su una risposta sì.

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

2
@Jav l'eco stampa una nuova riga dopo la tua risposta. Senza di essa, la prossima cosa da stampare apparirebbe immediatamente dopo la tua risposta sulla stessa riga. Prova a rimuovere il echoper vedere di persona.
Dennis

13

Per ottenere una bella casella di input simile a ncurses, utilizzare la finestra di dialogo comandi in questo modo:

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

Il pacchetto di dialogo è installato di default almeno con SUSE Linux. Sembra: il comando "dialog" in azione


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

L' -eopzione consente all'utente di modificare l'input usando i tasti freccia.

Se si desidera utilizzare un suggerimento come input:

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

-i L'opzione stampa un input suggestivo.


sì, -e -inon lavorare in sh (Bourne shell), ma la domanda è taggata bash
nello

12

È possibile utilizzare il valore predefinito REPLYsu a read, convertire in lettere minuscole e confrontarlo con un insieme di variabili con un'espressione.
Lo script supporta anche ja/ si/oui

read -rp "Do you want a demo? [y/n/c] "

[[ ${REPLY,,} =~ ^(c|cancel)$ ]] && { echo "Selected Cancel"; exit 1; }

if [[ ${REPLY,,} =~ ^(y|yes|j|ja|s|si|o|oui)$ ]]; then
   echo "Positive"
fi

12

È possibile gestire una scelta "Sì / No" in base alle impostazioni locali in una shell POSIX; usando le voci della LC_MESSAGEScategoria locale, witch fornisce schemi RegEx già pronti per abbinare un input e stringhe per localizzato Sì 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: Come @Urhixidur ha menzionato nel suo commento :

Sfortunatamente, POSIX specifica solo i primi due (yesexpr e noexpr). Su Ubuntu 16, yesstr e nostr sono vuoti.

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

LC_MESSAGES

Le parole chiave yesstre nostrlocale e gli elementi YESSTRe NOSTRlanginfo erano precedentemente utilizzati per abbinare le risposte affermative e negative dell'utente. Nel POSIX.1-2008, il yesexpr, noexpr, YESEXPR, e NOEXPRle espressioni regolari estese hanno li ha sostituiti. Le applicazioni dovrebbero utilizzare le strutture di messaggistica generali basate su impostazioni locali per inviare messaggi di richiesta che includono risposte di esempio desiderate.

In alternativa, usando le localizzazioni nel modo Bash:

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

La risposta viene abbinata utilizzando regexps forniti dall'ambiente locale.

Per tradurre i messaggi rimanenti, utilizzare bash --dump-po-strings scriptnameper generare le stringhe di po per la localizzazione:

#: 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 ""

Adoro l'aggiunta di un'opzione agnostica linguistica. Molto bene.
Myrddin Emrys,

1
Sfortunatamente, POSIX specifica solo i primi due (yesexpr e noexpr). Su Ubuntu 16, yesstr e nostr sono vuoti.
Urhixidur,

1
Ma aspetta! Ci sono notizie peggiori! Le espressioni dell'istruzione case bash non sono regolari, sono espressioni di nome file. Quindi yesexpr e noexpr di Ubuntu 16 ("^ [yY]. *" E "^ [nN]. *", Rispettivamente) falliranno completamente a causa del periodo incorporato. In un'espressione regolare, ". *" Significa "qualsiasi carattere non newline, zero o più volte". Ma in una dichiarazione del caso, è un "." Letterale seguito da un numero qualsiasi di caratteri.
Urhixidur

1
Finalmente il meglio che può essere fatto con yesexpre noexprin un ambiente shell, è usarlo nello specifico abbinamento RegEx di Bashif [[ "$yn" =~ $yesexpr ]]; then echo $"Answered yes"; else echo $"Answered no"; fi
Léa Gris

10

Solo pressione di un tasto

Ecco un approccio più lungo, ma riutilizzabile e modulare:

  • Restituisce 0= sì e 1= no
  • Non è necessario premere invio richiesto - solo un singolo carattere
  • Può premere enterper accettare la scelta predefinita
  • È possibile disabilitare la scelta predefinita per forzare una selezione
  • Funziona per entrambi zshe bash.

L'impostazione predefinita è "no" quando si preme invio

Si noti che Nè in maiuscolo. Qui viene premuto Invio, accettando il valore predefinito:

$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]?

Si noti inoltre che è [y/N]?stato aggiunto automaticamente. Il "no" predefinito è accettato, quindi non viene ripetuto nulla.

Richiama finché non viene fornita una risposta valida:

$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]? X
Show dangerous command [y/N]? y
rm *

L'impostazione predefinita è "sì" quando si preme invio

Nota che Yè in maiuscolo:

$ confirm_yes "Show dangerous command" && echo "rm *"
Show dangerous command [Y/n]?
rm *

Sopra, ho appena premuto Invio, quindi il comando è stato eseguito.

Nessun valore predefinito attivo enter: richiede yon

$ get_yes_keypress "Here you cannot press enter. Do you like this [y/n]? "
Here you cannot press enter. Do you like this [y/n]? k
Here you cannot press enter. Do you like this [y/n]?
Here you cannot press enter. Do you like this [y/n]? n
$ echo $?
1

Qui, 1o falso è stato restituito. Tieni presente che con questa funzione di livello inferiore dovrai fornire la tua[y/n]? prompt.

Codice

# Read a single char from /dev/tty, prompting with "$*"
# Note: pressing enter will return a null string. Perhaps a version terminated with X and then remove it in caller?
# See https://unix.stackexchange.com/a/367880/143394 for dealing with multi-byte, etc.
function get_keypress {
  local REPLY IFS=
  >/dev/tty printf '%s' "$*"
  [[ $ZSH_VERSION ]] && read -rk1  # Use -u0 to read from STDIN
  # See https://unix.stackexchange.com/q/383197/143394 regarding '\n' -> ''
  [[ $BASH_VERSION ]] && </dev/tty read -rn1
  printf '%s' "$REPLY"
}

# Get a y/n from the user, return yes=0, no=1 enter=$2
# Prompt using $1.
# If set, return $2 on pressing enter, useful for cancel or defualting
function get_yes_keypress {
  local prompt="${1:-Are you sure [y/n]? }"
  local enter_return=$2
  local REPLY
  # [[ ! $prompt ]] && prompt="[y/n]? "
  while REPLY=$(get_keypress "$prompt"); do
    [[ $REPLY ]] && printf '\n' # $REPLY blank if user presses enter
    case "$REPLY" in
      Y|y)  return 0;;
      N|n)  return 1;;
      '')   [[ $enter_return ]] && return "$enter_return"
    esac
  done
}

# Credit: http://unix.stackexchange.com/a/14444/143394
# Prompt to confirm, defaulting to NO on <enter>
# Usage: confirm "Dangerous. Are you sure?" && rm *
function confirm {
  local prompt="${*:-Are you sure} [y/N]? "
  get_yes_keypress "$prompt" 1
}    

# Prompt to confirm, defaulting to YES on <enter>
function confirm_yes {
  local prompt="${*:-Are you sure} [Y/n]? "
  get_yes_keypress "$prompt" 0
}

Quando ho testato questa sceneggiatura, invece del risultato precedente ho ricevuto, Show dangerous command [y/N]? [y/n]?eShow dangerous command [Y/n]? [y/n]?
Ilias Karim il

Grazie @IliasKarim, l'ho risolto proprio ora.
Tom Hale,

9

Ci scusiamo per la pubblicazione su un post così vecchio. Alcune settimane fa stavo affrontando un problema simile, nel mio caso avevo bisogno di una soluzione che funzionasse anche all'interno di uno script di installazione online, ad esempio:curl -Ss https://raw.github.com/_____/installer.sh | bash

L'utilizzo read yesno < /dev/ttyfunziona bene per me:

echo -n "These files will be uploaded. Is this ok? (y/n) "
read yesno < /dev/tty

if [ "x$yesno" = "xy" ];then

   # Yes
else

   # No
fi

Spero che questo aiuti qualcuno.


Una parte importante di ciò è la convalida dell'input. Penso che adattare il mio primo esempio per accettare l' ttyinput come hai fatto avrebbe fatto anche per te, e ho anche ottenuto un loop su input errato (immagina alcuni caratteri nel buffer; il tuo metodo costringerebbe l'utente a scegliere sempre no).
Myrddin Emrys,

7

Ho notato che nessuno ha pubblicato una risposta che mostra il menu echo multilinea per un input utente così semplice, quindi ecco il mio passo avanti:

#!/bin/bash

function ask_user() {    

echo -e "
#~~~~~~~~~~~~#
| 1.) Yes    |
| 2.) No     |
| 3.) Quit   |
#~~~~~~~~~~~~#\n"

read -e -p "Select 1: " choice

if [ "$choice" == "1" ]; then

    do_something

elif [ "$choice" == "2" ]; then

    do_something_else

elif [ "$choice" == "3" ]; then

    clear && exit 0

else

    echo "Please select 1, 2, or 3." && sleep 3
    clear && ask_user

fi
}

ask_user

Questo metodo è stato pubblicato nella speranza che qualcuno potesse trovarlo utile e risparmiare tempo.


4

Versione a scelta multipla:

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
}

Esempio:

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

Verrà impostato REPLYsu y(all'interno dello script).


4

Ti suggerisco di usare la finestra di dialogo ...

Apprendista Linux: migliora gli script di Bash Shell usando la finestra di dialogo

Il comando dialog abilita l'uso delle finestre negli script della shell per renderne più interattivo l'uso.

è semplice e facile da usare, c'è anche una versione di gnome chiamata gdialog che accetta esattamente gli stessi parametri, ma mostra lo stile della GUI su X.


Per il lettore occasionale, guarda uno snippet usando il comando dialog qui: stackoverflow.com/a/22893526/363573
Stephan

4

Ispirato dalle risposte di @Mark e @Myrddin, ho creato questa funzione per un prompt universale

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
}

usalo così:

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

4

più generico sarebbe:

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
}

3

Un modo semplice per farlo è con xargs -po gnuparallel --interactive .

Mi piace il comportamento di xargs un po 'meglio per questo perché esegue ogni comando immediatamente dopo il prompt come altri comandi unix interattivi, piuttosto che raccogliere gli yess da eseguire alla fine. (Puoi Ctrl-C dopo aver superato quelli che volevi.)

per esempio,

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

Non male, ma xargs --interactiveè limitato a sì o no. Finché è tutto ciò di cui hai bisogno, può essere sufficiente, ma la mia domanda originale ha fornito un esempio con tre possibili risultati. Mi piace davvero che sia streaming; molti scenari comuni trarrebbero beneficio dalla sua capacità di essere convogliato.
Myrddin Emrys il

Vedo. Il mio pensiero era che "annulla" significava semplicemente interrompere tutte le ulteriori esecuzioni, cosa che supporta tramite Ctrl-C, ma se hai bisogno di fare qualcosa di più complesso su Annulla (o su No), ciò non sarà sufficiente.
Joshua Goldberg,

3

Come amico di un comando a una riga ho usato quanto segue:

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

Scritto longform, funziona così:

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

Potete chiarire l'uso della variabile prompt? Mi sembra che sia stato cancellato dopo il liner, quindi come usi la linea per fare qualcosa?
Myrddin Emrys il

il prompt viene cancellato dopo il ciclo while. Perché voglio che la variabile prompt venga inizializzata in seguito (poiché sto usando l'istruzione più spesso). Avere questa riga in uno script di shell procederà solo se si digita y | Y e si esce se si digita n | N o si ripete chiedendo input per tutto il resto.
ccDict

3

Ho usato la casefrase un paio di volte in uno scenario del genere, usare la dichiarazione case è un buon modo per farlo. Un whileloop, che ecapsula il caseblocco, che utilizza una condizione booleana può essere implementato per mantenere un controllo ancora maggiore del programma e soddisfare molti altri requisiti. Dopo che tutte le condizioni sono state soddisfatte, è breakpossibile utilizzare una a che riporterà il controllo alla parte principale del programma. Inoltre, per soddisfare altre condizioni, ovviamente è possibile aggiungere istruzioni condizionali per accompagnare le strutture di controllo: caseistruzione e possibilewhile loop.

Esempio di utilizzo di una casedichiarazione per soddisfare la tua richiesta

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

3

Sì / No / Annulla

Funzione

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

uso

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

Confermare con input utente pulito

Funzione

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

uso

if @confirm 'Confirm?' ; then
  echo "Yes"
else
  echo "No"
fi

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

Questo sembra complesso e fragile. Che ne dici diyn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
tripleee,

2

In risposta ad altri:

Non è necessario specificare maiuscole / minuscole in BASH4, basta usare ',,' per rendere una var minuscola. Inoltre, non mi piace molto inserire il codice all'interno del blocco di lettura, ottenere il risultato e gestirlo al di fuori del blocco di lettura IMO. Includi anche una 'q' per uscire dall'IMO. Infine, perché digitare 'yes' basta usare -n1 e premere y.

Esempio: l'utente può premere y / n e anche q per uscire.

ans=''
while true; do
    read -p "So is MikeQ the greatest or what (y/n/q) ?" -n1 ans
    case ${ans,,} in
        y|n|q) break;;
        *) echo "Answer y for yes / n for no  or q for quit.";;
    esac
done

echo -e "\nAnswer = $ans"

if [[ "${ans,,}" == "q" ]] ; then
        echo "OK Quitting, we will assume that he is"
        exit 0
fi

if [[ "${ans,,}" == "y" ]] ; then
        echo "MikeQ is the greatest!!"
else
        echo "No? MikeQ is not the greatest?"
fi

0

La maggior parte delle volte in tali scenari, è necessario continuare a eseguire lo script fino a quando l'utente continua a immettere "yes" e deve interrompere solo quando l'utente immette "no". Lo snippet di seguito ti aiuterà a raggiungere questo obiettivo!

#!/bin/bash
input="yes"
while [ "$input" == "yes" ]
do
  echo "execute script functionality here..!!"
  echo "Do you want to continue (yes/no)?"
  read input
done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.