¿Cómo solicito la entrada Sí / No / Cancelar en un script de shell de Linux?

1445

Quiero pausar la entrada en un script de shell y pedirle al usuario que elija.
La norma Yes, Noo Canceltipo de pregunta.
¿Cómo logro esto en un típico bash prompt?

Myrddin Emrys
fuente
12
Solo como una nota: la convención para los avisos es tal que si presenta una [yn]opción, la que está en mayúscula es la predeterminada, es decir, el valor predeterminado es [Yn]"sí" y el valor [yN]predeterminado es "no". Ver ux.stackexchange.com/a/40445/43532
Tyzoid
Cualquiera que venga aquí desde ZSH, vea esta respuesta sobre cómo usar el readcomando para solicitar
smac89

Respuestas:

1620

El método más simple y más ampliamente disponible para obtener la entrada del usuario en un indicador de comandos de shell es el readcomando. La mejor manera de ilustrar su uso es una demostración 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

Otro método, señalado por Steven Huwig , es el selectcomando de Bash . Aquí está el mismo ejemplo 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 selectusted no necesita desinfectar la entrada: muestra las opciones disponibles y escribe un número correspondiente a su elección. También realiza un bucle automático, por lo que no es necesario que un while truebucle vuelva a intentarlo si proporcionan una entrada no válida.

Además, Léa Gris demostró una forma de hacer que el lenguaje de solicitud fuera agnóstico en su respuesta . La adaptación de mi primer ejemplo para servir mejor a varios idiomas podría verse así:

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

Obviamente, otras cadenas de comunicación permanecen sin traducir aquí (Instalar, Responder), lo que debería abordarse en una traducción más completa, pero incluso una traducción parcial sería útil en muchos casos.

Finalmente, mira la excelente respuesta de F. Hauri .

Myrddin Emrys
fuente
31
Usando Bash en OS X Leopard, cambié exita breakpara evitar cerrar la pestaña cuando seleccioné 'no'.
Trey Piepmeier
1
¿Cómo funciona esto con opciones más largas que Sí o No? En la cláusula case, escribe algo como: Instalar programa y no hacer nada después) make install; rotura;
Shawn
1
@Shawn Usando read puedes, por supuesto, usar cualquier patrón glob o regex soportado por la instrucción switch de bash shell. Simplemente coincide con el texto escrito en sus patrones hasta que encuentra una coincidencia. Usando select , la lista de opciones se le da al comando y se las muestra al usuario. Puede hacer que los elementos de la lista sean tan largos o tan cortos como desee. Recomiendo consultar sus páginas de manual para obtener información completa sobre el uso.
Myrddin Emrys
3
¿Por qué hay un breaken el selectsi no hay bucle?
Jayen
1
@akostadinov Realmente debería leer más mi historial de edición. Tienes razón, estoy equivocado, e introduje un error siguiendo el consejo de Jayan, ja. El error fue corregido más tarde, pero las ediciones estaban tan separadas que ni siquiera recordaba haberlo cambiado.
Myrddin Emrys
527

Al menos cinco respuestas para una pregunta genérica.

Dependiendo de

  • compatible: podría funcionar en sistemas pobres con genéricos ambientes
  • específico: usando los llamados basismos

y si tu quieres

  • pregunta / respuesta simple `` en línea '' (soluciones genéricas)
  • interfaces bonitas formateadas, como o más gráfico usando libgtk o libqt ...
  • utilizar la poderosa capacidad de historial de lectura

1. Soluciones genéricas POSIX

Puede usar el readcomando, seguido de 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

(Gracias al comentario de Adam Katz : reemplazó la prueba anterior con una que es más portátil y evita una bifurcación :)

POSIX, pero característica clave única

Pero si no desea que el usuario tenga que golpear Return, puede escribir:

( Editado: como @JonathanLeffler sugiere correctamente, guardar la configuración de stty podría ser mejor que simplemente obligarlos a estar cuerdos ).

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: Esto fue probado bajo, , , y !

Lo mismo, pero esperando explícitamente 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

Usando herramientas dedicadas

Hay muchas herramientas que fueron construidos usando libncurses, libgtk, libqtu otras librerías gráficas. Por ejemplo, usando whiptail:

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

Dependiendo de su sistema, es posible que deba reemplazarlo whiptailcon otra herramienta similar:

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

donde 20es la altura del cuadro de diálogo en número de líneas y el 60ancho del cuadro de diálogo. Todas estas herramientas tienen casi la misma sintaxis.

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

2. Soluciones específicas de Bash

Método básico en línea

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

