comandos - ¿Cómo solicito la entrada Sí/No/Cancelar en un script de shell de Linux?
shell script (27)
Quiero pausar la entrada en un script de shell y pedirle al usuario opciones La pregunta estándar de tipo ''Sí, No o Cancelar''. ¿Cómo puedo lograr esto en un típico mensaje de bash?
Al menos cinco respuestas para una pregunta genérica.
Dependiendo de
- compatible con posix : podría funcionar en sistemas pobres con entornos de shell genéricos
- bash específico: utilizando los llamados bashismos
y si tu quieres
- Pregunta / respuesta simple `` en línea '''' (soluciones genéricas)
- interfaces bastante formateadas, como ncurses o más gráficas usando libgtk o libqt ...
- usar la capacidad de historial de readline
1. POSIX soluciones genéricas.
Podría usar el comando de read
, 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 quieres que el usuario tenga que presionar Retorno , puedes escribir:
( Editado: como lo sugiere acertadamente @JonathanLeffler, guardar la configuración de stty podría ser mejor que simplemente obligarlos a estar en su sano juicio).
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 sh , bash , ksh , dash y busybox !
Lo mismo, pero esperando explícitamente por y o 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 se libncurses
utilizando libncurses
, libgtk
, libgtk
u otras bibliotecas 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 reemplazar el whiptail
con 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 20
es la altura del cuadro de diálogo en número de líneas y 60
es el ancho 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. Bash soluciones específicas
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 el case
para poder incluso probar yes | ja | si | oui
yes | ja | si | oui
yes | ja | si | oui
si es necesario ...
en línea con la característica clave única
Bajo bash, podemos especificar la longitud de la entrada prevista para el comando de read
:
read -n 1 -p "Is this a good question (y/n)? " answer
Bajo bash, el comando de read
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
Algunos trucos para herramientas dedicadas.
Cuadros de diálogo más sofisticados, más allá del simple yes - no
propósitos:
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 a Uso de whiptail para elegir el dispositivo USB y el selector de almacenamiento extraíble USB: USBKeyChooser
5. Usando la historia 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.history
en su directorio $HOME
, de lo que podría usar los comandos de historial de readline, como Arriba , Abajo , Ctrl + r y otros.
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 de usuario limpia
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
Pulsar una sola tecla solamente
Aquí hay un enfoque más largo, pero reutilizable y modular:
- Devuelve
0
= sí y1
= no - No es necesario presionar Intro - solo un solo carácter
- Puede presionar enter para aceptar la opción por defecto
- Puede deshabilitar la opción predeterminada para forzar una selección
- Funciona tanto para
zsh
como parabash
.
Predeterminado a "no" al presionar enter
Tenga en cuenta que la N
está en mayúsculas. Aquí se pulsa enter, aceptando el predeterminado:
$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]?
También tenga en cuenta, que [y/N]?
Se adjuntó automáticamente. Se acepta el "no" predeterminado, por lo que no se repite nada.
Reintentar 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 *
Predeterminado a "sí" al presionar enter
Tenga en cuenta que la Y
está en mayúscula:
$ confirm_yes "Show dangerous command" && echo "rm *"
Show dangerous command [Y/n]?
rm *
Arriba, solo presioné Enter, entonces el comando corrió.
No hay valor predeterminado en enter - requiere y
o n
$ 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í, 1
o falso fue devuelto. Tenga en cuenta que con esta función de nivel inferior, deberá proporcionar su propio [y/n]?
rápido.
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
}
Usted quiere:
- Bash comandos integrados (es decir, portátiles)
- Compruebe TTY
- Respuesta predeterminada
- Se acabó el tiempo
- Pregunta de color
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 ...
=> El comando de llamadaread
si TTY -
read -n 1
=> espera un caracter -
$''/e[1;32m ... /e[0m ''
=> Imprimir en verde
(el verde está bien porque se puede leer en fondos blancos / negros) -
[[ $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
Aquí hay algo que pongo 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 tome esto con un grano de sal, pero parece funcionar.
Bash ha select para este propósito.
select result in Yes No Cancel
do
echo $result
done
Como amigo de un comando de una línea usé 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=;
Escrito en formato largo, 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=;
En respuesta a los demás:
No necesita especificar mayúsculas y minúsculas en BASH4, solo use '','' para hacer una variable en minúscula. Tampoco me gusta mucho colocar el código dentro del bloque de lectura, obtener el resultado y tratar con él fuera del bloque de lectura IMO. También incluya una ''q'' para salir de IMO. Por último, ¿por qué escribir ''sí'' solo usa -n1 y presiona y.
Ejemplo: el usuario puede presionar y / n y también q solo 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
Esta solución lee un solo carácter y llama a una función en una respuesta de sí.
read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
do_something
fi
He usado la declaración del case
un par de veces en ese escenario, usar la declaración del caso es una buena manera de hacerlo. Se puede implementar un bucle while, que ecapsula el bloque de case
, que utiliza una condición booleana para mantener aún más el control del programa y cumplir con muchos otros requisitos. Después de que se hayan cumplido todas las condiciones, se puede usar un break
que pasará el control de regreso a la parte principal del programa. Además, para cumplir con otras condiciones, por supuesto, se pueden agregar declaraciones condicionales para acompañar las estructuras de control: declaración de case
y posible bucle while.
Ejemplo de uso de una declaración de case
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
Inspirado por las respuestas de @Mark y @Myrddin, creé esta función para un mensaje 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
}
utilízalo así:
unipromtp "Select an option: (a)-Do one (x)->Do two (f)->Do three : " "a x f" selection
echo "$selection"
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 if
es solo un ejemplo: depende de usted cómo manejar esta variable.
Lo siento 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
curl -Ss https://raw.github.com/_____/installer.sh | bash
Usar read yesno < /dev/tty
funciona 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.
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
}
Noté que nadie publicó una respuesta que muestre el menú de eco de multilínea para una entrada tan sencilla por parte del usuario, así que aquí está mi oportunidad:
#!/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 se publicó con la esperanza de que a alguien le resulte útil y ahorre tiempo.
Para obtener un buen 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 de forma predeterminada al menos con SUSE Linux.
Puedes usar el comando de read incorporado; Use la opción -p
para preguntar al usuario con una pregunta.
Desde BASH4, ahora puede usar -i
para sugerir una respuesta, por lo que el usuario solo tiene que presionar return
para ingresar:
read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
echo $FILEPATH
(Pero recuerde usar la opción "readline" -e
para permitir la edición de líneas 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 ~/
Te sugiero que uses el diálogo ...
Linux Apprentice: Mejore los scripts de shell Bash usando el diálogo
El comando de diálogo permite el uso de cuadros de ventana en scripts de shell para hacer su uso 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 de GUI en X.
Una forma sencilla de hacer esto es con xargs -p
o gnu parallel --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 ejecutar al final. (Puedes presionar Ctrl-C una vez que superas las que querías).
p.ej,
echo *.xml | xargs -p -n 1 -J {} mv {} backup/
Utilice el comando de read
:
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
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
$
Establecerá REPLY
en y
(dentro del script).
El método más simple y más ampliamente disponible para obtener la entrada del usuario en un indicador de shell es el comando de read
. La mejor manera de ilustrar su uso es una simple demostración:
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 comando de select
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 select
no necesita sanear la entrada: muestra las opciones disponibles y escribe el número correspondiente a su elección. También se repite automáticamente, por lo que no es necesario que un ciclo while true
vuelva a intentarlo si dan una entrada no válida.
También, por favor revisa la excelente respuesta de F. Hauri.
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
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
La opción -e
permite al usuario editar la entrada con las teclas de flecha.
Si desea utilizar una sugerencia como entrada:
read -e -i "yes" -p "Enter your choice: " choice
-i
opción -i
imprime una entrada sugerente.
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
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!''