Como solicito a entrada Sim / Não / Cancelar em um script de shell do Linux?

1444

Desejo pausar a entrada em um script de shell e solicitar opções ao usuário.
A pergunta padrão Yes, Noou Canceltipo.
Como faço isso em um prompt típico do bash?

Myrddin Emrys
fonte
11
Apenas como uma observação: a convenção para prompts é tal que, se você apresentar uma [yn]opção, a que está em maiúscula é o padrão, ou seja, o padrão é [Yn]"yes" e o [yN]"no". Veja ux.stackexchange.com/a/40445/43532
Tyzoid
Qualquer um que venha do ZSH, veja esta resposta para saber como usar o readcomando para prompt
smac89

Respostas:

1619

O método mais simples e amplamente disponível para obter entrada do usuário em um prompt do shell é o readcomando A melhor maneira de ilustrar seu uso é uma demonstração simples:

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

Outro método, apontado por Steven Huwig , é o selectcomando de Bash . Aqui está o mesmo exemplo 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

Com selectvocê, não é necessário limpar a entrada - ela exibe as opções disponíveis e você digita um número correspondente à sua escolha. Também faz loops automaticamente, portanto, não há necessidade de um while trueloop tentar novamente se eles fornecerem entradas inválidas.

Além disso, Léa Gris demonstrou uma maneira de tornar a linguagem de solicitação agnóstica em sua resposta . A adaptação do meu primeiro exemplo para melhor atender a vários idiomas pode ser assim:

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, outras strings de comunicação permanecem sem tradução aqui (Instalação, Resposta), que precisariam ser tratadas em uma tradução mais completa, mas mesmo uma tradução parcial seria útil em muitos casos.

Por fim, confira a excelente resposta de F. Hauri .

Myrddin Emrys
fonte
31
Usando Bash no OS X Leopard, eu mudei exitpara breakmanter-se de fechar a guia quando eu selecionei 'não'.
Trey Piepmeier 02/12/2009
1
Como isso funciona com opções maiores que Sim ou Não? Na cláusula case, você escreve algo como: Instale o programa e não faça nada depois) make install; quebrar;
Shawn
1
@ Shawn Com a leitura, você pode, é claro, usar qualquer padrão glob ou regex suportado pela instrução switch do bash shell. Apenas corresponde ao texto digitado nos seus padrões até encontrar uma correspondência. Usando select , a lista de opções é fornecida ao comando e as exibe ao usuário. Você pode fazer com que os itens da lista sejam longos ou curtos, conforme desejar. Eu recomendo verificar suas páginas de manual para obter informações abrangentes de uso.
Myrddin Emrys
3
por que existe um breakno selectse não houver loop?
Jayen
1
@akostadinov Eu realmente deveria ler mais meu histórico de edições. Você está correto, eu estou errado, e apresentei um bug seguindo o conselho de Jayan, hah. O bug foi corrigido mais tarde, mas as edições estavam tão distantes que nem me lembro de alterá-lo.
Myrddin Emrys #
527

Pelo menos cinco respostas para uma pergunta genérica.

Dependendo

  • compatível: poderia funcionar em sistemas ruins com genéricos ambientes
  • específico: usando os chamados basismos

e se você quiser

  • pergunta / resposta `` alinhada '' simples (soluções genéricas)
  • interfaces bastante formatadas, como ou mais gráfico usando libgtk ou libqt ...
  • use o poderoso recurso de histórico de linha de leitura

1. Soluções genéricas POSIX

Você pode usar o readcomando, seguido por 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

(Obrigado ao comentário de Adam Katz : Substituído o teste acima por um que seja mais portátil e evite um garfo :)

POSIX, mas recurso de chave única

Mas se você não quiser que o usuário acerte Return, escreva:

( Editado: como o @JonathanLeffler sugere, salvar a configuração do stty pode ser melhor do que simplesmente forçá-lo a sane .)

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: Isso foi testado em, , , e !

Mesmo, mas esperando explicitamente por you 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 ferramentas dedicadas

Existem muitas ferramentas que foram construídos usando libncurses, libgtk, libqtou outras bibliotecas gráficas. Por exemplo, usando whiptail:

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

Dependendo do seu sistema, pode ser necessário substituí-lo whiptailpor outra ferramenta 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

onde 20é a altura da caixa de diálogo em número de linhas e a 60largura da caixa de diálogo. Todas essas ferramentas têm quase a mesma sintaxe.

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