Prefiero usar casepara poder probar yes | ja | si | ouisi es necesario ...

en línea con la función de tecla única

En bash, podemos especificar la longitud de la entrada prevista para el readcomando:

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

En bash, el readcomando acepta un parámetro de tiempo de espera , que podría ser útil.

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. Algunos trucos para herramientas dedicadas

Cuadros de diálogo más sofisticados, más allá de yes - nopropósitos simples :

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

Barra de progreso:

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
) 

Pequeña 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

Más muestra? Eche un vistazo al uso de Whiptail para elegir un dispositivo USB y un selector de almacenamiento extraíble USB: USBKeyChooser

5. Usando el historial de readline

Ejemplo:

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

Esto creará un archivo .myscript.historyen su $HOMEdirectorio, lo que se podría utilizar comandos de la historia de readline, como Up, Down, Ctrl+ rotros y.

F. Hauri
fuente
44
Tenga en cuenta que sttyofrece la -gopción de uso: old_stty=$(stty -g); stty raw -echo; …; stty "$old_stty". Esto restaura la configuración exactamente como se encontraron, lo que puede o no ser lo mismo stty -sane.
Jonathan Leffler
3
read answerinterpretará las barras diagonales inversas antes de los espacios y los avances de línea, y de lo contrario los eliminará, lo que rara vez se pretende. Utilice en su read -r answerlugar según SC2162 .
vlfig
1
El método "Usar el historial de readline" es terriblemente inapropiado para la pregunta del OP. Me alegro de que lo hayas incluido de todos modos. ¡Tengo docenas de scripts mucho más complicados que tengo la intención de actualizar con ese patrón!
Bruno Bronosky
55
Puede usar casePOSIX así como bash (use una condición comodín en lugar de una subcadena bash:) case $answer in; [Yy]* ) echo Yes ;;, pero prefiero usar una declaración condicional en su lugar, favoreciendo [ "$answer" != "${answer#[Yy]}" ]su echo "$answer" | grep -iq ^y. Es más portátil (algunos greps que no son GNU no se implementan -qcorrectamente) y no tiene la llamada al sistema. ${answer#[Yy]}usa la expansión de parámetros para eliminar Yo ydesde el principio $answer, causando una desigualdad cuando cualquiera de los dos está presente. Esto funciona en cualquier shell POSIX (guión, ksh, bash, zsh, busybox, etc.).
Adam Katz
1
@CarterPape Sí, esto fue una broma! ¡Pero en esta respuesta detallada, puede encontrar muchos consejos (el segundo punto presenta al menos 3 métodos diferentes)! Y ... desde hace aproximadamente 5 años, ¡eres el primero en contar mi método de conteo! ;-))
F. Hauri
349
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
Pistos
fuente
26
No estoy de acuerdo, porque solo implementa una parte de la funcionalidad del cuadro de diálogo 'Sí, No, Cancelar' en DOS. La parte que no puede implementar es la verificación de entrada ... en bucle hasta que se reciba una respuesta válida.
Myrddin Emrys
1
(El título original de la pregunta era "¿Cómo solicito la entrada en un script de shell de Linux?")
Pistos
8
Pero la descripción original de la pregunta no ha cambiado, y siempre solicitó una respuesta a un mensaje Sí / No / Cancelar. El título se ha actualizado para que sea más claro que el original, pero la descripción de la pregunta siempre fue clara (en mi opinión).
Myrddin Emrys
165

Puede usar el comando de lectura incorporado ; Use la -popción para preguntar al usuario con una pregunta.

Desde BASH4, ahora puede usar -ipara sugerir una respuesta:

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

(Pero recuerde usar la opción "readline" -epara permitir la edición de línea con las teclas de flecha)

Si desea una lógica de "sí / no", puede hacer algo como esto:

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

[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/
yPhil
fuente
66
Cabe señalar que FILEPATHes el nombre de la variable que ha elegido y se configura con la respuesta al símbolo del sistema. Entonces, si tuviera que ejecutar vlc "$FILEPATH", por ejemplo, vlcabriría ese archivo.
Ken Sharp
¿Cuál es el beneficio del -esegundo ejemplo (simple sí / no)?
JBallin
¿Alguna razón para usar en -e -plugar de -ep?
JBallin
1
Sin el -eindicador / opción, es posible que (según la implementación) no pueda escribir "y", y luego cambiar de opinión y reemplazarlo con una "n" (o cualquier otra cosa); Al documentar un comando, enumerar las opciones por separado es mejor para facilitar la lectura / claridad, entre otras razones.
yPhil
109

Bash ha seleccionado para este propósito.

select result in Yes No Cancel
do
    echo $result
done
Steven Huwig
fuente
18
+1 Solución genéricamente simple. Lo único: esto le indicará y le indicará y le indicará ... hasta que agregue un exitinterior :)
kaiser
55
(Kaiser: para romperlo, solo ingrese el EOT:. Ctrl-DPero, por supuesto, el código real que lo use necesitará un descanso o una salida en el cuerpo).
Zorawar
12
Sin embargo, esto no le permitirá ingresar y o n. Usted elige ingresando 1 2 o 3.
djjeck
3
exitsaldrá de la escritura de todos juntos, breaksólo salir del bucle que se encuentra (si se encuentra en una whileo casebucle)
wranvaud
58
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
serg
fuente
36

