No Bash, como adicionar "Você tem certeza [Y / n]" a qualquer comando ou apelido?

228

Nesse caso em particular, gostaria de adicionar uma confirmação no Bash para

Você tem certeza? [S / n]

para o Mercurial hg push ssh://[email protected]//somepath/morepath, que na verdade é um apelido. Existe um comando padrão que pode ser adicionado ao alias para alcançá-lo?

A razão é que hg pushe hg outpode soar semelhante e, por vezes, quando eu quero hgoutrepo, eu posso accidentlly tipo hgpushrepo(ambos são aliases).

Atualização: se pode ser algo como um comando interno com outro comando, como: confirm && hg push ssh://...isso seria ótimo ... apenas um comando que pode pedir um yesou noe continuar com o restante se yes.

falta de polaridade
fonte
1
Você provavelmente desejará usar uma função em vez de um alias. From info bash: "Para quase todos os propósitos, as funções do shell são preferidas aos aliases."
Pausado até novo aviso.
realmente? mesmo para quem gosta ls -lou rm -i?
nonopolarity
5
Para quase todos, não todos . A propósito, nunca faça algo assim alias rm='rm -i'. Um dia, quando você mais precisar, o alias não estará lá e boom!algo importante será perdido.
Pausado até novo aviso.
espere, se você não tiver o alias para rm -i, também não poderá contar com um script de shell. Então você sempre digita sempre rm -i?
nonopolarity
8
É sobre hábitos . Você pode criar um alias como o do meu comentário anterior e ter o hábito de digitar rme esperar o -icomportamento; então, um dia o alias não estará lá (por algum motivo, ele fica desativado ou não está definido ou você está em um sistema diferente ) e você digita rme, em seguida, ele exclui imediatamente as coisas sem confirmação. Opa! No entanto, se você criou um alias como esse alias askrm='rm -i', estaria bem, pois receberia um erro "comando não encontrado".
Pausado até novo aviso.

Respostas:

332

Essas são formas mais compactas e versáteis da resposta de Hamish . Eles lidam com qualquer mistura de letras maiúsculas e minúsculas:

read -r -p "Are you sure? [y/N] " response
case "$response" in
    [yY][eE][sS]|[yY]) 
        do_something
        ;;
    *)
        do_something_else
        ;;
esac

Ou, para o Bash> = versão 3.2:

read -r -p "Are you sure? [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]
then
    do_something
else
    do_something_else
fi

Nota: Se $responsefor uma sequência vazia, ocorrerá um erro. Para corrigir, basta adicionar aspas: "$response". - Sempre usar aspas duplas em variáveis que contêm cadeias (por exemplo: preferem usar "$@"em vez $@).

Ou, Bash 4.x:

read -r -p "Are you sure? [y/N] " response
response=${response,,}    # tolower
if [[ "$response" =~ ^(yes|y)$ ]]
...

Editar:

Em resposta à sua edição, veja como você criaria e usaria um confirmcomando com base na primeira versão da minha resposta (funcionaria da mesma forma com as outras duas):

confirm() {
    # call with a prompt string or use a default
    read -r -p "${1:-Are you sure? [y/N]} " response
    case "$response" in
        [yY][eE][sS]|[yY]) 
            true
            ;;
        *)
            false
            ;;
    esac
}

Para usar esta função:

confirm && hg push ssh://..

ou

confirm "Would you really like to do a push?" && hg push ssh://..
Pausado até novo aviso.
fonte
9
Na verdade, deve ser [s / N] e não [S / n] para o teste atual.
Simon A. Eugster
Vale ressaltar que, se você quiser verificar not equal too segundo exemplo, deve converter a $responsevariável. Por exemplo: if [[ ! $response =~ ^([yY][eE][sS]|[yY])$ ]].
João Cunha
@ JoãoCunha: Isso seria "não corresponder" em vez de "não igual", já que =~é o operador de correspondência regex.
Pausado até novo aviso.
@DennisWilliamson correto, foi o que tentei dizer. Não é possível editar meu comentário por algum motivo.
João Cunha
6
Bom uso geral função sim / não . suporta um prompt forte e, opcionalmente, tornando 'y' ou 'n' padrão.
Wkw
19

Aqui está aproximadamente um trecho que você deseja. Deixe-me descobrir como encaminhar os argumentos.

read -p "Are you sure you want to continue? <y/N> " prompt
if [[ $prompt == "y" || $prompt == "Y" || $prompt == "yes" || $prompt == "Yes" ]]
then
  # http://stackoverflow.com/questions/1537673/how-do-i-forward-parameters-to-other-command-in-bash-script
else
  exit 0
fi

Cuidado com yes | command name here:)