2. Soluções específicas do Bash

Método básico em linha

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

Eu prefiro usar casepara testar yes | ja | si | ouise necessário ...

de acordo com o recurso de tecla única

Sob bash, podemos especificar o comprimento da entrada pretendida para o readcomando:

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

Em bash, o readcomando aceita um parâmetro de tempo limite , que pode 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. Alguns truques para ferramentas dedicadas

Caixas de diálogo mais sofisticadas, além de yes - nopropósitos simples :

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

Barra de progresso:

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
) 

Pequena demonstração:

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

Mais amostra? Veja Usando o chicote para escolher o dispositivo USB e o seletor de armazenamento removível USB: USBKeyChooser

5. Usando o histórico da readline

Exemplo:

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

Isto irá criar um arquivo .myscript.historyem seu $HOMEdiretório, que você pode usar comandos de história do readline, como Up, Down, Ctrl+ routros e.

F. Hauri
fonte
4
Note-se que sttyfornece a -gopção de uso: old_stty=$(stty -g); stty raw -echo; …; stty "$old_stty". Isso restaura a configuração exatamente como foi encontrada, que pode ou não ser a mesma que stty -sane.
Jonathan Leffler
3
read answerinterpretará as barras invertidas antes dos espaços e das linhas e, de outra forma, as removerá, o que raramente é planejado. Use em read -r answervez disso, conforme SC2162 .
vlfig 31/03
1
O método "Usando o histórico da readline" é tão inapropriado para a pergunta do OP. Estou feliz que você tenha incluído de qualquer maneira. Eu tenho dezenas de scripts muito mais complicados que pretendo atualizar com esse padrão!
Bruno Brosky # 19/17
5
Você pode usar o casePOSIX e o bash (use uma condição curinga em vez de uma substring do bash:) case $answer in; [Yy]* ) echo Yes ;;, mas prefiro usar uma instrução condicional, favorecendo [ "$answer" != "${answer#[Yy]}" ]a sua echo "$answer" | grep -iq ^y. É mais portátil (alguns greps não-GNU não implementam -qcorretamente) e não possui a chamada de sistema. ${answer#[Yy]}usa expansão de parâmetro para remover You ydo início de $answer, causando uma desigualdade quando uma delas está presente. Isso funciona em qualquer shell POSIX (traço, ksh, bash, zsh, busybox, etc).
23418 Adam
1
@CarterPape Sim, isso foi uma piada! Mas nesta resposta detalhada, você pode encontrar muitas dicas (o segundo ponto apresenta pelo menos três métodos diferentes)! E ... há aproximadamente 5 anos, você é o primeiro a contar sobre o meu método de contagem! ;-))
F. Hauri
349
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
Pistos
fonte
25
Não concordo, porque ele implementa apenas uma parte da funcionalidade da caixa de diálogo 'Sim, Não, Cancelar' no DOS. A parte que ele falha na implementação é a verificação de entrada ... repetindo até que uma resposta válida seja recebida.
Myrddin Emrys #
1
(O título da pergunta original era "Como eu pronta para a entrada em um script shell Linux?")
Pistos
8
Mas a descrição original da pergunta permanece inalterada e sempre solicita uma resposta para um prompt Sim / Não / Cancelar. O título foi atualizado para ser mais claro que o original, mas a descrição da pergunta sempre foi clara (na minha opinião).
Myrddin Emrys
165

Você pode usar o comando de leitura interno; Use a -popção para solicitar ao usuário uma pergunta.

Desde o BASH4, agora você pode usar -ipara sugerir uma resposta:

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

(Mas lembre-se de usar a opção "readline" -epara permitir a edição de linha com as teclas de seta)

Se você deseja uma lógica "sim / não", pode fazer algo assim:

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

