Как запросить Да / Нет / Отменить ввод в сценарии оболочки Linux?

1445

Я хочу приостановить ввод в сценарии оболочки и предложить пользователю выбор.
Стандартный Yes, Noили Cancelтипа вопрос.
Как мне сделать это в типичной командной строке?

Мирддин Эмрис
источник
12
Так же, как примечание: соглашение для приглашений таково, что если вы представите [yn]опцию, то заглавная будет по умолчанию, то есть по [Yn]умолчанию «да», а по [yN]умолчанию «нет». См. Ux.stackexchange.com/a/40445/43532
Tyzoid
Любой, кто приходит сюда из ZSH, видит этот ответ, чтобы узнать, как использовать readкоманду для запроса
smac89

Ответы:

1620

Самым простым и наиболее доступным способом получения пользовательского ввода в командной строке является 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

Другой метод, указал на Стивене Huwig , является в Bash 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повторять цикл, если они дают неверный ввод.

Кроме того, Леа Гри продемонстрировала способ сделать язык запроса независимым в своем ответе . Адаптация моего первого примера для лучшего обслуживания нескольких языков может выглядеть следующим образом:

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

Очевидно, что другие коммуникационные строки здесь остаются непереведенными (Install, Answer), которые должны быть учтены в более законченном переводе, но даже частичный перевод был бы полезен во многих случаях.

Наконец, пожалуйста , проверьте отличный ответ на Ф. Хаури .

Мирддин Эмрис
источник
31
Использование Bash в OS X Leopard, я изменил , exitчтобы breakсохранить от не закрывая вкладку , когда я выбрал «нет».
Трей Пипмайер
1
Как это работает с опциями дольше, чем Да или Нет? В случае case вы пишете что-то вроде: Установите программу и ничего не делайте потом) make install; перемена;
Шон
1
@Shawn Используя read, вы, конечно, можете использовать любой шаблон glob или regex, поддерживаемый оператором switch оболочки bash. Он просто сопоставляет текст, набранный с вашими шаблонами, пока не найдет совпадение. Используя select , команда получает список вариантов выбора и отображает их пользователю. Вы можете иметь элементы в списке так долго или так быстро, как вам нравится. Я рекомендую проверить их справочные страницы для всесторонней информации об использовании.
Мирддин Эмрис
3
почему breakв, selectесли нет петли?
Jayen
1
@akostadinov Я действительно должен больше читать историю редактирования. Вы правы, я не прав, и я внес ошибку, следуя совету Джаяна, ха. Ошибка была позже исправлена, но изменения были настолько далеки друг от друга, что я даже не вспомнил, что менял их.
Мирддин Эмрис
527

Как минимум пять ответов на один общий вопрос.

В зависимости от

  • совместимость: может работать на плохих системах с универсальным окружающая среда
  • специфический: использование так называемых башмизмов

и если вы хотите

  • простой `` in line '' вопрос / ответ (общие решения)
  • довольно отформатированные интерфейсы, вроде или более графический, используя 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

Примечание: это было проверено в соответствии с, , , а также !

То же самое, но ожидая явно 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

В bash 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

3. Некоторые приемы для специальных инструментов

Более сложные диалоговые окна, помимо простых 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, как и Up, Down, Ctrl+ rи другие.

