Jak monitować o wprowadzenie Tak / Nie / Anuluj w skrypcie powłoki systemu Linux?

1443

Chcę wstrzymać wprowadzanie danych w skrypcie powłoki i poprosić użytkownika o dokonanie wyboru.
Średnia Yes, Nolub Canceltypu pytanie.
Jak to zrobić w typowym monicie bash?

Myrddin Emrys
źródło
11
Tak jak uwaga: konwencja dla monitów jest taka, że ​​jeśli przedstawisz [yn]opcję, to, która jest pisana wielkimi literami, jest domyślna, tzn. [Yn]Domyślnie „tak” i [yN]domyślnie „nie”. Zobacz ux.stackexchange.com/a/40445/43532
Tyzoid
Każdy, kto przychodzi tutaj z ZSH, zapoznaj się z odpowiedzią na pytanie, jak używać readpolecenia, aby monitować
smac89,

Odpowiedzi:

1616

Najprostszą i najszerzej dostępną metodą uzyskiwania danych wejściowych od użytkownika w wierszu poleceń jest readpolecenie. Najlepszym sposobem zilustrowania tego zastosowania jest prosta demonstracja:

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

Inną metodą, zauważył przez Stevena Huwig , bash za selectpolecenie. Oto ten sam przykład, używając 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

Ze selectnie trzeba zdezynfekować wejście - wyświetla dostępne opcje i wpisać numer odpowiadający wyboru. Zapętla się również automatycznie, więc nie ma potrzeby, aby while truepętla próbowała się powtórzyć, jeśli dadzą nieprawidłowe dane wejściowe.

W swojej odpowiedzi Léa Gris pokazała sposób na agnostycyzm języka zapytań . Dostosowanie mojego pierwszego przykładu do lepszej obsługi wielu języków może wyglądać następująco:

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

Oczywiście inne ciągi komunikacyjne pozostają tutaj nieprzetłumaczone (zainstaluj, odpowiedz), które należałoby rozwiązać w tłumaczeniu pełniejszym, ale w wielu przypadkach pomocne byłoby nawet tłumaczenie częściowe.

Wreszcie, należy zapoznać się z doskonałą odpowiedź przez F. HAURI .

Myrddin Emrys
źródło
30
Korzystając z Bash w OS X Leopard, zmieniłem exitna, breakaby nie zamykać karty po wybraniu „nie”.
Trey Piepmeier,
1
Jak to działa z opcjami dłuższymi niż Tak lub Nie? W klauzuli przypadku, czy piszesz coś w stylu: Zainstaluj program i nic później nie rób) zainstaluj; złamać;
Shawn
1
@Shawn Korzystając z read , możesz oczywiście użyć dowolnego wzorca glob lub regex obsługiwanego przez instrukcję switch powłoki bash. Po prostu dopasowuje tekst wpisany do twoich wzorów, dopóki nie znajdzie pasującego tekstu. Za pomocą polecenia select lista opcji jest przekazywana do polecenia i wyświetla je użytkownikowi. Elementy na liście mogą być tak długie lub tak krótkie, jak chcesz. Polecam sprawdzenie ich stron podręcznika w celu uzyskania wyczerpujących informacji na temat użytkowania.
Myrddin Emrys
3
dlaczego jest breakw tym, selectjeśli nie ma pętli?
Jayen
1
@akostadinov Naprawdę powinienem więcej przeczytać historię edycji. Masz rację, mylę się i wprowadziłem błąd zgodnie z radą Jayana, hah. Błąd został później naprawiony, ale zmiany były tak daleko od siebie, że nawet nie pamiętałem, żeby to zmienić.
Myrddin Emrys
525

Co najmniej pięć odpowiedzi na jedno ogólne pytanie.

Zależy od

  • zgodny: może działać na słabych systemach z ogólnymi środowiska
  • specyficzne: stosowanie tak zwanych baszizmów

a jeśli chcesz

  • proste pytanie / odpowiedź `` w linii '' (ogólne rozwiązania)
  • całkiem sformatowane interfejsy, jak lub bardziej graficzny za pomocą libgtk lub libqt ...
  • korzystaj z zaawansowanych możliwości historii odczytu

1. Ogólne rozwiązania POSIX