Aquí hay algo que puse juntos:

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

Soy un principiante, así que tómalo con un grano de sal, pero parece funcionar.

mpen
fuente
99
Si cambia case $yn ina case ${yn:-$2} in, puede usar el segundo argumento como valor predeterminado, Y o N.
jchook
1
o cambie case $yna case "${yn:-Y}"tener sí como predeterminado
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?"

...
SumoRunner
fuente
1
Coloque cuatro espacios al comienzo de cada línea para preservar el formato del código.
Jouni K. Seppänen
10
¿Por qué proporcionamos 'y' y 'n' como parámetros para preguntar () si los interruptores de caso están codificados? Eso es solo pedir mal uso. Son parámetros fijos, no modificables, por lo que el eco en la línea 2 debería leer: echo -n "$ 1 [S / N]?" No se pueden cambiar, por lo que no se deben suministrar.
Myrddin Emrys
1
@MyrddinEmrys ¿Podría por favor elaborar su comentario? O publique un enlace a un artículo o un par de palabras clave para que pueda investigar por mi cuenta.
Mateusz Piotrowski
44
@MateuszPiotrowski La respuesta ha sido editada y mejorada desde que hice mi comentario. Puede hacer clic en el enlace 'editado el 23 de diciembre' para ver todas las versiones anteriores de esta respuesta. En 2008, el código era bastante diferente.
Myrddin Emrys
27

Usted quiere:

  • Comandos incorporados de Bash (es decir, portátiles)
  • Comprobar TTY
  • Respuesta por defecto
  • Se acabó el tiempo
  • Pregunta coloreada

Retazo

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

Explicaciones

  • [[ -t 0 ]] && read ...=> Llamar comando readsi TTY
  • read -n 1 => Espera un personaje
  • $'\e[1;32m ... \e[0m '=> Imprimir en verde
    (el verde está bien porque se puede leer en ambos fondos blanco / negro)
  • [[ $do_xxxx =~ ^(y|Y|)$ ]] => bash regex

Tiempo de espera => La respuesta predeterminada es 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
olibre
fuente
26

La forma más fácil de lograr esto con el menor número de líneas es la siguiente:

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

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

El ifes solo un ejemplo: depende de usted cómo manejar esta variable.

Apurv Nerlekar
fuente
20

Usa el 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

y luego todas las otras cosas que necesitas

ThatLinuxGuy
fuente
17

Esta solución lee un solo carácter y llama a una función en una respuesta sí.

read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    do_something      
fi
Dennis
fuente
2
@Jav el eco imprime una nueva línea después de su respuesta. Sin él, lo siguiente que se imprimirá aparecería inmediatamente después de su respuesta en la misma línea. Intenta eliminar el echopara verlo por ti mismo.
Dennis
13

Para obtener un bonito cuadro de entrada similar a ncurses, use el cuadro de diálogo de comando como este:

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

El paquete de diálogo se instala por defecto al menos con SUSE Linux. Parece: el comando "diálogo" en acción

Thorsten Staerk
fuente
12
read -e -p "Enter your choice: " choice

La -eopción permite al usuario editar la entrada usando las teclas de flecha.

Si desea utilizar una sugerencia como entrada:

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

-i La opción imprime una entrada sugerente.

Jahid
fuente
-e -isí , no trabajes en sh (Bourne shell), pero la pregunta está etiquetada específicamente como bash ..
Jahid
12

Puede usar el valor predeterminado REPLYen a read, convertir a minúsculas y comparar con un conjunto de variables con una expresión.
El script también es compatible con 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
Walter A
fuente
12

Es posible manejar una "opción Sí / No" local en un shell POSIX; Al utilizar las entradas de la LC_MESSAGEScategoría de configuración regional, witch proporciona patrones RegEx listos para que coincidan con una entrada y cadenas para 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