Ф. Хаури
источник
4
Обратите внимание , что sttyобеспечивает -gвозможность для использования: old_stty=$(stty -g); stty raw -echo; …; stty "$old_stty". Это восстанавливает настройку в точности так, как они были найдены, которая может совпадать или не совпадать с stty -sane.
Джонатан Леффлер
3
read answerбудет интерпретировать обратную косую черту перед пробелами и переводами строки, и в противном случае удалит их, что редко требуется. Используйте read -r answerвместо этого согласно SC2162 .
vlfig
1
Метод «Использование истории readline» так ужасно не подходит для вопроса ОП. Я рад, что ты все равно включил это. У меня есть десятки гораздо более сложных сценариев, которые я собираюсь обновить с помощью этого шаблона!
Бруно Броноски
5
Вы можете использовать как caseдля POSIX, так и для bash (используйте подстановочный знак вместо подстроки bash:) case $answer in; [Yy]* ) echo Yes ;;, но я предпочитаю вместо этого использовать условный оператор, отдавая предпочтение [ "$answer" != "${answer#[Yy]}" ]вашему echo "$answer" | grep -iq ^y. Он более переносим (некоторые не-GNU greps не реализуются -qправильно) и не имеет системного вызова. ${answer#[Yy]}использует расширение параметра, чтобы удалить Yили yиз начала $answer, вызывая неравенство, когда либо присутствует. Это работает в любой оболочке POSIX (dash, ksh, bash, zsh, busybox и т. Д.).
Адам Кац
1
@CarterPape Да, это была шутка! Но в этом подробном ответе вы можете найти много советов (во втором пункте представлены как минимум 3 различных метода)! И ... примерно через 5 лет вы первыми расскажете о моем методе подсчета! ;-))
Ф. Хаури
349
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
Pistos
источник
26
Я не согласен, потому что он реализует только часть функциональности диалога «Да, Нет, Отмена» в DOS. Часть, которую он не может реализовать, это проверка ввода ... зацикливание, пока не будет получен правильный ответ.
Мирддин Эмрис
1
(Первоначальный заголовок вопроса был «Как мне
запросить
8
Но исходное описание вопроса остается неизменным и всегда запрашивается ответ на запрос «Да / Нет / Отмена». Название было обновлено, чтобы быть более понятным, чем мой оригинальный, но описание вопроса было всегда ясно (по моему мнению).
Мирддин Эмрис,
165

Вы можете использовать встроенную команду чтения ; Используйте -pопцию, чтобы ответить пользователю с вопросом.

Начиная с BASH4, теперь вы можете использовать, -iчтобы предложить ответ:

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 ~/
yPhil
источник
6
Следует отметить, что FILEPATHэто имя переменной, которое вы выбрали, и задается в ответе на командную строку. Так что если бы вы запустили vlc "$FILEPATH", например, vlcоткрыли бы этот файл.
Кен Шарп
В чем выгода -eво втором примере (просто да / нет)?
JBallin
Любая причина использовать -e -pвместо -ep?
JBallin
1
Без -eфлажка / опции вы можете (в зависимости от реализации) не иметь возможности набрать «y», а затем передумать и заменить его на «n» (или что-нибудь еще в этом отношении); При документировании команды перечисление параметров по отдельности лучше для удобства чтения / ясности, среди прочих причин.
yphil
109

Bash выбрал для этой цели.

select result in Yes No Cancel
do
    echo $result
done
Стивен Хьюиг
источник
18
+1 Гениально простое решение. Единственное: это будет подсказывать, подсказывать и подсказывать ... пока вы не добавите exitвнутренность :)
kaiser
5
(Кайзер: Чтобы выйти из него, просто введите EOT:. Ctrl-DНо, конечно, для реального кода, использующего его, потребуется разрыв или выход в теле.)
Zorawar
12
Это не позволит вам ввести y или n, однако. Вы выбираете, введя 1 2 или 3.
djjeck
3
exitвыйдет из сценария все вместе, breakвыйдет только из цикла, в котором вы находитесь (если вы находитесь в цикле whileили case)
wranvaud
58
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
источник
36

Вот что я собрал:

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

Я новичок, поэтому возьмите это с крошкой соли, но, похоже, это сработает.

mpen
источник
9
Если вы измените case $yn inна, case ${yn:-$2} inто вы можете использовать второй аргумент в качестве значения по умолчанию, Y или N.
jchook
1
или измените, case $ynчтобы case "${yn:-Y}"иметь да по умолчанию
rubo77
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
источник
1
Поставьте четыре пробела в начале каждой строки, чтобы сохранить форматирование кода.
Джуни К. Сеппанен
10
Почему мы предоставляем 'y' и 'n' в качестве параметров для запроса (), если переключатели регистра жестко закодированы? Это просто просит злоупотребления. Это фиксированные параметры, которые нельзя изменить, поэтому эхо в строке 2 должно выглядеть следующим образом: echo -n "$ 1 [Y / N]?" Они не могут быть изменены, поэтому их не следует указывать.
Мирддин Эмрис
1
@ MyrddinEmrys Не могли бы вы разработать свой комментарий? Или разместите ссылку на статью или пару ключевых слов, чтобы я мог провести исследование самостоятельно.
Матеуш Пиотровски
4
@MateuszPiotrowski Ответ был отредактирован и улучшен, так как я сделал свой комментарий. Вы можете нажать ссылку «Отредактировано 23 декабря» выше, чтобы просмотреть все предыдущие версии этого ответа. Еще в 2008 году код был совсем другим.
Мирддин Эмрис
27

Ты хочешь:

  • Встроенные команды 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 если TTY
  • read -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
olibre
источник
26

Самый простой способ добиться этого с наименьшим количеством строк заключается в следующем:

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

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

Это ifпросто пример: вам решать, как обращаться с этой переменной.