[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/
yPhil
fonte
6
Note-se que FILEPATHé o nome da variável que você escolheu e é definido com a resposta no prompt de comando. Portanto, se você executasse vlc "$FILEPATH", por exemplo, vlcabriria esse arquivo.
Ken Sharp
Qual é o benefício do -esegundo exemplo (simples sim / não)?
JBallin
Alguma razão para usar em -e -pvez de -ep?
JBallin
1
Sem o -esinalizador / opção, você pode (dependendo da implementação) não conseguir digitar "y" e depois mudar de idéia e substituí-lo por um "n" (ou qualquer outra coisa a esse respeito); Ao documentar um comando, listar as opções separadamente é melhor para legibilidade / clareza, entre outros motivos.
yPhil
109

O Bash selecionou para esse fim.

select result in Yes No Cancel
do
    echo $result
done
Steven Huwig
fonte
18
+1 Solução genialmente simples. A única coisa: Isto irá pedir e promt e pronta ... até que você adicione um exitinterior :)
kaiser
5
(kaiser: Para quebrar a partir dele, basta digitar o EOT: Ctrl-DMas é claro, o código real usando ele vai precisar de uma pausa ou uma saída no corpo..)
Zorawar
12
Isso não vai permitir que você digite y ou n, though.You escolher inserindo 1 2 ou 3.
djjeck
3
exitvai sair do script todos juntos, breaksó vai sair do loop você está no (se você estiver em um whileou casecircular)
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
serg
fonte
36

Aqui está uma coisa que eu montei:

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

Sou iniciante, então leve isso com um pouco de sal, mas parece funcionar.

mpen
fonte
9
Se você mudar case $yn inpara case ${yn:-$2} in, em seguida, você pode usar o segundo argumento como o valor padrão, Y ou N.
jchook
1
ou mude case $ynpara case "${yn:-Y}"ter sim como padrão
rubo77 7/04
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
fonte
1
Coloque quatro espaços no início de cada linha para preservar a formatação do código.
Jouni K. Seppänen
10
Por que fornecemos 'y' e 'n' como parâmetros para indagar () se as opções de maiúsculas e minúsculas são codificadas? Isso é apenas pedir mau uso. Como parâmetros fixos, não são alteráveis, o eco na linha 2 deve ler: echo -n "$ 1 [S / N]?" Eles não podem ser alterados, portanto não devem ser fornecidos.
Myrddin Emrys
1
@MyrddinEmrys Você poderia, por favor, elaborar seu comentário? Ou poste um link para um artigo ou algumas palavras-chave para que eu possa fazer pesquisas por conta própria.
Mateusz Piotrowski 26/03
4
@MateuszPiotrowski A resposta foi editada e melhorada desde que fiz o meu comentário. Você pode clicar no link "editado em 23 de dezembro" acima para visualizar todas as versões anteriores desta resposta. Em 2008, o código era bem diferente.
Myrddin Emrys 27/03
27

Você quer:

  • Comandos internos do Bash (ou seja, portáteis)
  • Verifique TTY
  • Resposta padrão
  • Tempo esgotado
  • Pergunta colorida

Snippet

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

Explicações

  • [[ -t 0 ]] && read ...=> Chamar comando readse TTY
  • read -n 1 => Aguarde um caractere
  • $'\e[1;32m ... \e[0m '=> Imprimir em verde
    (o verde é bom porque legível nos fundos branco / preto)
  • [[ $do_xxxx =~ ^(y|Y|)$ ]] => bash regex

Tempo limite => A resposta padrão é Não

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
fonte
26

A maneira mais fácil de conseguir isso com o menor número de linhas é a seguinte:

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

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

Este ifé apenas um exemplo: você decide como lidar com essa variável.

Apurv Nerlekar
fonte
20

Use o 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 então todas as outras coisas que você precisa

ThatLinuxGuy
fonte
17

Esta solução lê um único caractere e chama uma função em uma resposta sim.

read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    do_something      
fi
Dennis
fonte
2
@Jav o eco imprime uma nova linha após sua resposta. Sem ele, a próxima coisa a ser impressa aparecerá imediatamente após a sua resposta na mesma linha. Tente remover o echopara ver por si mesmo.
Dennis
13

Para obter uma boa caixa de entrada semelhante a ncurses, use a caixa de diálogo de comando assim:

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

O pacote de diálogo é instalado por padrão pelo menos com o SUSE Linux. Parece: o comando "dialog" em ação

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

A -eopção permite que o usuário edite a entrada usando as teclas de seta.

Se você deseja usar uma sugestão como entrada:

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

-i A opção imprime uma entrada sugestiva.

Jahid
fonte
yap, -e -inão funcionam no sh (Bourne shell), mas a questão é o bash marcado especificamente ..
Jahid
12

Você pode usar o padrão REPLYem a read, converter em minúscula e comparar com um conjunto de variáveis ​​com uma expressão.
O script também suporta 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
fonte
12