Możesz użyć readpolecenia, a następnie 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

(Dzięki komentarzowi Adama Katza : zastąpiłem powyższy test testem, który jest bardziej przenośny i unika jednego widelca :)

POSIX, ale funkcja jednego klucza

Ale jeśli nie chcesz, aby użytkownik musiał trafić Return, możesz napisać:

( Edytowano: Jak słusznie sugerują @JathanathanLeffler, zapisanie konfiguracji stty może być lepsze niż po prostu zmusić ich do rozsądku .)

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

Uwaga: zostało to przetestowane pod, , , i !

To samo, ale czekając wprost na ylub 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

Korzystanie z dedykowanych narzędzi

Istnieje wiele narzędzi, które zostały zbudowane za pomocą libncurses, libgtk, libqtlub innych bibliotek graficznych. Na przykład za pomocą whiptail:

if whiptail --yesno "Is this a good question" 20 60 ;then
    echo Yes
else
    echo No
fi

W zależności od systemu może być konieczne zastąpienie whiptailgo innym podobnym narzędziem:

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

gdzie 20jest wysokość okna dialogowego w liczbie linii i 60szerokość okna dialogowego. Wszystkie te narzędzia mają prawie taką samą składnię.

DIALOG=whiptail
if [ -x /usr/bin/gdialog ] ;then DIALOG=gdialog ; fi
if [ -x /usr/bin/xdialog ] ;then DIALOG=xdialog ; fi
...
$DIALOG --yesno ...

2. Rozwiązania specyficzne dla Bash

Podstawowa metoda liniowa

read -p "Is this a good question (y/n)? " answer
case ${answer:0:1} in
    y|Y )
        echo Yes
    ;;
    * )
        echo No
    ;;
esac

Wolę używać, casewięc mogę nawet przetestować w yes | ja | si | ouirazie potrzeby ...

zgodnie z funkcją jednego klucza

W bash możemy określić długość zamierzonego wejścia dla readpolecenia:

read -n 1 -p "Is this a good question (y/n)? " answer

W bash readpolecenie akceptuje parametr limitu czasu , który może być przydatny.

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. Kilka sztuczek dla dedykowanych narzędzi

Bardziej zaawansowane okna dialogowe poza prostymi yes - nocelami:

dialog --menu "Is this a good question" 20 60 12 y Yes n No m Maybe

Pasek postępu:

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
) 

Mała wersja demo:

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

Więcej próbek? Zobacz Korzystanie z whiptail do wybierania urządzenia USB i wymiennego selektora pamięci USB: USBKeyChooser

5. Korzystanie z historii readline

Przykład:

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

Spowoduje to utworzenie pliku .myscript.historyw $HOMEkatalogu, niż można użyć polecenia readline historii, na przykład Up, Down, Ctrl+ ri innych.