Апурв Нерлекар
источник
20

Используйте 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

а затем все остальные вещи, которые вам нужны

ThatLinuxGuy
источник
17

Это решение читает один символ и вызывает функцию с ответом «да».

read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    do_something      
fi
Деннис
источник
2
@Jav эхо печатает новую строку после вашего ответа. Без него следующая вещь, которая будет напечатана, появится сразу после вашего ответа в той же строке. Попробуйте удалить, echoчтобы убедиться в этом.
Деннис
13

Чтобы получить красивое поле ввода, похожее на 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. Похоже: команда "диалог" в действии

Торстен Стэрк
источник
12
read -e -p "Enter your choice: " choice

-eОпция позволяет пользователю редактировать ввод с помощью клавиш со стрелками.

Если вы хотите использовать предложение в качестве входных данных:

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

-i опция печатает суггестивный ввод

Jahid
источник
да, -e -iне работай в sh (оболочка Bourne), но вопрос специально помечен как bash ..
Джахид
12

Вы можете использовать значение REPLYпо умолчанию для a 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
Вальтер А
источник
12

Можно обрабатывать с учетом локали «выбор да / нет» в оболочке POSIX; используя записи LC_MESSAGESкатегории локали, witch предоставляет готовые шаблоны RegEx для соответствия входным данным и строки для локализованного Да Нет.

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

РЕДАКТИРОВАТЬ: Как @Urhixidur отметил в своем комментарии :

К сожалению, POSIX указывает только первые два (yesexpr и noexpr). В Ubuntu 16 yesstr и nostr пусты.

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

LC_MESSAGES

yesstrИ nostrлокал ключевые слова и YESSTRи NOSTRlanginfo элементов ранее были использованы для пользовательского матча позитивных и негативных ответов. В POSIX.1-2008, тем yesexpr, noexpr, YESEXPR, и NOEXPRрасширенные регулярные выражения заменили их. Приложения должны использовать общие средства обмена сообщениями на основе локали для выдачи сообщений с подсказками, которые содержат примеры желаемых ответов.

В качестве альтернативы, используя локализацию 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

Ответ сопоставляется с использованием регулярных выражений, предусмотренных в локали.

Чтобы перевести остальные сообщения, используйте bash --dump-po-strings scriptnameдля вывода строки po для локализации:

#: 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 ""
Леа Гри
источник
Мне нравится добавление опций, не зависящих от языка. Отлично сработано.
Мирддин Эмрис
1
К сожалению, POSIX указывает только первые два (yesexpr и noexpr). В Ubuntu 16 yesstr и nostr пусты.
Урхиксидур
1
Но ждать! Есть плохие новости! Выражения оператора случая bash не являются регулярными, они являются выражениями имени файла. Таким образом, yesexpr и noexpr в Ubuntu 16 («^ [yY]. *» И «^ [nN]. *» Соответственно) завершатся неудачно из-за встроенного периода. В регулярном выражении «. *» Означает «любой не символ новой строки, ноль или более раз». Но в случае с кейсом это буквальное "." с последующим любым количеством символов.
Урхиксидур
1
Наконец самое лучшее , что можно сделать с помощью yesexprи noexprв среде оболочки, является использование его в согласовании конкретных RegEx Bashif [[ "$yn" =~ $yesexpr ]]; then echo $"Answered yes"; else echo $"Answered no"; fi
Леа Gris
10

Только одно нажатие

Вот более длинный, но многократно используемый и модульный подход:

  • Возвращает 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 *

Выше я просто нажал клавишу ввода, поэтому команда запустилась.

Нет по умолчанию enter- требуется 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
}
Том Хейл
источник
Когда я тестировал этот сценарий, вместо вышеприведенного результата я получил, Show dangerous command [y/N]? [y/n]?иShow dangerous command [Y/n]? [y/n]?
Илиас Карим
Спасибо @IliasKarim, я исправил это только сейчас.
Том Хейл,
9

Извините за публикацию на такой старый пост. Несколько недель назад я столкнулся с подобной проблемой, в моем случае мне нужно было решение, которое также работало в онлайн-установочном скрипте, например: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

Надеюсь, это кому-нибудь поможет.

user9869932
источник
Важной частью этого является проверка входных данных. Я думаю, что адаптация моего первого примера так, чтобы он принимал ttyввод, как вы это сделали, также сработала бы для вас, а также получила бы цикл при неправильном вводе (представьте несколько символов в буфере; ваш метод заставит пользователя всегда выбирать нет).
Мирддин Эмрис
7