EDITAR: Como @Urhixidur mencionó en su comentario :

Desafortunadamente, POSIX solo especifica los dos primeros (yesexpr y noexpr). En Ubuntu 16, yesstr y nostr están vacíos.

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

LC_MESSAGES

Las palabras clave yesstry nostrlocale y los elementos YESSTRy NOSTRlanginfo se usaban anteriormente para hacer coincidir las respuestas afirmativas y negativas del usuario. En POSIX.1-2008, la yesexpr, noexpr, YESEXPR, y NOEXPRexpresiones regulares extendidas su sustitución. Las aplicaciones deben usar las funciones de mensajería basadas en la localización general para emitir mensajes de solicitud que incluyen ejemplos de respuestas deseadas.

Alternativamente, usando configuraciones locales como 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 respuesta coincide con las expresiones regulares proporcionadas por el entorno local.

Para traducir los mensajes restantes, utilice bash --dump-po-strings scriptnamepara generar las cadenas de po para la localización:

#: 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 ""
Léa Gris
fuente
Me encanta la adición de una opción agnóstica de idiomas. Bien hecho.
Myrddin Emrys
1
Desafortunadamente, POSIX solo especifica los dos primeros (yesexpr y noexpr). En Ubuntu 16, yesstr y nostr están vacíos.
Urhixidur
1
¡Pero espera! ¡Hay peores noticias! Las expresiones de declaración de caso bash no son regulares, son expresiones de nombre de archivo. Entonces yesexpr y noexpr de Ubuntu 16 ("^ [aa]. *" Y "^ [nN]. *", Respectivamente) fallarán por completo debido al período incrustado. En una expresión regular, ". *" Significa "cualquier carácter que no sea de nueva línea, cero o más veces". Pero en una declaración de caso, es un literal "." seguido de cualquier número de caracteres.
Urhixidur
1
Finalmente, lo mejor que se puede hacer con yesexpry noexpren un entorno de shell es usarlo en la correspondencia RegEx específica de Bashif [[ "$yn" =~ $yesexpr ]]; then echo $"Answered yes"; else echo $"Answered no"; fi
Léa Gris
10

Solo presionar una tecla

Aquí hay un enfoque más largo, pero reutilizable y modular:

  • Devuelve 0= sí y 1= no
  • No es necesario presionar enter, solo un carácter
  • Puede presionar enterpara aceptar la opción predeterminada
  • Puede deshabilitar la opción predeterminada para forzar una selección
  • Funciona para ambos zshy bash.

Por defecto a "no" al presionar enter

Tenga en cuenta que el Nestá en mayúscula. Aquí se presiona enter, aceptando el valor predeterminado:

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

También tenga en cuenta que eso [y/N]?se agregó automáticamente. Se acepta el "no" predeterminado, por lo que no se repite nada.

Vuelva a preguntar hasta que se dé una respuesta válida:

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

Por defecto a "sí" al presionar enter

Tenga en cuenta que el Yestá en mayúscula:

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

Arriba, solo presioné enter, por lo que el comando se ejecutó.

Sin valor predeterminado activado enter: requiere 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

Aquí, 1o falso fue devuelto. Tenga en cuenta que con esta función de nivel inferior deberá proporcionar su propia[y/n]? aviso.

Código

# 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
}
Tom Hale
fuente
Cuando probé este script, en lugar del outut anterior obtuve, Show dangerous command [y/N]? [y/n]?yShow dangerous command [Y/n]? [y/n]?
Ilias Karim
Gracias @IliasKarim, lo arreglé justo ahora.
Tom Hale
9

Perdón por publicar en una publicación tan antigua. Hace algunas semanas me enfrentaba a un problema similar, en mi caso necesitaba una solución que también funcionara dentro de un script de instalación en línea, por ejemplo:curl -Ss https://raw.github.com/_____/installer.sh | bash

Usar read yesno < /dev/ttyfunciona bien para mí:

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

Espero que esto ayude a alguien.

usuario9869932
fuente
Una parte importante de esto es la validación de entrada. Creo que adaptar mi primer ejemplo para aceptar la ttyentrada como lo hiciste también lo habría hecho bien para ti, y también haber hecho un bucle en una entrada incorrecta (imagina algunos caracteres en el búfer; tu método obligaría al usuario a elegir siempre no).
Myrddin Emrys
7

Me di cuenta de que nadie publicó una respuesta que mostrara el menú de eco de varias líneas para una entrada de usuario tan simple, así que aquí está mi intento:

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

Este método fue publicado con la esperanza de que alguien pueda encontrarlo útil y que ahorre tiempo.