F. Hauri
źródło
4
Zauważ, że sttyzapewnia -gmożliwość użycia: old_stty=$(stty -g); stty raw -echo; …; stty "$old_stty". To przywraca ustawienia dokładnie tak, jak zostały znalezione, które mogą, ale nie muszą być takie same jak stty -sane.
Jonathan Leffler,
3
read answerzinterpretuje odwrotne ukośniki przed spacjami i liniami, a w przeciwnym razie usuwa je, co rzadko jest zamierzone. Użyj read -r answerzamiast tego zgodnie z SC2162 .
vlfig
1
Metoda „Korzystanie z historii readline” jest tak strasznie nieodpowiednia dla pytania PO. Cieszę się, że i tak to uwzględniłeś. Mam dziesiątki znacznie bardziej skomplikowanych skryptów, które zamierzam zaktualizować za pomocą tego wzoru!
Bruno Bronosky,
5
Możesz używać casezarówno POSIX, jak i bash (użyj znaku zastępczego zamiast podciągu bash:) case $answer in; [Yy]* ) echo Yes ;;, ale wolę zamiast tego używać instrukcji warunkowej, faworyzując [ "$answer" != "${answer#[Yy]}" ]twoje echo "$answer" | grep -iq ^y. Jest bardziej przenośny (niektóre grepy spoza GNU nie implementują się -qpoprawnie) i nie ma wywołania systemowego. ${answer#[Yy]}używa rozszerzenia parametrów do usunięcia Ylub yod początku $answer, powodując nierówność, gdy którykolwiek z nich jest obecny. Działa to w dowolnej powłoce POSIX (dash, ksh, bash, zsh, busybox itp.).
Adam Katz
1
@CarterPape Tak, to był żart! Ale w tej oderwanej odpowiedzi możesz znaleźć wiele wskazówek (drugi punkt przedstawia co najmniej 3 różne metody)! I ... od około 5 lat po raz pierwszy opowiadasz o mojej metodzie liczenia! ;-))
F. Hauri
349
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
Pistacje
źródło
25
Nie zgadzam się, ponieważ implementuje tylko część funkcjonalności okna dialogowego „Tak, Nie, Anuluj” w DOS. Częścią, której nie implementuje, jest sprawdzanie wejścia ... zapętlanie do momentu otrzymania poprawnej odpowiedzi.
Myrddin Emrys
1
(Oryginalny tytuł pytania brzmiał: „Jak
poprosić
8
Ale oryginalny opis pytania pozostaje niezmieniony i zawsze pytany jest o odpowiedź na pytanie Tak / Nie / Anuluj. Tytuł został zaktualizowany, aby był jaśniejszy niż mój oryginalny, ale opis pytania był zawsze jasny (moim zdaniem).
Myrddin Emrys
165

Możesz użyć wbudowanego polecenia odczytu ; Użyj -popcji, aby zapytać użytkownika o pytanie.

Od wersji BASH4 możesz teraz -isugerować odpowiedź:

read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
echo $FILEPATH

(Pamiętaj jednak, aby użyć opcji „readline”, -eaby umożliwić edycję linii za pomocą klawiszy strzałek)

Jeśli chcesz logiki „tak / nie”, możesz zrobić coś takiego:

read -e -p "
List the content of your home dir ? [Y/n] " YN

[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/
yPhil
źródło
6
Należy zauważyć, że FILEPATHjest to nazwa zmiennej, którą wybrałeś i jest ustawiana wraz z odpowiedzią na wiersz polecenia. Więc jeśli miałbyś wtedy uruchomić vlc "$FILEPATH", na przykład vlcotworzyłby ten plik.
Ken Sharp
Jakie są korzyści z -edrugiego przykładu (proste tak / nie)?
JBallin,
Czy jest jakiś powód, aby użyć -e -pzamiast -ep?
JBallin,
1
Bez -eflagi / opcji możesz (w zależności od implementacji) nie być w stanie wpisać „y”, a następnie zmienić zdanie i zamienić na „n” (lub cokolwiek innego w tym zakresie); Podczas dokumentowania polecenia osobne wyszczególnienie opcji jest lepsze między innymi ze względu na czytelność / przejrzystość.
yPhil
109

Bash wybrał do tego celu.

select result in Yes No Cancel
do
    echo $result
done
Steven Huwig
źródło
18
+1 Genialnie proste rozwiązanie. Jedyna rzecz: to podpowie, podpowie i podpowie ... dopóki nie dodasz exitwewnątrz :)
kaiser
5
(kaiser: Aby się od niego oderwać, wystarczy wpisać EOT:. Ctrl-DAle oczywiście prawdziwy kod, który go użyje, będzie wymagał przerwy lub wyjścia w ciele.)
Zorawar
12
Nie pozwoli to jednak na wpisanie y ani n. Możesz wybrać, wpisując 1 2 lub 3.
djjeck,
3
exitwyjdzie ze skryptu wszystkie razem, breakwyjdzie tylko z pętli, w której się znajdujesz (jeśli jesteś w pętli whilelub case)
wranvaud
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
serg
źródło
36

Oto coś, co złożyłem:

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

Jestem początkującym, więc weź to z odrobiną soli, ale wydaje się, że działa.

mpen
źródło
9
Jeśli zmienisz case $yn inna case ${yn:-$2} in, możesz użyć drugiego argumentu jako wartości domyślnej, Y lub N.
jchook
1
lub zmień case $ynna case "${yn:-Y}"domyślną tak
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
źródło
1
Umieść cztery spacje na początku każdego wiersza, aby zachować formatowanie kodu.
Jouni K. Seppänen
10
Dlaczego podajemy „y” i „n” jako parametry do zapytania (), jeśli przełączniki wielkości liter są zakodowane na stałe? To tylko prośba o niewłaściwe użycie. Są to parametry stałe, niezmienne, więc echo w linii 2 powinno brzmieć: echo -n „$ 1 [T / N]?” Nie można ich zmienić, więc nie należy ich podawać.
Myrddin Emrys
1
@MyrddinEmrys Czy mógłbyś rozwinąć swój komentarz? Lub opublikuj link do artykułu lub kilku słów kluczowych, aby móc samodzielnie przeprowadzić badania.
Mateusz Piotrowski
4
@MateuszPiotrowski Odpowiedź została zredagowana i poprawiona od czasu mojego komentarza. Możesz kliknąć link „edytowany 23 grudnia” powyżej, aby wyświetlić wszystkie poprzednie wersje tej odpowiedzi. W 2008 roku kod był zupełnie inny.
Myrddin Emrys
27

Chcesz:

  • Wbudowane polecenia Bash (np. Przenośne)
  • Sprawdź TTY
  • Domyślna odpowiedź
  • Koniec czasu
  • Kolorowe pytanie

Skrawek

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

Objaśnienia

  • [[ -t 0 ]] && read ...=> Polecenie wywołania, readjeśli TTY
  • read -n 1 => Poczekaj na jeden znak
  • $'\e[1;32m ... \e[0m '=> Drukuj na zielono
    (zielony jest w porządku, ponieważ można go odczytać zarówno na białym / czarnym tle)
  • [[ $do_xxxx =~ ^(y|Y|)$ ]] => wyrażenie regularne bash

Limit czasu => Domyślna odpowiedź to Nie

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
źródło
26

Najłatwiejszym sposobem osiągnięcia tego celu przy użyciu najmniejszej liczby linii jest:

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

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

To iftylko przykład: od Ciebie zależy, jak obsłużyć tę zmienną.

Apurv Nerlekar
źródło
20

Użyj readpolecenia:

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

a potem wszystkie inne potrzebne rzeczy

ThatLinuxGuy
źródło
17

To rozwiązanie odczytuje pojedynczy znak i wywołuje funkcję w odpowiedzi TAK.

read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    do_something      
fi
Dennis
źródło
2
@Jaw echo drukuje nowy wiersz po Twojej odpowiedzi. Bez tego następna rzecz do wydrukowania pojawi się natychmiast po Twojej odpowiedzi w tym samym wierszu. Spróbuj usunąć echona własne oczy.
Dennis
13

Aby uzyskać ładne pole tekstowe podobne do ncurses, skorzystaj z okna dialogowego poleceń takiego:

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

Pakiet dialogowy jest instalowany domyślnie przynajmniej w systemie SUSE Linux. Wygląda jak: polecenie „dialog” w akcji

Thorsten Staerk
źródło
12
read -e -p "Enter your choice: " choice

-eOpcja pozwala użytkownikowi na edycję wejście przy użyciu klawiszy strzałek.

Jeśli chcesz użyć sugestii jako danych wejściowych:

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

-i opcja wypisuje sugestywne dane wejściowe.

Jahid
źródło
yap, -e -inie pracuj w sh (powłoka Bourne'a), ale pytanie jest oznaczone bash konkretnie ..
Jahid
12

Możesz użyć wartości domyślnej REPLYna a read, przekonwertować na małe litery i porównać z zestawem zmiennych z wyrażeniem.
Skrypt obsługuje również 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
Walter A.
źródło
12

W powłoce POSIX można obsługiwać rozpoznawanie ustawień narodowych „Tak / Nie wybór”; za pomocą wpisów z LC_MESSAGESkategorii ustawień regionalnych czarownica zapewnia gotowe wzorce RegEx pasujące do danych wejściowych oraz ciągi znaków dla zlokalizowanego Tak Nie.

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

EDYCJA: Jak wspomniał @Urhixidur w swoim komentarzu :

Niestety POSIX określa tylko dwa pierwsze (yesexpr i noexpr). Na Ubuntu 16, takstr i nostr są puste.

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

LC_MESSAGES

yesstrI nostrlocale słowa kluczowe a YESSTRi NOSTRlanginfo przedmioty były dawniej stosowane do użytkowników mecz pozytywnych i negatywnych odpowiedzi. W POSIX.1-2008 Z yesexpr, noexpr, YESEXPR, i NOEXPRrozszerzone wyrażenia regularne zastąpiły je. Aplikacje powinny korzystać z ogólnych funkcji przesyłania wiadomości opartych na ustawieniach regionalnych, aby wydawać komunikaty monitujące, które zawierają przykładowe pożądane odpowiedzi.

Alternatywnie, używając ustawień narodowych w 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

Odpowiedź jest dopasowywana za pomocą wyrażeń regularnych dostarczonych przez środowisko regionalne.

Aby przetłumaczyć pozostałe komunikaty, użyj, bash --dump-po-strings scriptnameaby wyprowadzić ciągi znaków do lokalizacji:

#: 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 ""
Léa Gris
źródło
Uwielbiam dodanie opcji agnostycznej języka. Dobra robota.
Myrddin Emrys
1
Niestety POSIX określa tylko dwa pierwsze (yesexpr i noexpr). Na Ubuntu 16, takstr i nostr są puste.
Urhixidur
1
Ale poczekaj! Są gorsze wiadomości! Wyrażenia instrukcji bash nie są regularne, są wyrażeniami nazw plików. Tak więc yesexpr i noexpr w Ubuntu 16 (odpowiednio „^ [yY]. *” I „^ [nN]. *”) Całkowicie się nie powiodą z powodu osadzonego okresu. W wyrażeniu regularnym „. *” Oznacza „dowolny znak inny niż znak, zero lub więcej razy”. Ale w opisie przypadku jest to dosłowne „”. po którym następuje dowolna liczba znaków.
Urhixidur
1
Ostatecznie najlepszym, co można zrobić z yesexpri noexprw środowisku powłoki, jest wykorzystanie go w bash za specyficzny RegEx dopasowywaniaif [[ "$yn" =~ $yesexpr ]]; then echo $"Answered yes"; else echo $"Answered no"; fi
Léa Gris
10

Tylko jedno naciśnięcie klawisza

Oto dłuższe, ale wielokrotnego użytku i modułowe podejście:

  • Zwraca 0= tak i 1= nie
  • Nie trzeba naciskać Enter - tylko jeden znak
  • Można nacisnąć, enteraby zaakceptować domyślny wybór
  • Może wyłączyć domyślny wybór, aby wymusić wybór
  • Działa zarówno dla, jak zshi dla bash.

Domyślnie „nie” po naciśnięciu Enter

Zauważ, że Njest on kapitałowany. Tutaj naciśnij klawisz Enter, przyjmując wartość domyślną:

$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]?

Pamiętaj też, że [y/N]?został on automatycznie dołączony. Domyślne „nie” jest akceptowane, więc nic nie jest powtarzane.

Powtórz monit, dopóki nie zostanie udzielona poprawna odpowiedź:

$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]? X
Show dangerous command [y/N]? y
rm *

