Як запропонувати ввести Так / Ні / Скасувати введення в сценарії оболонки Linux?


1443

Я хочу призупинити введення в скрипті оболонки та запропонувати користувачеві вибрати.
Стандартний Yes, Noабо Cancelтипу питання.
Як мені це зробити в типовому підказі на башти?


11
Так само, як примітка: умовна умова для підказок є такою, що якщо ви представляєте [yn]параметр, той, який використовується з великої літери, є типовим, тобто [Yn]за замовчуванням "так", а [yN]за замовчуванням - "ні". Дивіться ux.stackexchange.com/a/40445/43532
Tyzoid

Кожен, хто приходить сюди зі ZSH, дивиться цю відповідь, як використовувати readкоманду для
підказок

Відповіді:


1616

Найпростіший і найпоширеніший спосіб отримати введення користувача в командному рядку оболонки - це 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

Очевидно, що інші рядки комунікації залишаються неперекладеними (Встановити, відповісти), що потрібно було б вирішити у більш повному перекладі, але навіть частковий переклад був би корисним у багатьох випадках.

Нарешті, будь ласка , перевірте відмінний відповідь на Ф. Хаурі .


30
Використовуючи Bash в OS X Leopard, я змінив, exitщоб breakне закривати вкладку, коли вибрав "ні".
Трей Піпмейер

1
Як це працює з опціями довше, ніж Так чи Ні? У випадку ви пишете щось на кшталт: Встановити програму і нічого не робити після цього) зробити встановлення; перерва;
Шон

1
@Shawn Використовуючи читання, ви, звичайно, можете використовувати будь-яку схему glob або regex, підтримувану оператором перемикання оболонки bash. Він просто відповідає тексту, набраному вашим шаблонам, поки не знайде збіг. Використовуючи select , команда надає список варіантів і відображає їх користувачеві. Елементи в списку ви можете бути такими ж довгими або короткими, як вам подобається. Рекомендую перевіряти їх підручні сторінки на предмет вичерпної інформації про використання.
Мірддін Емріс

3
чому там breakв selectразі немає циклу?
Jayen

1
@akostadinov Я дійсно повинен більше читати свою історію редагування. Ви маєте рацію, я помиляюся, і я представив помилку, слідуючи порадам Джаяна, так. Помилка була виправлена ​​пізніше, але зміни були настільки далеко один від одного, що я навіть не пам'ятав, щоб змінити її.
Мірддін Емріс

525

Щонайменше п’ять відповідей на одне загальне питання.

В залежності від

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

і якщо хочете

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

Основний на лінії метод

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
Метод "Використання історії історії читання" настільки страшенно недоречний для питання ОП. Я радий, що ти все одно включив його. У мене є десятки набагато складніших сценаріїв, які я маю намір оновити за цим шаблоном!
Бруно Броноскі

5
Ви можете використовувати як caseдля POSIX, так і для bash (використовуйте умовні підстановки, а не bash-підрядку:) case $answer in; [Yy]* ) echo Yes ;;, але я вважаю за краще використовувати умовне твердження замість цього, віддаючи перевагу [ "$answer" != "${answer#[Yy]}" ]над вашим echo "$answer" | grep -iq ^y. Він більш портативний (деякі не-GNU-грейпи не реалізуються -qправильно) і не має системного виклику. ${answer#[Yy]}використовує розширення параметрів для вилучення Yабо yз початку $answer, викликаючи нерівність, коли будь-який присутній. Це працює в будь-якій оболонці POSIX (тире, ksh, bash, zsh, busybox тощо).
Адам Кац

1
@CarterPape Так, це був жарт! Але в цій деталізованій відповіді ви можете знайти безліч порад (другий пункт - принаймні 3 різні методи)! І ... приблизно з 5 років ви спочатку розкажете про мій метод підрахунку! ;-))
Ф. Хаурі

349
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"

25
Я не згоден, оскільки він реалізує лише частину функціональності діалогового вікна "Так, ні, скасувати" в DOS. Частина, яку вона не вдається реалізувати, - це перевірка введення ... циклічне отримання, поки не буде отримана дійсна відповідь.
Мірддін Емріс

1
(Оригінальна назва питання була "Як запросити введення в скрипт оболонки Linux?")
Пістос

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 ~/

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
цикл

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

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

Я початківець, тому прийміть це із зерном солі, але, здається, це працює.


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?"

...

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|)$ ]] => bashe regex

Час очікування => Відповідь за замовчуванням - Ні

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

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

а потім усі інші речі, які вам потрібні


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 опція друкує сугестивний ввід.


так, -e -iне працюйте в ш (
оборона

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категорії локалів, відьма забезпечує готові шаблони 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

EDIT: Як @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величина з великим капіталом. Тут натискається клавіша Enter, приймаючи за замовчуванням:

$ 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, тому команда бігла.

Не встановлено за замовчуванням 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

Сподіваюся, що це комусь допоможе.


Важливою частиною цього є перевірка вводу. Я думаю, що адаптувати мій перший приклад, щоб він прийняв 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

Цей метод був розміщений у надії, що хтось може вважати його корисним та економією часу.


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 Shell за допомогою діалогового вікна

Команда діалогу дозволяє використовувати вікна в скриптах оболонок, щоб зробити їх використання більш інтерактивним.

це простий і простий у використанні, також є версія gnome під назвою gdialog, яка приймає точно такі ж параметри, але показує її стиль GUI на X.


Для випадкового читача знайдіть фрагмент за допомогою діалогової команди тут: stackoverflow.com/a/22893526/363573
Stephan

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"

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або gnuparallel --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=;

Чи можете ви уточнити використання змінної підказок? Мені здається, що він стирається після одного вкладиша, тож як ти використовуєш лінію, щоб зробити що-небудь?
Мірддін Емріс

після циклу час витирається підказка. Тому що я хочу, щоб змінна підказка була ініціалізована після цього (оскільки я використовую оператор частіше). Надання цього рядка в оболонковому скрипті буде продовжуватися лише у випадку, якщо 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

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!'

Це здається складним і крихким. Як щодо всьогоyn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
трійчатка

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.