Yokai
fuente
4

Versión de opción múltiple:

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
}

Ejemplo:

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

Se establecerá REPLYen y(dentro del script).

Ernest A
fuente
4

Te sugiero que uses el diálogo ...

Aprendiz de Linux: Mejore los scripts de Bash Shell usando el diálogo

El comando de diálogo permite el uso de cuadros de ventana en scripts de shell para que su uso sea más interactivo.

es simple y fácil de usar, también hay una versión de gnome llamada gdialog que toma exactamente los mismos parámetros, pero muestra su estilo GUI en X.

Osama Al-Maadeed
fuente
Para el lector casual, busque un fragmento usando el comando de diálogo aquí: stackoverflow.com/a/22893526/363573
Stephan
4

Inspirado por las respuestas de @Mark y @Myrddin, creé esta función para un aviso universal

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
}

úsalo así:

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

más genérico sería:

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
}
Alexander Löfqvist
fuente
3

Una manera simple de hacer esto es con xargs -po gnuparallel --interactive .

Me gusta el comportamiento de xargs un poco mejor para esto porque ejecuta cada comando inmediatamente después de la solicitud como otros comandos interactivos de Unix, en lugar de recopilar los yesses para ejecutarlos al final. (Puede hacer Ctrl-C después de pasar por los que desea).

p.ej,

echo *.xml | xargs -p -n 1 -J {} mv {} backup/
Joshua Goldberg
fuente
No está mal, pero xargs --interactivese limita a sí o no. Mientras eso sea todo lo que necesita, puede ser suficiente, pero mi pregunta original dio un ejemplo con tres resultados posibles. Sin embargo, me gusta mucho que sea transmisible; Muchos escenarios comunes se beneficiarían de su capacidad de canalización.
Myrddin Emrys
Veo. Pensé que "cancelar" significaba simplemente detener toda ejecución adicional, lo que esto admite a través de Ctrl-C, pero si necesita hacer algo más complejo al cancelar (o en no), esto no será suficiente.
Joshua Goldberg
3

Como amigo de un comando de una línea, utilicé lo siguiente:

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

Forma larga escrita, funciona así:

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=;
ccDict
fuente
¿Puedes aclarar el uso de la variable prompt? Me parece que está borrado después del revestimiento único, entonces, ¿cómo se usa la línea para HACER algo?
Myrddin Emrys
La solicitud se borra después del ciclo while. Porque quiero que la variable de solicitud se inicialice después (ya que estoy usando la declaración con más frecuencia). Tener esta línea en un script de shell solo continuará si se escribe y | Y y saldrá si se escribe n | N o repita la solicitud de entrada para todo lo demás.
ccDict
3

He usado la casedeclaración un par de veces en tal escenario, usar el enunciado del caso es una buena manera de hacerlo. Se puede implementar un whilebucle, que encapsula el casebloque, que utiliza una condición booleana para mantener aún más control del programa y cumplir con muchos otros requisitos. Una vez que se hayan cumplido todas las condiciones, breakse puede utilizar un que devolverá el control a la parte principal del programa. Además, para cumplir otras condiciones, por supuesto, se pueden agregar declaraciones condicionales para acompañar las estructuras de control: casedeclaración y posible whilebucle.

Ejemplo de uso de una casedeclaración para cumplir con su solicitud

#! /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
oOpSgEo
fuente
3

Sí / no / cancelar

Función

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

Confirmar con entrada limpia del usuario

Función

#!/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
Eduardo Cuomo
fuente
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!'
jlettvin
fuente
Esto parece complejo y frágil. ¿Qué tal soloyn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
tripleee
2

En respuesta a otros:

No necesita especificar mayúsculas y minúsculas en BASH4, solo use '' para hacer una var en minúsculas. También me desagrada poner código dentro del bloque de lectura, obtener el resultado y tratarlo fuera del bloque de lectura IMO. También incluya una 'q' para salir de la OMI. Por último, ¿por qué escribir 'sí'? Solo use -n1 y presione y.

Ejemplo: el usuario puede presionar y / ny también q para salir.

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
Mike Q
fuente
0

La mayoría de las veces en tales escenarios, debe continuar ejecutando el script hasta que el usuario continúe ingresando "sí" y solo necesite detenerse cuando el usuario ingrese "no". ¡El siguiente fragmento lo ayudaría a lograr esto!

#!/bin/bash
input="yes"
while [ "$input" == "yes" ]
do
  echo "execute script functionality here..!!"
  echo "Do you want to continue (yes/no)?"
  read input
done
Azhar Ansari
fuente