É possível manipular uma escolha "Sim / Não" com reconhecimento de localidade em um shell POSIX; usando as entradas da LC_MESSAGEScategoria localidade, o witch fornece padrões RegEx prontos para corresponder a uma entrada e cadeias de caracteres localizadas Sim Não.

#!/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: Como @Urhixidur mencionado em seu comentário :

Infelizmente, o POSIX especifica apenas os dois primeiros (yesexpr e noexpr). No Ubuntu 16, yesstr e nostr estão vazios.

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

LC_MESSAGES

As palavras-chave yesstre nostrlocale e os itens YESSTRe NOSTRlanginfo foram usados ​​anteriormente para corresponder às respostas afirmativas e negativas do usuário. Em POSIX.1-2008, o yesexpr, noexpr, YESEXPR, e NOEXPRexpressões regulares estendidas substituíram-los. Os aplicativos devem usar os recursos gerais de mensagens com base no código do idioma para emitir mensagens de prompt que incluem respostas desejadas de amostra.

Como alternativa, use localidades da maneira 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

A resposta é correspondida usando os regexps fornecidos pelo ambiente de localidade.

Para converter as mensagens restantes, use bash --dump-po-strings scriptnamepara gerar as seqüências de caracteres po para localização:

#: 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
fonte
Adoro a adição de uma opção independente de idioma. Bem feito.
Myrddin Emrys
1
Infelizmente, o POSIX especifica apenas os dois primeiros (yesexpr e noexpr). No Ubuntu 16, yesstr e nostr estão vazios.
Urhixidur 9/01
1
Mas espere! Há notícias piores! As expressões da instrução bash case não são regulares, são expressões de nome de arquivo. Portanto, yesexpr e noexpr do Ubuntu 16 ("^ [yY]. *" E "^ [nN]. *", Respectivamente) falharão totalmente por causa do período incorporado. Em uma expressão regular, ". *" Significa "qualquer caractere que não seja de nova linha, zero ou mais vezes". Mas em uma declaração de caso, é um literal "." seguido por qualquer número de caracteres.
Urhixidur 9/01
1
Finalmente, o melhor que pode ser feito com yesexpre noexprem um ambiente shell é usá-lo na correspondência RegEx específica do Bashif [[ "$yn" =~ $yesexpr ]]; then echo $"Answered yes"; else echo $"Answered no"; fi
Léa Gris
10

Somente pressionamento de tecla único

Aqui está uma abordagem mais longa, mas reutilizável e modular:

  • Retorna 0= sim e 1= não
  • Não é necessário pressionar pressionar - apenas um único caractere
  • Pode pressionar enterpara aceitar a opção padrão
  • Pode desativar a opção padrão para forçar uma seleção
  • Funciona para ambos zshe bash.

O padrão é "não" ao pressionar enter

Observe que o Ncapital é maiúsculo. Aqui, enter é pressionado, aceitando o padrão:

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

Observe também que [y/N]?foi anexado automaticamente. O "não" padrão é aceito, então nada é ecoado.

Solicite novamente até que uma resposta válida seja fornecida:

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

O padrão é "yes" ao pressionar enter

Observe que o Yé maiúsculo:

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

Acima, eu apenas pressionei enter, então o comando foi executado.

Nenhum padrão ativado enter- requer youn

$ 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

Aqui, 1ou false foi retornado. Observe que, com esta função de nível inferior, você precisará fornecer seu próprio [y/n]?prompt.

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
fonte
Quando eu testei esse script, em vez do outut acima que eu tenho, Show dangerous command [y/N]? [y/n]?eShow dangerous command [Y/n]? [y/n]?
Ilias Karim
Obrigado @IliasKarim, consertei isso agora.
Tom Hale
9

Desculpe por postar em um post tão antigo. Algumas semanas atrás, eu estava enfrentando um problema semelhante, no meu caso, precisava de uma solução que também funcionasse dentro de um script de instalação online, por exemplo:curl -Ss https://raw.github.com/_____/installer.sh | bash

Usar read yesno < /dev/ttyfunciona bem para mim:

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 isso ajude alguém.

user9869932
fonte
Uma parte importante disso é a validação de entrada. Eu acho que adaptar meu primeiro exemplo para aceitar ttyentradas como você teria feito também para você, e também ter recebido loop de entrada ruim (imagine alguns caracteres no buffer; seu método forçaria o usuário a sempre escolher não).
Myrddin Emrys
7