Domyślnie „tak” po naciśnięciu Enter

Pamiętaj, że Ywielkie litery:

$ confirm_yes "Show dangerous command" && echo "rm *"
Show dangerous command [Y/n]?
rm *

Powyżej po prostu nacisnąłem Enter, więc polecenie zostało uruchomione.

Brak domyślnego włączenia enter- wymaga ylubn

$ 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

Tutaj 1lub false został zwrócony. Zauważ, że dzięki tej funkcji niższego poziomu musisz podać swój własny [y/n]?monit.

Kod

# Read a single char from /dev/tty, prompting with "$*"
# Note: pressing enter will return a null string. Perhaps a version terminated with X and then remove it in caller?
# See https://unix.stackexchange.com/a/367880/143394 for dealing with multi-byte, etc.
function get_keypress {
  local REPLY IFS=
  >/dev/tty printf '%s' "$*"
  [[ $ZSH_VERSION ]] && read -rk1  # Use -u0 to read from STDIN
  # See https://unix.stackexchange.com/q/383197/143394 regarding '\n' -> ''
  [[ $BASH_VERSION ]] && </dev/tty read -rn1
  printf '%s' "$REPLY"
}

# Get a y/n from the user, return yes=0, no=1 enter=$2
# Prompt using $1.
# If set, return $2 on pressing enter, useful for cancel or defualting
function get_yes_keypress {
  local prompt="${1:-Are you sure [y/n]? }"
  local enter_return=$2
  local REPLY
  # [[ ! $prompt ]] && prompt="[y/n]? "
  while REPLY=$(get_keypress "$prompt"); do
    [[ $REPLY ]] && printf '\n' # $REPLY blank if user presses enter
    case "$REPLY" in
      Y|y)  return 0;;
      N|n)  return 1;;
      '')   [[ $enter_return ]] && return "$enter_return"
    esac
  done
}