Я заметил, что никто не опубликовал ответ, показывающий многострочное эхо-меню для такого простого пользовательского ввода, так что вот мое мнение:

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

Этот метод был опубликован в надежде, что кто-то найдет его полезным и экономящим время.

Yokai
источник
4

Вариант с множественным выбором:

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(внутри скрипта).

Эрнест А
источник
4

Я предлагаю вам использовать диалог ...

Ученик Linux: улучшение сценариев оболочки Bash с помощью диалогового окна

Команда dialog позволяет использовать оконные блоки в сценариях оболочки, чтобы сделать их использование более интерактивным.

он прост и удобен в использовании, также есть версия gnome под названием gdialog, которая принимает те же параметры, но показывает стиль GUI на X.

Усама Аль-Маадид
источник
Для случайного читателя посмотрите фрагмент, используя команду диалога здесь: stackoverflow.com/a/22893526/363573
Стефан
4

Вдохновленный ответами @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"
Miguel
источник
4

более общим будет:

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
}
Александр Лёфквист
источник
3

Один простой способ сделать это с помощью xargs -pили GNU parallel --interactive.

Мне нравится поведение xargs немного лучше, потому что оно выполняет каждую команду сразу после приглашения, как и другие интерактивные команды unix, вместо того, чтобы собирать дачи для выполнения в конце. (Вы можете Ctrl-C после того, как вы пройдете через те, которые вы хотели.)

например,

echo *.xml | xargs -p -n 1 -J {} mv {} backup/
Джошуа Голдберг
источник
Неплохо, но xargs --interactiveограничено да или нет. Пока это все, что вам нужно, этого может быть достаточно, но мой оригинальный вопрос привел пример с тремя возможными результатами. Мне действительно нравится, что это поток, хотя; многие распространенные сценарии извлекут выгоду из его способности быть переданным по трубопроводу.
Мирддин Эмрис
Понимаю. Я думал, что «отмена» означает просто остановить все дальнейшее выполнение, которое поддерживается с помощью Ctrl-C, но если вам нужно сделать что-то более сложное при отмене (или нет), этого будет недостаточно.
Джошуа Гольдберг
3

В качестве друга однострочной команды я использовал следующее:

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=;
ccDict
источник
Можете ли вы уточнить использование переменной подсказки? Похоже, он вытерся после того, как один лайнер, так как вы используете линию, чтобы сделать что-нибудь?
Мирддин Эмрис
приглашение стирается после цикла while. Потому что я хочу, чтобы переменная подсказки была инициализирована впоследствии (поскольку я чаще использую оператор). Наличие этой строки в shell-скрипте будет продолжаться только в том случае, если набрано y | Y, и завершится, если набрано n | N, или повторите запрос ввода для всего остального.
ccDict
3

Я использовал это caseутверждение пару раз в таком сценарии, и использование статистики случая - хороший способ сделать это. whileПетля, что ecapsulates в caseблоке, который использует логическое условие может быть реализована для того , чтобы провести еще больший контроль над программой, и выполнить множество других требований. После того, как все условия будут выполнены, breakможно будет использовать элемент управления, который вернет управление в основную часть программы. Кроме того, для удовлетворения других условий, конечно, могут быть добавлены условные операторы, сопровождающие управляющие структуры: caseоператор и возможный whileцикл.

Пример использования caseвыписки для выполнения вашего запроса

#! /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
источник
3

Да / Нет / Отмена

функция

#!/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
Эдуардо Куомо
источник
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
источник
Это кажется сложным и хрупким. Как насчет только чтоyn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
tripleee
2

В ответ на других:

Вам не нужно указывать регистр в BASH4, просто используйте «,,», чтобы сделать переменную в нижнем регистре. Также мне очень не нравится помещать код внутри блока чтения, получать результат и иметь дело с ним вне IMO блока чтения. Также добавьте «q» для выхода из IMO. И наконец, почему введите «да», просто используйте -n1 и нажмите клавишу y.

Пример: пользователь может нажать y / n, а также q, чтобы просто выйти.

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
Майк К
источник
0

В большинстве случаев в таких сценариях необходимо продолжать выполнение сценария до тех пор, пока пользователь не продолжит вводить «да», и останавливать его нужно только тогда, когда пользователь вводит «нет». Приведенный ниже фрагмент поможет вам достичь этого!

#!/bin/bash
input="yes"
while [ "$input" == "yes" ]
do
  echo "execute script functionality here..!!"
  echo "Do you want to continue (yes/no)?"
  read input
done
Азар Ансари
источник