Как мне запросить Да / Нет / Отменить ввод в сценарии оболочки Linux?
Я хочу приостановить ввод в сценарии оболочки и предложить пользователю выбор. Стандартный вопрос типа "Да, Нет или Отмена". Как мне сделать это в типичной командной строке?
40 ответов
Самый простой и широко доступный способ получения пользовательского ввода в командной строке - это read
команда. Лучший способ проиллюстрировать его использование - это простая демонстрация:
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
Еще один метод, на который указал Стивен Хьюиг, это метод Баша. select
команда. Вот тот же пример использования 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
С select
Вам не нужно очищать ввод - он отображает доступные варианты, и вы вводите число, соответствующее вашему выбору. Он также зацикливается автоматически, поэтому нет необходимости while true
цикл, чтобы повторить, если они дают неверный ввод.
Также, пожалуйста, ознакомьтесь с отличным ответом Ф. Хаури.
Как минимум пять ответов на один общий вопрос.
В зависимости от
- совместимость с posix: может работать на слабых системах с универсальной оболочкой
- специфичные для bash: использование так называемых bashisms
и если вы хотите
- простой ``in line'' вопрос / ответ (общие решения)
- довольно отформатированные интерфейсы, такие как ncurses или более графические, используя libgtk или libqt...
- использовать мощные возможности чтения истории
1. Общие решения POSIX
Вы могли бы использовать read
команда, а затем 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
(Благодаря комментарию Адама Каца: заменил тест, приведенный выше, на более переносимый и не использующий одну форк:)
POSIX, но функция одного ключа
Но если вы не хотите, чтобы пользователь нажимал Return, вы можете написать:
(Отредактировано: как справедливо предположил @JonathanLeffler, сохранение конфигурации stty может быть лучше, чем просто принудительное их исправление.)
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
Примечание: это было проверено в sh, bash, ksh, dash и busybox!
То же самое, но явно ожидая y или 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
Использование специальных инструментов
Есть много инструментов, которые были построены с использованием libncurses
, libgtk
, libqt
или другие графические библиотеки. Например, используя whiptail
:
if whiptail --yesno "Is this a good question" 20 60 ;then
echo Yes
else
echo No
fi
В зависимости от вашей системы вам может потребоваться заменить whiptail
с другим похожим инструментом:
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
где 20
высота диалогового окна в количестве строк и 60
ширина диалогового окна. Эти инструменты имеют почти одинаковый синтаксис.
DIALOG=whiptail
if [ -x /usr/bin/gdialog ] ;then DIALOG=gdialog ; fi
if [ -x /usr/bin/xdialog ] ;then DIALOG=xdialog ; fi
...
$DIALOG --yesno ...
2. Bash конкретные решения
Основной в линии метод
read -p "Is this a good question (y/n)? " answer
case ${answer:0:1} in
y|Y )
echo Yes
;;
* )
echo No
;;
esac
Я предпочитаю использовать case
чтобы я мог даже проверить на yes | ja | si | oui
если нужно...
в соответствии с одной ключевой функцией
В bash мы можем указать длину предполагаемого ввода для read
команда:
read -n 1 -p "Is this a good question (y/n)? " answer
Под баш, read
Команда принимает параметр времени ожидания, который может быть полезен.
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
Некоторые хитрости для специальных инструментов
Более сложные диалоговые окна, помимо простых yes - no
цели:
dialog --menu "Is this a good question" 20 60 12 y Yes n No m Maybe
Индикатор:
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
)
Маленькая демка:
#!/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
Больше образца? Посмотрите Использование Whiptail для выбора USB-устройства и USB-накопителя: USBKeyChooser
5. Использование истории readline
Пример:
#!/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
Это создаст файл .myscript.history
в вашем $HOME
каталог, чем вы могли бы использовать команды истории readline, как вверх, вниз, Ctrl + r и другие.
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
Вы можете использовать встроенную команду чтения; Использовать -p
возможность подсказать пользователю вопрос.
Начиная с BASH4, теперь вы можете использовать -i
предложить ответ, так что пользователь должен только нажать return
ввести его:
read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
echo $FILEPATH
(Но не забудьте использовать опцию "readline" -e
разрешить редактирование строки клавишами со стрелками)
Если вам нужна логика "да / нет", вы можете сделать что-то вроде этого:
read -e -p "
List the content of your home dir ? [Y/n] " YN
[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/
Bash выбрал для этой цели.
select result in Yes No Cancel
do
echo $result
done
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
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?"
...
Вот что я собрал:
#!/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
Я новичок, так что возьмите это с крошкой соли, но, похоже, это сработает.
Ты хочешь:
- Встроенные команды Bash (т.е. переносимые)
- Проверьте TTY
- Ответ по умолчанию
- Тайм-аут
- Цветной вопрос
отрывок
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
Пояснения
[[ -t 0 ]] && read ...
=> Команда вызоваread
если TTYread -n 1
=> Ждать одного символа$'\e[1;32m ... \e[0m '
=> Печать зеленым цветом
(зеленый хорошо, потому что читается как на белом, так и на черном фоне)[[ $do_xxxx =~ ^(y|Y|)$ ]]
=> Bash регулярное выражение
Тайм-аут => Ответ по умолчанию - Нет
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
Самый простой способ добиться этого с наименьшим количеством строк заключается в следующем:
read -p "<Your Friendly Message here> : y/n/cancel" CONDITION;
if [ "$CONDITION" == "y" ]; then
# do something here!
fi
if
это всего лишь пример: вам решать, как обращаться с этой переменной.
Использовать 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
а затем все остальные вещи, которые вам нужны
Это решение читает один символ и вызывает функцию с ответом "да".
read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
do_something
fi
Можно обрабатывать "Да / Нет выбор" с учетом локали в оболочке POSIX; используя записи из LC_MESSAGES
Категория локали, witch предоставляет готовые шаблоны RegEx для соответствия вводу и строки для локализованного Да Нет.
#!/usr/bin/env sh
# Getting LC_MESSAGES values into variales
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
Чтобы получить красивое поле ввода, похожее на ncurses, используйте командный диалог:
#!/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
Диалоговый пакет по умолчанию устанавливается по крайней мере с SUSE Linux.
Извините за публикацию на такой старый пост. Несколько недель назад я столкнулся с подобной проблемой, в моем случае мне нужно было решение, которое также работало в онлайн-установочном скрипте, например: curl -Ss https://raw.github.com/_____/installer.sh | bash
С помощью read yesno < /dev/tty
у меня отлично работает
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
Надеюсь, это кому-нибудь поможет.
Только одно нажатие
Вот более длинный, но многократно используемый и модульный подход:
- Возвращает
0
= да и1
= нет - Не нужно нажимать ввод - только один символ
- Нажмите Enter, чтобы принять выбор по умолчанию.
- Можно отключить выбор по умолчанию для принудительного выбора
- Работает для обоих
zsh
а такжеbash
,
По умолчанию "нет" при нажатии Enter
Обратите внимание, что N
с большой буквы Здесь нажимается ввод, принимая значение по умолчанию:
$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]?
Также обратите внимание, что [y/N]?
был автоматически добавлен. По умолчанию "нет" принимается, поэтому ничего не отображается.
Повторяйте запрос, пока не получите правильный ответ:
$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]? X
Show dangerous command [y/N]? y
rm *
По умолчанию "да" при нажатии Enter
Обратите внимание, что Y
с большой буквы:
$ confirm_yes "Show dangerous command" && echo "rm *"
Show dangerous command [Y/n]?
rm *
Выше я просто нажал клавишу ввода, поэтому команда запустилась.
Нет по умолчанию при вводе - требуется y
или же 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
Вот, 1
или ложь была возвращена. Обратите внимание, что с помощью этой функции более низкого уровня вам необходимо предоставить [y/n]?
незамедлительный.
Код
# 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
}
Вы можете использовать по умолчанию REPLY
на read
, преобразовать в нижний регистр и сравнить с набором переменных с выражением.
Скрипт также поддерживает 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
read -e -p "Enter your choice: " choice
-e
Опция позволяет пользователю редактировать ввод с помощью клавиш со стрелками.
Если вы хотите использовать предложение в качестве входных данных:
read -e -i "yes" -p "Enter your choice: " choice
-i
опция печатает суггестивный ввод
На этот вопрос есть много хороших ответов, но из того, что я вижу, ни один из них не является моим идеалом, который бы:
- Будь проще, всего пара строк оболочки
- Работа с одним нажатием клавиши y/n (нет необходимости нажимать ввод)
- По умолчанию да, если вы просто нажмете ввод
- Работайте также с заглавными буквами Y/N
Вот моя версия, которая имеет эти свойства:
read -n1 -p "Continue? (Y/n) " confirm
if ! echo $confirm | grep '^[Yy]\?$'; then
exit 1
fi
Вы можете изменить это условное выражение, чтобы оно выполнялось только при «да» (просто удалите!
вif
заявление) или добавитьelse
если вы хотите запустить код в обеих ветвях.
Один лайнер:
read -p "Continue? [Enter] → Yes, [Ctrl]+[C] → No."
Это предполагает, что «Нет» и «Отмена» имеют одинаковый результат, поэтому нет причин рассматривать их по-разному.
Я заметил, что никто не опубликовал ответ, показывающий многострочное эхо-меню для такого простого пользовательского ввода, так что вот мое мнение:
#!/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
Этот метод был опубликован в надежде, что кто-то найдет его полезным и экономящим время.
Проверь это
read -p "Continue? (y/n): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
Вдохновленный ответами @Mark и @Myrddin, я создал эту функцию для универсального приглашения
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
}
используйте это так:
unipromtp "Select an option: (a)-Do one (x)->Do two (f)->Do three : " "a x f" selection
echo "$selection"
Я предлагаю вам использовать диалог...
Ученик Linux: улучшение сценариев оболочки Bash с помощью диалогового окна
Команда dialog позволяет использовать оконные блоки в сценариях оболочки, чтобы сделать их использование более интерактивным.
он прост и удобен в использовании, также есть версия gnome под названием gdialog, которая принимает те же параметры, но показывает стиль GUI на X.
Вариант с множественным выбором:
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
}
Пример:
$ 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
$
Это установит REPLY
в y
(внутри сценария).
Самое простое решение - это однострочник без хитрых уловок:
read -p "press enter ..." y
Напоминает классический DOS
Hit any key to continue
, за исключением того, что он ждет клавиши Enter, а не просто любой клавиши.
Правда, это не предлагает вам трех вариантов для Да Нет Отмена, но это полезно, когда вы принимаете Ctrl-C как Нет, соответственно. Отмена в простых скриптах, например:
#!/bin/sh
echo Backup this project
read -p "press enter ..." y
rsync -tavz . /media/hard_to_remember_path/backup/projects/yourproject/
потому что вам не нравится запоминать уродливые команды и пути, но ни скрипты, которые выполняются слишком быстро, не давая вам возможности остановиться, прежде чем вы решите, что это не тот скрипт, который вы намеревались запустить.
Более общим будет:
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
}
Да / Нет / Отмена
функция
#!/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
}
использование
case $(@confirm 'Confirm?') in
Y ) echo "Yes" ;;
N ) echo "No" ;;
C ) echo "Cancel" ;;
esac
Подтвердите с чистым пользовательским вводом
функция
#!/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
}
использование
if @confirm 'Confirm?' ; then
echo "Yes"
else
echo "No"
fi
В качестве друга однострочной команды я использовал следующее:
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, оно работает так:
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=;
Один простой способ сделать это с xargs -p
или гну parallel --interactive
,
Мне нравится поведение xargs немного лучше для этого, потому что он выполняет каждую команду сразу после приглашения, как другие интерактивные команды Unix, вместо того, чтобы собирать дачи для выполнения в конце. (Вы можете нажать Ctrl-C после того, как пройдете те, которые вы хотели.)
например,
echo *.xml | xargs -p -n 1 -J {} mv {} backup/