# Credit: http://unix.stackexchange.com/a/14444/143394
# Prompt to confirm, defaulting to NO on <enter>
# Usage: confirm "Dangerous. Are you sure?" && rm *
function confirm {
  local prompt="${*:-Are you sure} [y/N]? "
  get_yes_keypress "$prompt" 1
}    

# Prompt to confirm, defaulting to YES on <enter>
function confirm_yes {
  local prompt="${*:-Are you sure} [Y/n]? "
  get_yes_keypress "$prompt" 0
}
Tom Hale
źródło
Kiedy przetestowałem ten skrypt, zamiast outut powyżej, dostałem Show dangerous command [y/N]? [y/n]?iShow dangerous command [Y/n]? [y/n]?
Ilias Karim
Dzięki @IliasKarim, naprawiłem to właśnie teraz.
Tom Hale
9

Przepraszamy za zamieszczanie postów na tak starym postu. Kilka tygodni temu miałem do czynienia z podobnym problemem, w moim przypadku potrzebowałem rozwiązania, które działałoby również w internetowym skrypcie instalacyjnym, np .:curl -Ss https://raw.github.com/_____/installer.sh | bash

Używanie read yesno < /dev/ttydziała dobrze dla mnie:

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

Mam nadzieję, że to komuś pomoże.