Hamish Grubijan
fonte
13

As confirmações são facilmente ignoradas com retornos de carro, e acho útil solicitar continuamente a entrada válida.

Aqui está uma função para facilitar isso. "entrada inválida" aparece em vermelho se Y | N não for recebido e o usuário for solicitado novamente.

prompt_confirm() {
  while true; do
    read -r -n 1 -p "${1:-Continue?} [y/n]: " REPLY
    case $REPLY in
      [yY]) echo ; return 0 ;;
      [nN]) echo ; return 1 ;;
      *) printf " \033[31m %s \n\033[0m" "invalid input"
    esac 
  done  
}

# example usage
prompt_confirm "Overwrite File?" || exit 0

Você pode alterar o prompt padrão passando um argumento

Briceburg
fonte
12

Para evitar a verificação explícita dessas variantes de 'yes', você pode usar o operador de expressão regular do bash '= ~' com uma expressão regular:

read -p "Are you sure you want to continue? <y/N> " prompt
if [[ $prompt =~ [yY](es)* ]]
then
(etc...)

Isso testa se a entrada do usuário começa com 'y' ou 'Y' e é seguida por zero ou mais 'es'.

user389850
fonte
4

Adicione o seguinte ao seu arquivo / etc / bashrc. Este script adiciona uma "função" residente em vez de um alias chamado "confirm".


function confirm( )
{
#alert the user what they are about to do.
echo "About to $@....";
#confirm with the user
read -r -p "Are you sure? [Y/n]" response
case "$response" in
    [yY][eE][sS]|[yY]) 
              #if yes, then execute the passed parameters
               "$@"
               ;;
    *)
              #Otherwise exit...
              echo "ciao..."
              exit
              ;;
esac
}
MattyV
fonte
Agradável. Algumas correções menores, no entanto: você deve colocar aspas duplas $responsee, $@para evitar erros de interpretação, existem alguns pontos e vírgulas redundantes e a *condição deve retornar, e não sair.
Gordon Davisson
Só para esclarecer, são os pontos e vírgulas únicos no final de algumas declarações que são desnecessários.
Pausado até novo aviso.
4

Pode ser um pouco curto demais, mas para meu uso particular, ele funciona muito bem

read -n 1 -p "Push master upstream? [Y/n] " reply; 
if [ "$reply" != "" ]; then echo; fi
if [ "$reply" = "${reply#[Nn]}" ]; then
    git push upstream master
fi

O read -n 1apenas lê um personagem. Não há necessidade de pressionar enter. Se não for um 'n' ou 'N', presume-se que seja um 'Y'. Pressionar enter também significa Y.

(quanto à pergunta real: torne esse script bash e altere seu alias para apontar para esse script em vez do que estava apontando antes)

commonpike
fonte
Curto e doce, exatamente o que eu precisava. Obrigado!
Raymo111
3
read -r -p "Are you sure? [Y/n]" response
  response=${response,,} # tolower
  if [[ $response =~ ^(yes|y| ) ]] || [[ -z $response ]]; then
      your-action-here
  fi
SergioAraujo
fonte
Então, um caractere espacial significa sim?
Xen2050
Todos, exceto "sim ou y", confirmarão a ação, então você está certo! @ xen2050
SergioAraujo
3

Não é necessário pressionar pressionar

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 padrão "nã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"
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 não há letras maiúsculas em[y/n]?

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
2

Bem, aqui está a minha versão confirm, modificada da de James:

function confirm() {
  local response msg="${1:-Are you sure} (y/[n])? "; shift
  read -r $* -p "$msg" response || echo
  case "$response" in
  [yY][eE][sS]|[yY]) return 0 ;;
  *) return 1 ;;
  esac
}

Essas mudanças são:

  1. use localpara impedir que nomes de variáveis ​​colidam
  2. readuse $2 $3 ...para controlar sua ação, para que você possa usar -ne-t
  3. se readsair sem êxito, echoum feed de linha para beleza
  4. meu Git on Windowsúnico tem bash-3.1e não tem trueou false, então use-o return. Obviamente, isso também é compatível com o bash-4.4 (o atual no Git for Windows).
  5. use o estilo IPython "(y / [n])" para indicar claramente que "n" é o padrão.
Dahan Gong
fonte
2

Esta versão permite que você tenha mais de um caso you Y, nouN

  1. Opcionalmente: repita a pergunta até que uma pergunta de aprovação seja fornecida

  2. Opcionalmente: ignore qualquer outra resposta

  3. Opcionalmente: saia do terminal se desejar

    confirm() {
        echo -n "Continue? y or n? "
        read REPLY
        case $REPLY in
        [Yy]) echo 'yup y' ;; # you can change what you do here for instance
        [Nn]) break ;;        # exit case statement gracefully
        # Here are a few optional options to choose between
        # Any other answer:
    
        # 1. Repeat the question
        *) confirm ;;
    
        # 2. ignore
        # *) ;;
    
        # 3. Exit terminal
        # *) exit ;;
    
        esac
        # REPLY=''
    }