Notei que ninguém postou uma resposta mostrando o menu de eco de várias linhas para uma entrada tão simples do usuário, então aqui está a minha tentativa:

#!/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 foi publicado na esperança de que alguém possa achar útil e que economiza tempo.

Yokai
fonte
4

Versão de múltipla escolha:

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
}

Exemplo:

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

Ele será definido REPLYcomo y(dentro do script).

Ernest A
fonte
4

Eu sugiro que você use o diálogo ...

Aprendiz do Linux: melhore os scripts do Bash Shell usando o diálogo

O comando dialog permite o uso de caixas de janela em shell scripts para tornar seu uso mais interativo.

é simples e fácil de usar, há também uma versão do gnome chamada gdialog que usa exatamente os mesmos parâmetros, mas mostra o estilo da GUI no X.

Osama Al-Maadeed
fonte
Para o leitor casual, consulte um trecho usando o comando de diálogo aqui: stackoverflow.com/a/22893526/363573
Stephan
4

Inspirado pelas respostas de @Mark e @Myrddin, criei esta função para um prompt 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
}

use-o assim:

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

mais genérico seria:

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
fonte
3

Uma maneira simples de fazer isso é com xargs -pou gnu parallel --interactive.

Eu gosto do comportamento do xargs um pouco melhor para isso, porque ele executa cada comando imediatamente após o prompt, como outros comandos unix interativos, em vez de coletar os yesses para executar no final. (Você pode pressionar Ctrl-C depois de passar pelos que deseja.)

por exemplo,

echo *.xml | xargs -p -n 1 -J {} mv {} backup/
Joshua Goldberg
fonte
Não é ruim, mas xargs --interactiveé limitado a sim ou não. Contanto que seja tudo o que você precisa, isso pode ser suficiente, mas minha pergunta original deu um exemplo com três resultados possíveis. Eu realmente gosto que seja fácil de usar; muitos cenários comuns se beneficiariam de sua capacidade de ser canalizado.
Myrddin Emrys
Eu vejo. Meu pensamento era que "cancel" significava simplesmente interromper toda a execução adicional, compatível com Ctrl-C, mas se você precisar fazer algo mais complexo no cancelamento (ou não), isso não será suficiente.
21415 Joshua Goldberg #
3

Como amigo de um comando de uma linha, usei o seguinte:

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 escrito, funciona assim:

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
fonte
Você pode esclarecer o uso da variável prompt? Parece-me que ele foi apagado após o revestimento, então como você usa a linha para fazer alguma coisa?
Myrddin Emrys
O prompt é apagado após o loop while. Porque eu quero que a variável prompt seja inicializada posteriormente (já que estou usando a instrução com mais frequência). Ter essa linha em um shell-script só continuará se y | Y for digitado e sairá se n | N for digitado ou repita, solicitando entrada para todo o resto.
ccDict
3

Eu usei a casedeclaração algumas vezes nesse cenário, usar a declaração de caso é uma boa maneira de fazer isso. Um whileloop, que ecapsula o casebloco, que utiliza uma condição booleana, pode ser implementado para manter ainda mais controle do programa e atender a muitos outros requisitos. Após todas as condições terem sido atendidas, breakpode ser usado um que passará o controle de volta para a parte principal do programa. Além disso, para atender a outras condições, é claro que instruções condicionais podem ser adicionadas para acompanhar as estruturas de controle: caseinstrução e whileloop possível .

Exemplo de uso de uma casedeclaração para atender sua solicitação

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

Sim / Não / Cancelar

Função

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

Confirme com entrada limpa do usuário

Função

#!/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
fonte
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
fonte
Isso parece complexo e frágil. Que tal apenasyn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
triplee
2

Em resposta a outros:

Você não precisa especificar maiúsculas e minúsculas no BASH4, basta usar o ',,' para fazer uma var minúscula. Também não gosto de colocar código dentro do bloco de leitura, obter o resultado e lidar com ele fora do bloco de leitura IMO. Inclua também um 'q' para sair do IMO. Por fim, por que digite 'yes' basta usar -n1 e pressionar y.

Exemplo: o usuário pode pressionar y / ne também q para sair.

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
fonte
0

Na maioria das vezes, nesses cenários, você precisa continuar executando o script até que o usuário continue digitando "yes" e só precise parar quando o usuário digitar "no". O trecho abaixo ajudaria você a conseguir isso!

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