użytkownik9869932
źródło
Ważną częścią tego jest sprawdzanie poprawności danych wejściowych. Myślę, że dostosowanie mojego pierwszego przykładu do akceptowania ttydanych wejściowych, tak jak zrobiłbyś to samo, zrobiłoby to również dla ciebie, a także dostało się zapętlenia na złych danych wejściowych (wyobraź sobie kilka znaków w buforze; twoja metoda zmusiłaby użytkownika, aby zawsze wybierał nie).
Myrddin Emrys
7

Zauważyłem, że nikt nie opublikował odpowiedzi pokazującej wielowierszowe menu echa dla tak prostego wprowadzania danych przez użytkownika, więc oto mój wybór:

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

Ta metoda została opublikowana w nadziei, że ktoś może uznać ją za przydatną i oszczędzającą czas.

Jokai
źródło
4

Wersja wielokrotnego wyboru:

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
}

Przykład:

$ 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
$

Ustawi się REPLYna y(wewnątrz skryptu).

Ernest A.
źródło
4

Sugeruję skorzystanie z okna dialogowego ...

Linux Apprentice: Popraw skrypty powłoki Bash za pomocą okna dialogowego

Polecenie dialogowe umożliwia użycie okien w skryptach powłoki, aby zwiększyć ich interaktywność.

jest prosty i łatwy w użyciu, istnieje również wersja gnome o nazwie gdialog, która przyjmuje dokładnie te same parametry, ale pokazuje styl GUI na X.

Osama Al-Maadeed
źródło
Dla zwykłego czytelnika zajrzyj do fragmentu za pomocą polecenia dialogowego tutaj: stackoverflow.com/a/22893526/363573
Stephan
4

Zainspirowany odpowiedziami @Mark i @Myrddin stworzyłem tę funkcję dla uniwersalnego monitu

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
}

użyj tego w ten sposób:

unipromtp "Select an option: (a)-Do one (x)->Do two (f)->Do three : " "a x f" selection
echo "$selection"
Miguel
źródło
4

bardziej ogólne byłoby:

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
}
Alexander Löfqvist
źródło
3

Jednym prostym sposobem na to jest za pomocą xargs -plub GNUparallel --interactive .

Podoba mi się w tym nieco bardziej zachowanie xargs, ponieważ wykonuje on każde polecenie natychmiast po znaku zachęty, podobnie jak inne interaktywne polecenia unix, zamiast zbierać tak, aby uruchomić je na końcu. (Możesz Ctrl-C po przejściu przez te, które chciałeś.)