Observe também: Na última linha desta função, limpe a variável REPLY. Caso contrário, se você echo $REPLYver que ele ainda está definido até você abrir ou fechar o terminal ou configurá-lo novamente.

jasonleonhard
fonte
1

Tarde para o jogo, mas criei outra variante das confirmfunções das respostas anteriores:

confirm ()
{
    read -r -p "$(echo $@) ? [y/N] " YESNO

    if [ "$YESNO" != "y" ]; then
        echo >&2 "Aborting"
        exit 1
    fi

    CMD="$1"
    shift

    while [ -n "$1" ]; do
        echo -en "$1\0"
        shift
    done | xargs -0 "$CMD" || exit $?
}

Para usá-lo:

confirm your_command

Recursos:

  • imprime seu comando como parte do prompt
  • passa argumentos usando o delimitador NULL
  • preserva o estado de saída do seu comando

Insetos:

  • echo -enfunciona com bashmas pode falhar no seu shell
  • poderá falhar se os argumentos interferirem echoouxargs
  • um zilhão de outros bugs porque scripts de shell são difíceis
thomas.me
fonte
1

Experimentar,

 #!/bin/bash
 pause ()
 {
 REPLY=Y
 while [ "$REPLY" == "Y" ] || [ "$REPLY" != "y" ]
 do
  echo -e "\t\tPress 'y' to continue\t\t\tPress 'n' to quit"
  read -n1 -s
      case "$REPLY" in
      "n")  exit                      ;;
      "N")  echo "case sensitive!!"   ;; 
      "y")  clear                     ;;
      "Y")  echo "case sensitive!!"   ;;
      * )  echo "$REPLY is Invalid Option"     ;;
 esac
 done
 }
 pause
 echo "Hi"
Ranjithkumar T
fonte
0

Isso não é exatamente um "pedido de sim ou não", mas apenas um truque: aliás o hg push ...não, hgpushrepomas para hgpushrepoconfirmedpushe quando eu puder explicar tudo, o cérebro esquerdo fez uma escolha lógica.

falta de polaridade
fonte
1
Mas você sabe que irá usar a TABchave!
Hamish Grubijan
ok, é verdade, mas pelo menos eu vou olhar para o comando e ver o que é estranho e pensar duas vezes antes de bater Enter
nonopolarity
0

Não é o mesmo, mas a idéia que funciona de qualquer maneira.

#!/bin/bash  
i='y'  
while [ ${i:0:1} != n ]  
do  
    # Command(s)  
    read -p " Again? Y/n " i  
    [[ ${#i} -eq 0 ]] && i='y'  
done  

Saída:
Novamente? S / n N
novamente? S / N Alguma coisa de
novo? S / n 7 de
novo? S / N &
novamente? S / n nsijf
$

Agora apenas verifica o primeiro caractere de $ i read.

anônimo
fonte
0

O código abaixo está combinando duas coisas

  1. shopt -s nocasematch que cuidará de maiúsculas e minúsculas

  2. e se a condição que aceitar a entrada for aprovada, sim, Sim, SIM, y.

    shopt -s nocasematch

    se [[sed-4.2.2. $ LINE = ~ (sim | y) $]]

    então saia 0

    fi

Shyam Gupta
fonte
0

Aqui está a minha solução que usando regex localizado. Assim, também em alemão "j" para "Ja" seria interpretado como sim.

O primeiro argumento é a pergunta, se o segundo argumento for "y" que sim, seria a resposta padrão, caso contrário, não seria a resposta padrão. O valor de retorno é 0 se a resposta for "sim" e 1 se a resposta for "não".

function shure(){
    if [ $# -gt 1 ] && [[ "$2" =~ ^[yY]*$ ]] ; then
        arg="[Y/n]"
        reg=$(locale noexpr)
        default=(0 1)
    else
        arg="[y/N]"
        reg=$(locale yesexpr)
        default=(1 0)
    fi
    read -p "$1 ${arg}? : " answer
    [[ "$answer" =~ $reg ]] && return ${default[1]} || return ${default[0]}
}

Aqui está um uso básico

# basic example default is no
shure "question message" && echo "answer yes" || echo "answer no"
# print "question message [y/N]? : "

# basic example default set to yes
shure "question message" y && echo "answer yes" || echo "answer no"
# print "question message [Y/n]? : "
David Boho
fonte