linux - instalar - shell script install
Como faço para solicitar a entrada Sim/Não/Cancelar em um script de shell do Linux? (19)
Eu quero pausar a entrada em um script de shell e solicitar ao usuário para escolhas. A pergunta do tipo padrão "Sim, Não ou Cancelar". Como faço isso em um típico prompt bash?
Pelo menos cinco respostas para uma pergunta genérica.
Dependendo
- posix compliant: poderia funcionar em sistemas pobres com ambientes shell genéricos
- bash specific: usando os chamados bashisms
e se você quiser
- pergunta / resposta simples "em linha" (soluções genéricas)
- interfaces bem formatadas, como ncurses ou mais gráficas usando libgtk ou libqt ...
- usar capacidade poderosa de histórico readline
1. Soluções genéricas POSIX
Você poderia usar o comando read
, 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
(Graças ao comentário de Adam Katz : Substitui o teste acima por um que é mais portátil e evita um fork :)
POSIX, mas único recurso chave
Mas se você não quer que o usuário tenha que pressionar Return , você pode escrever:
( Editado: Como @JonathanLeffler sugere, salvar a configuração do stty pode ser melhor do que simplesmente forçá-los a serem
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: Isto foi testado sob sh , bash , ksh , dash e busybox !
O mesmo, mas esperando explicitamente por y ou 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ídas usando libgtk
, libgtk
, libqt
ou 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, você pode precisar substituir o whiptail
por outra ferramenta semelhante:
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 no número de linhas e 60
é a largura 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 de 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 o case
para que eu possa testar yes | ja | si | oui
yes | ja | si | oui
yes | ja | si | oui
se necessário ...
alinhado com o recurso chave único
Sob bash, podemos especificar o comprimento da entrada pretendida para o comando read
:
read -n 1 -p "Is this a good question (y/n)? " answer
Sob bash, o comando read
aceita um parâmetro de timeout , 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
Alguns truques para ferramentas dedicadas
Caixas de diálogo mais sofisticadas, além do simples yes - no
propósitos:
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? Dê uma olhada em Usando o whiptail para escolher o dispositivo USB e o seletor de armazenamento USB removível: 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
Isso criará um arquivo .myscript.history
em seu diretório $HOME
, do que você poderia usar os comandos de histórico readline, como Up , Down , Ctrl + r e outros.
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 a entrada do usuário limpa
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
Você quer:
- Comandos Bash embutidos (ou seja, portáteis)
- Verifique o 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 ...
=> Comando de chamadaread
se TTY -
read -n 1
=> Aguarde um caractere -
$'\e[1;32m ... \e[0m '
=> Imprimir em verde
(verde é bom porque legível em ambos os fundos branco / preto) -
[[ $do_xxxx =~ ^(y|Y|)$ ]]
=> regex do bash
Tempo limite => 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
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
O if
é apenas um exemplo: cabe a você como lidar com essa variável.
Bash select para este propósito.
select result in Yes No Cancel
do
echo $result
done
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=;
Em resposta a outros:
Você não precisa especificar o caso no BASH4 apenas use o ',,' para fazer uma letra minúscula var. Também não gosto de colocar código dentro do bloco de leitura, obter o resultado e lidar com ele fora do IMO do bloco de leitura. Inclua também um 'q' para sair do IMO. Por último, por que digitar "sim" é só usar -n1 e pressionar y.
Exemplo: o usuário pode pressionar y / n e também q para apenas 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
Essa solução lê um único caractere e chama uma função em uma resposta yes.
read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
do_something
fi
Eu usei a declaração de case
um par de vezes em tal cenário, usando a declaração do caso é uma boa maneira de fazê-lo. Um loop while, que ecapsula o bloco de case
, que utiliza uma condição booleana, pode ser implementado para manter ainda mais o controle do programa e atender a muitos outros requisitos. Depois que todas as condições tiverem sido atendidas, uma break
pode ser usada, o 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: instrução case
e loop while
possível.
Exemplo de uso de uma declaração de case
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
Inspirado pelas respostas de @Mark e @Myrddin eu 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"
Para obter uma boa caixa de entrada do tipo ncurses use a caixa de diálogo de comando como esta:
#!/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.
Uma maneira simples de fazer isso é com xargs -p
ou gnu parallel --interactive
.
Eu gosto do comportamento de 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 Ctrl-C depois de passar pelos que você queria.)
por exemplo,
echo *.xml | xargs -p -n 1 -J {} mv {} backup/
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 irá definir REPLY
para y
(dentro do script).
Você pode usar o comando de read embutido; Use a opção -p
para solicitar ao usuário uma pergunta.
Desde BASH4, agora você pode usar -i
para sugerir uma resposta, então o usuário só precisa pressionar Enter para entrar:
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" -e
para permitir edição de linha com teclas de seta)
Se você quer uma lógica "sim / não", você pode fazer algo assim:
read -e -p "
List the content of your home dir ? [Y/n] " YN
[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/
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
}
O método mais simples e mais amplamente disponível para obter entrada do usuário em um prompt do shell é o comando de read
. 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 comando select
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 a select
você não precisa limpar a entrada - ela exibe as opções disponíveis e você digita um número correspondente à sua escolha. Ele também faz um loop automaticamente, portanto, não há necessidade de um loop while true
para tentar novamente se eles fornecerem uma entrada inválida.
Além disso, confira a excelente resposta de F. Hauri.
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
A opção -e
permite que o usuário edite a entrada usando as teclas de seta.
Se você quiser usar uma sugestão como entrada:
read -e -i "yes" -p "Enter your choice: " choice
-i
opção imprime uma entrada sugestiva.
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!'