na przykład,

echo *.xml | xargs -p -n 1 -J {} mv {} backup/
Joshua Goldberg
źródło
Nieźle, ale xargs --interactiveogranicza się do tak lub nie. Dopóki to wszystko, czego potrzebujesz, może wystarczyć, ale moje pierwotne pytanie zawierało przykład z trzema możliwymi wynikami. Naprawdę podoba mi się, że można go przesyłać strumieniowo; wiele typowych scenariuszy zyskałoby na tym, że można go potokować.
Myrddin Emrys
Widzę. Myślałem, że „anuluj” miało po prostu zatrzymać wszelkie dalsze wykonywanie, które obsługuje to za pomocą Ctrl-C, ale jeśli musisz zrobić coś bardziej złożonego po anulowaniu (lub nie), to nie wystarczy.
Joshua Goldberg,
3

Jako przyjaciel polecenia jednowierszowego użyłem:

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=;

Napisane długie, działa w następujący sposób:

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
źródło
Czy możesz wyjaśnić użycie zmiennej pytania? Wygląda mi na to, że został wyczyszczony po jednej wkładce, więc jak używasz tej linii do ZROBIENIA czegokolwiek?
Myrddin Emrys
monit zostanie wyczyszczony po pętli while. Ponieważ chcę później zainicjować zmienną zachęty (ponieważ częściej używam instrukcji). Umieszczenie tego wiersza w skrypcie powłoki będzie kontynuowane tylko wtedy, gdy zostanie wpisane y | Y i wyjdzie, jeśli zostanie wpisane n | N, lub powtórzy zapytanie o dane wejściowe dla całej reszty.
ccDict
3

Użyłem tego casestwierdzenia kilka razy w takim scenariuszu, użycie statystyki przypadku jest dobrym sposobem na obejście tego. whilePętla, że ecapsulates ten caseblok, który wykorzystuje stan logiczny mogą być realizowane w celu przeprowadzenia jeszcze większą kontrolę programu i spełniać wiele innych wymagań. Po spełnieniu wszystkich warunków breakmożna użyć przycisku, który przekaże kontrolę z powrotem do głównej części programu. Ponadto, aby spełnić inne warunki, można oczywiście dodać instrukcje warunkowe, które towarzyszą strukturom kontrolnym: caseinstrukcja i możliwewhile pętla.

Przykład użycia caseinstrukcji w celu spełnienia żądania

#! /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
źródło
3

Tak / Nie / Anuluj

Funkcjonować

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

Stosowanie

case $(@confirm 'Confirm?') in
  Y ) echo "Yes" ;;
  N ) echo "No" ;;
  C ) echo "Cancel" ;;
esac

Potwierdź, wprowadzając czyste dane użytkownika

Funkcjonować

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

Stosowanie

if @confirm 'Confirm?' ; then
  echo "Yes"
else
  echo "No"
fi
Eduardo Cuomo
źródło
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
źródło
Wydaje się to złożone i kruche. Jak o właśnieyn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
tripleee
2

W odpowiedzi na inne:

Nie musisz określać wielkości liter w BASH4, po prostu użyj „,,”, aby małe litery były zmienne. Również zdecydowanie nie lubię umieszczać kodu w bloku odczytu, uzyskiwać wynik i radzić sobie z nim poza blokiem odczytu IMO. Dołącz także „q”, aby wyjść z IMO. Na koniec, dlaczego wpisz „tak”, po prostu użyj -n1 i naciśnij klawisz y.

Przykład: użytkownik może nacisnąć t / n, a także q, aby wyjść.

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
Mike Q
źródło
0

W większości przypadków w takich sytuacjach konieczne jest kontynuowanie wykonywania skryptu, dopóki użytkownik nie wprowadzi „tak”, a zatrzymanie nastąpi tylko wtedy, gdy użytkownik wprowadzi „nie”. Poniższy fragment pomoże Ci to osiągnąć!

#!/bin/bash
input="yes"
while [ "$input" == "yes" ]
do
  echo "execute script functionality here..!!"
  echo "Do you want to continue (yes/no)?"
  read input
done
Azhar Ansari
źródło