Làm cách nào để tôi nhắc nhập Có / Không / Hủy trong tập lệnh shell Linux?


1443

Tôi muốn tạm dừng đầu vào trong tập lệnh shell và nhắc người dùng lựa chọn.
Các câu hỏi tiêu chuẩn Yes, Nohoặc Cancelloại.
Làm thế nào để tôi thực hiện điều này trong một dấu nhắc bash điển hình?


11
Cũng giống như một lưu ý: quy ước cho lời nhắc là nếu bạn đưa ra một [yn]tùy chọn, tùy chọn được viết hoa là mặc định, nghĩa là [Yn]mặc định là "có" và [yN]mặc định là "không". Xem ux.stackexchange.com/a/40445/43532
Tyzoid

Bất cứ ai đến đây từ ZSH, hãy xem câu trả lời này để biết cách sử dụng readlệnh để nhắc
smac89

Câu trả lời:


1619

Phương pháp đơn giản và phổ biến nhất để nhận đầu vào của người dùng tại dấu nhắc shell là readlệnh. Cách tốt nhất để minh họa việc sử dụng nó là một minh chứng đơn giản:

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

Một phương pháp khác, được chỉ ra bởi Steven Huwig , là selectmệnh lệnh của Bash . Đây là ví dụ tương tự bằng cách sử dụng 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

Với selectbạn không cần vệ sinh đầu vào - nó sẽ hiển thị các lựa chọn có sẵn và bạn nhập một số tương ứng với lựa chọn của bạn. Nó cũng tự động lặp lại, do đó không cần while truevòng lặp để thử lại nếu chúng cho đầu vào không hợp lệ.

Ngoài ra, Léa Gris đã thể hiện một cách để làm cho ngôn ngữ yêu cầu trở thành bất khả tri trong câu trả lời của cô . Điều chỉnh ví dụ đầu tiên của tôi để phục vụ tốt hơn nhiều ngôn ngữ có thể giống như thế này:

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

Rõ ràng các chuỗi giao tiếp khác vẫn chưa được dịch ở đây (Cài đặt, Trả lời) cần được giải quyết trong một bản dịch hoàn chỉnh hơn, nhưng ngay cả một bản dịch một phần cũng có ích trong nhiều trường hợp.

Cuối cùng, xin vui lòng kiểm tra câu trả lời tuyệt vời của F. Hauri .


31
Sử dụng Bash trong OS X Leopard, tôi đã thay đổi exitđể breakđể tránh đóng tab khi tôi chọn 'không'.
Trey Piepmeier

1
Làm thế nào để nó hoạt động với các tùy chọn dài hơn Có hoặc Không? Trong mệnh đề trường hợp, bạn có viết một cái gì đó như: Cài đặt chương trình và không làm gì sau đó) thực hiện cài đặt; phá vỡ;
Shawn

1
@Shawn Sử dụng đọc , tất nhiên, bạn có thể sử dụng bất kỳ mẫu toàn cầu hoặc biểu thức chính quy nào được hỗ trợ bởi câu lệnh chuyển đổi của bash shell. Nó chỉ phù hợp với văn bản gõ vào mẫu của bạn cho đến khi tìm thấy kết quả khớp. Sử dụng select , danh sách các lựa chọn được đưa ra cho lệnh và nó hiển thị chúng cho người dùng. Bạn có thể có các mục trong danh sách dài hoặc ngắn như bạn muốn. Tôi khuyên bạn nên kiểm tra trang người đàn ông của họ để biết thông tin sử dụng toàn diện.
Myrddin Emrys

3
Tại sao có một breaktrong selectnếu không có vòng lặp?
Jayen

1
@akostadinov Tôi thực sự nên đọc lịch sử chỉnh sửa của mình nhiều hơn. Bạn nói đúng, tôi sai và tôi đã giới thiệu một lỗi theo lời khuyên của Jayan, hah. Lỗi này sau đó đã được sửa, nhưng các chỉnh sửa cách xa nhau tôi thậm chí không nhớ đã thay đổi nó.
Myrddin Emrys

526

Ít nhất năm câu trả lời cho một câu hỏi chung chung.

Phụ thuộc vào

  • tuân thủ: có thể làm việc trên các hệ thống kém với chung môi trường
  • cụ thể: sử dụng cái gọi là bashism

và nếu bạn muốn

  • câu hỏi / câu trả lời `` in line '' (giải pháp chung)
  • giao diện được định dạng đẹp, như hoặc nhiều đồ họa hơn bằng libgtk hoặc libqt ...
  • sử dụng khả năng lịch sử đọc mạnh mẽ

1. Giải pháp chung POSIX

Bạn có thể sử dụng readlệnh, theo sau 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

(Cảm ơn nhận xét của Adam Katz : Đã thay thế bài kiểm tra ở trên bằng một bài kiểm tra dễ mang theo hơn và tránh một ngã ba :)

POSIX, nhưng tính năng chính duy nhất

Nhưng nếu bạn không muốn người dùng phải đánh Return, bạn có thể viết:

( Đã chỉnh sửa: Như @JonathanLeffler đã đề xuất một cách đúng đắn, việc lưu cấu hình của stty có thể tốt hơn là chỉ đơn giản là buộc họ phải tỉnh táo .)

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

Lưu ý: Điều này đã được thử nghiệm dưới, , , !

Tương tự, nhưng chờ đợi rõ ràng cho yhoặc 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

Sử dụng các công cụ chuyên dụng

Có nhiều công cụ đã được xây dựng sử dụng libncurses, libgtk, libqthoặc các thư viện đồ họa khác. Ví dụ whiptail: sử dụng :

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

Tùy thuộc vào hệ thống của bạn, bạn có thể cần phải thay thế whiptailbằng một công cụ tương tự khác:

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

trong đó 20chiều cao của hộp thoại tính theo số dòng và 60chiều rộng của hộp thoại. Các công cụ này đều có cú pháp gần giống nhau .

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

2. Bash giải pháp cụ thể

Phương pháp cơ bản trong dòng

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

Tôi thích sử dụng casehơn nên tôi thậm chí có thể kiểm tra yes | ja | si | ouinếu cần ...

phù hợp với tính năng phím đơn

Trong bash, chúng ta có thể chỉ định độ dài của đầu vào dự định cho readlệnh:

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

Trong bash, readlệnh chấp nhận một tham số thời gian chờ , có thể hữu ích.

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. Một số thủ thuật cho các công cụ chuyên dụng

Hộp thoại tinh vi hơn, ngoài yes - nomục đích đơn giản :

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

Thanh tiến trình:

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
) 

Bản demo nhỏ:

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

Thêm mẫu? Hãy xem Sử dụng whiptail để chọn thiết bị USBbộ chọn lưu trữ di động USB: USBKeyChooser

5. Sử dụng lịch sử của readline

Thí dụ:

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

Điều này sẽ tạo ra một tập tin .myscript.historytrong bạn $HOMEthư mục, hơn bạn có thể sử dụng lệnh lịch sử readline, như Up, Down, Ctrl+ rvà những người khác.


4
Lưu ý rằng sttycung cấp -gtùy chọn để sử dụng : old_stty=$(stty -g); stty raw -echo; …; stty "$old_stty". Điều này khôi phục cài đặt chính xác như chúng đã được tìm thấy, có thể giống hoặc không stty -sane.
Jonathan Leffler

3
read answersẽ giải thích dấu gạch chéo ngược trước khoảng trắng và nguồn cấp dữ liệu, và nếu không thì loại bỏ chúng mà hiếm khi được dự định. Sử dụng read -r answerthay thế theo SC2162 .
vlfig

1
Phương pháp "Sử dụng lịch sử của readline" rất không phù hợp với câu hỏi của OP. Tôi rất vui vì bạn đã bao gồm nó. Tôi có hàng tá các kịch bản phức tạp hơn nhiều mà tôi dự định cập nhật với mẫu đó!
Bruno Bronosky

5
Bạn có thể sử dụng casecho POSIX cũng như bash (sử dụng điều kiện ký tự đại diện thay vì chuỗi con bash case $answer in; [Yy]* ) echo Yes ;;:), nhưng tôi thích sử dụng câu lệnh có điều kiện hơn, ưu tiên cho [ "$answer" != "${answer#[Yy]}" ]bạn echo "$answer" | grep -iq ^y. Nó dễ mang theo hơn (một số greps không phải GNU không thực hiện -qchính xác) và nó không có lệnh gọi hệ thống. ${answer#[Yy]}sử dụng mở rộng tham số để loại bỏ Yhoặc ytừ đầu $answer, gây ra sự bất bình đẳng khi có mặt. Điều này hoạt động trong bất kỳ shell POSIX (dash, ksh, bash, zsh, busybox, v.v.).
Adam Katz

1
@CarterPape Vâng, đây là một trò đùa! Nhưng trong câu trả lời khó hiểu này, bạn có thể tìm thấy rất nhiều lời khuyên (điểm thứ hai trình bày ít nhất 3 phương pháp khác nhau)! Và ... từ khoảng 5 năm nay, bạn là người đầu tiên nói về phương pháp đếm của tôi! ;-))
F. Hauri

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

25
Tôi không đồng ý, vì nó chỉ thực hiện một phần chức năng của hộp thoại 'Có, Không, Hủy' trong DOS. Phần không thực hiện được là kiểm tra đầu vào ... lặp cho đến khi nhận được câu trả lời hợp lệ.
Myrddin Emrys

1
(Tiêu đề câu hỏi ban đầu là "Làm cách nào để tôi nhắc nhập liệu trong tập lệnh shell Linux?")
Pistos

8
Nhưng mô tả câu hỏi ban đầu không thay đổi và luôn được yêu cầu trả lời lời nhắc Có / Không / Hủy. Tiêu đề đã được cập nhật để rõ ràng hơn so với tiêu đề ban đầu của tôi, nhưng mô tả câu hỏi luôn rõ ràng (theo ý kiến ​​của tôi).
Myrddin Emrys

165

Bạn có thể sử dụng lệnh đọc tích hợp ; Sử dụng -ptùy chọn để nhắc người dùng bằng một câu hỏi.

Kể từ BASH4, bây giờ bạn có thể sử dụng -iđể đề xuất câu trả lời:

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

(Nhưng hãy nhớ sử dụng tùy chọn "readline" -eđể cho phép chỉnh sửa dòng bằng các phím mũi tên)

Nếu bạn muốn logic "có / không", bạn có thể làm một cái gì đó như thế này:

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

[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/

6
Cần lưu ý rằng đó FILEPATHlà tên biến bạn đã chọn và được đặt với câu trả lời cho dấu nhắc lệnh. Vì vậy, nếu bạn đã chạy vlc "$FILEPATH", ví dụ, vlcsẽ mở tệp đó.
Ken Sharp

Lợi ích của -eví dụ thứ hai (đơn giản có / không) là gì?
JBallin

Bất kỳ lý do để sử dụng -e -pthay vì -ep?
JBallin

1
Nếu không có -ecờ / tùy chọn, bạn có thể (tùy thuộc vào việc triển khai) không thể nhập "y", sau đó thay đổi ý định và thay thế bằng "n" (hoặc bất cứ điều gì khác cho vấn đề đó); Khi ghi lại một lệnh, liệt kê các tùy chọn riêng biệt sẽ tốt hơn cho khả năng đọc / rõ ràng, trong số các lý do khác.
yPhil

109

Bash đã chọn cho mục đích này.

select result in Yes No Cancel
do
    echo $result
done

18
+1 Giải pháp đơn giản. Điều duy nhất: Điều này sẽ nhắc và quảng cáo và nhắc nhở ... cho đến khi bạn thêm một exitbên trong :)
kaiser

5
(kaiser: Để phá vỡ từ nó, chỉ cần nhập EOT: Ctrl-DNhưng tất nhiên, mã thực sử dụng nó sẽ cần nghỉ ngơi hay một lối ra trong cơ thể..)
Zorawar

12
Tuy nhiên, điều này sẽ không cho phép bạn nhập y hoặc n. Bạn chọn bằng cách nhập 1 2 hoặc 3.
djjeck

3
exitsẽ cùng nhau thoát khỏi tập lệnh, breaksẽ chỉ thoát khỏi vòng lặp mà bạn đang ở (nếu bạn đang ở trên một vòng lặp whilehoặc 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

36

Đây là thứ tôi ghép lại:

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

Tôi là người mới bắt đầu, vì vậy hãy dùng nó với một hạt muối, nhưng nó có vẻ hiệu quả.


9
Nếu bạn thay đổi case $yn inthành case ${yn:-$2} inthì bạn có thể sử dụng đối số thứ hai làm giá trị mặc định, Y hoặc N.
jchook

1
hoặc thay đổi case $ynđể case "${yn:-Y}"có có như mặc định
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
Đặt bốn khoảng trắng ở đầu mỗi dòng để giữ nguyên định dạng của mã.
Jouni K. Seppänen

10
Tại sao chúng tôi cung cấp 'y' và 'n' làm tham số để hỏi () nếu trường hợp chuyển đổi được mã hóa cứng? Đó chỉ là yêu cầu sử dụng sai. Chúng là các tham số cố định, không thể thay đổi, vì vậy tiếng vang trên dòng 2 nên đọc: echo -n "$ 1 [Y / N]?" Chúng không thể thay đổi, vì vậy chúng không nên được cung cấp.
Myrddin Emrys

1
@MyrddinEmrys Bạn có thể vui lòng giải thích ý kiến ​​của bạn? Hoặc đăng một liên kết đến một bài viết hoặc một vài từ khóa để tôi có thể tự nghiên cứu.
Mateusz Piotrowski

4
@MateuszPiotrowski Câu trả lời đã được chỉnh sửa và cải thiện kể từ khi tôi đưa ra nhận xét của mình. Bạn có thể nhấp vào liên kết 'ngày 23 tháng 12 đã chỉnh sửa ở trên để xem tất cả các phiên bản trước của câu trả lời này. Trở lại năm 2008, mã này khá khác biệt.
Myrddin Emrys

27

Bạn muốn:

  • Các lệnh dựng sẵn của Bash (tức là xách tay)
  • Kiểm tra TTY
  • Câu trả lời mặc định
  • Hết giờ
  • Câu hỏi màu

Đoạn trích

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

Giải thích

  • [[ -t 0 ]] && read ...=> Gọi lệnh readnếu TTY
  • read -n 1 => Đợi một ký tự
  • $'\e[1;32m ... \e[0m '=> In bằng màu xanh lá cây
    (màu xanh lá cây là tốt vì có thể đọc được trên cả nền trắng / đen)
  • [[ $do_xxxx =~ ^(y|Y|)$ ]] => bash regex

Hết giờ => Câu trả lời mặc định là Không

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

Cách dễ nhất để đạt được điều này với số lượng dòng ít nhất là như sau:

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

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

Đây ifchỉ là một ví dụ: tùy thuộc vào bạn cách xử lý biến này.


20

Sử dụng readlệnh:

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

và sau đó tất cả những thứ khác bạn cần


17

Giải pháp này đọc một ký tự đơn và gọi một hàm trên phản hồi có.

read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    do_something      
fi

2
@Jav tiếng vang in một dòng mới sau phản hồi của bạn. Không có nó, điều tiếp theo được in sẽ xuất hiện ngay sau phản hồi của bạn trên cùng một dòng. Hãy thử loại bỏ echođể xem cho chính mình.
Dennis

13

Để có được một hộp đầu vào giống như ncurses, hãy sử dụng hộp thoại lệnh như thế này:

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

Gói hộp thoại được cài đặt theo mặc định ít nhất là với SUSE Linux. Giống như: lệnh "hộp thoại" đang hoạt động


12
read -e -p "Enter your choice: " choice

Các -etùy chọn cho phép người dùng chỉnh sửa các đầu vào sử dụng các phím mũi tên.

Nếu bạn muốn sử dụng một đề xuất làm đầu vào:

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

-i tùy chọn in một đầu vào gợi ý.


yap, -e -iđừng làm việc trong sh (vỏ Bourne), nhưng câu hỏi được gắn thẻ bash cụ thể ..
Jahid

12

Bạn có thể sử dụng mặc định REPLYtrên a read, chuyển đổi thành chữ thường và so sánh với một tập hợp các biến có biểu thức.
Kịch bản cũng hỗ trợ 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

Có thể xử lý "Có / Không có lựa chọn" trong miền POSIX; bằng cách sử dụng các mục của LC_MESSAGESdanh mục miền địa phương, witch cung cấp các mẫu RegEx được tạo sẵn để khớp với đầu vào và chuỗi cho địa phương Có Không.

#!/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: Như @Urhixidur đã đề cập trong bình luận của mình :

Thật không may, POSIX chỉ chỉ định hai cái đầu tiên (yesexpr và noexpr). Trên Ubuntu 16, yesstr và nostr đều trống.

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

LC_MESSAGES

Các từ khóa yesstrnostrngôn ngữ địa phương và các mục YESSTRNOSTRlanginfo trước đây được sử dụng để khớp với các phản hồi khẳng định và phủ định của người dùng. Trong POSIX.1-2008, các yesexpr, noexpr, YESEXPR, và NOEXPRbiểu thức thông thường mở rộng đã thay thế chúng. Các ứng dụng nên sử dụng các phương tiện nhắn tin dựa trên miền địa phương để phát hành các tin nhắn nhắc nhở bao gồm các phản hồi mong muốn mẫu.

Hoặc sử dụng ngôn ngữ theo cách 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

Câu trả lời được kết hợp bằng cách sử dụng biểu thức chính được cung cấp của môi trường miền địa phương.

Để dịch các tin nhắn còn lại, sử dụng bash --dump-po-strings scriptnameđể xuất chuỗi po để bản địa hóa:

#: 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 ""

Tôi thích việc bổ sung một tùy chọn bất khả tri ngôn ngữ. Làm tốt.
Myrddin Emrys

1
Thật không may, POSIX chỉ chỉ định hai cái đầu tiên (yesexpr và noexpr). Trên Ubuntu 16, yesstr và nostr đều trống.
Urhixidur

1
Nhưng chờ đã! Có tin xấu hơn! Các biểu thức câu lệnh bash không thường xuyên, chúng là các biểu thức tên tệp. Vì vậy, yesexpr và noexpr của Ubuntu 16 ("^ [yY]. *" Và "^ [nN]. *", Tương ứng) sẽ thất bại hoàn toàn vì thời gian nhúng. Trong cụm từ thông dụng, ". *" Có nghĩa là "bất kỳ ký tự không phải dòng mới nào, không hoặc nhiều lần hơn". Nhưng trong một tuyên bố trường hợp, đó là một chữ "." theo sau là bất kỳ số lượng ký tự.
Urhixidur

1
Cuối cùng, điều tốt nhất có thể được thực hiện với yesexprnoexprtrong môi trường shell, là sử dụng nó trong kết hợp RegEx cụ thể của Bashif [[ "$yn" =~ $yesexpr ]]; then echo $"Answered yes"; else echo $"Answered no"; fi
Léa Gris

10

Nhấn phím duy nhất

Đây là một cách tiếp cận dài hơn, nhưng có thể tái sử dụng và mô-đun:

  • Trả về 0= có và 1= không
  • Không cần nhấn enter - chỉ một ký tự
  • Có thể nhấn enterđể chấp nhận lựa chọn mặc định
  • Có thể vô hiệu hóa lựa chọn mặc định để buộc lựa chọn
  • Hoạt động cho cả zshbash.

Mặc định là "không" khi nhấn enter

Lưu ý rằng đó Nlà thủ đô. Ở đây nhập được nhấn, chấp nhận mặc định:

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

Cũng lưu ý, điều đó [y/N]?đã được tự động thêm vào. "Không" mặc định được chấp nhận, vì vậy không có gì được lặp lại.

Nhắc lại cho đến khi phản hồi hợp lệ được đưa ra:

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

Mặc định là "có" khi nhấn enter

Lưu ý rằng Ychữ viết hoa:

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

Ở trên, tôi chỉ nhấn enter, nên lệnh chạy.

Không có mặc định trên enter- yêu cầu yhoặcn

$ 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

Ở đây, 1hoặc sai đã được trả lại. Lưu ý rằng với chức năng cấp thấp hơn này, bạn sẽ cần cung cấp [y/n]?lời nhắc của riêng mình .

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

Khi tôi thử nghiệm kịch bản này, thay vì phần bên ngoài tôi nhận được, Show dangerous command [y/N]? [y/n]?Show dangerous command [Y/n]? [y/n]?
Ilias Karim

Cảm ơn @IliasKarim, tôi đã sửa nó ngay bây giờ.
Tom Hale

9

Xin lỗi vì đã đăng trên một bài viết cũ. Vài tuần trước tôi đã phải đối mặt với một vấn đề tương tự, trong trường hợp của tôi, tôi cần một giải pháp cũng hoạt động trong tập lệnh cài đặt trực tuyến, ví dụ:curl -Ss https://raw.github.com/_____/installer.sh | bash

Sử dụng read yesno < /dev/ttyhoạt động tốt cho tôi:

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

Hy vọng điều này sẽ giúp được ai đó.


Một phần quan trọng của điều này là xác nhận đầu vào. Tôi nghĩ rằng việc điều chỉnh ví dụ đầu tiên của tôi để chấp nhận ttyđầu vào như bạn đã làm cũng như đối với bạn và cũng bị lặp lại với đầu vào xấu (hãy tưởng tượng một vài ký tự trong bộ đệm; phương pháp của bạn sẽ buộc người dùng luôn chọn không).
Myrddin Emrys

7

Tôi nhận thấy rằng không ai đăng một câu trả lời hiển thị menu echo nhiều dòng cho đầu vào người dùng đơn giản như vậy, vì vậy đây là cách của tôi:

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

Phương pháp này đã được đăng với hy vọng ai đó có thể thấy nó hữu ích và tiết kiệm thời gian.


4

Phiên bản nhiều lựa chọn:

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
}

Thí dụ:

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

Nó sẽ được đặt REPLYthành y(bên trong tập lệnh).


4

Tôi đề nghị bạn sử dụng hộp thoại ...

Học viên Linux: Cải thiện tập lệnh Bash Shell bằng hộp thoại

Lệnh hộp thoại cho phép sử dụng các hộp cửa sổ trong các kịch bản shell để sử dụng chúng tương tác nhiều hơn.

nó đơn giản và dễ sử dụng, cũng có một phiên bản gnome gọi là gdialog có cùng tham số chính xác, nhưng hiển thị kiểu GUI trên X.


Đối với người đọc bình thường, hãy xem đoạn trích bằng lệnh hộp thoại tại đây: stackoverflow.com/a/22893526/363573
Stephan

4

Lấy cảm hứng từ câu trả lời của @Mark và @Myrddin Tôi đã tạo chức năng này cho một dấu nhắc phổ quát

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
}

sử dụng nó như thế này:

unipromtp "Select an option: (a)-Do one (x)->Do two (f)->Do three : " "a x f" selection
echo "$selection"

4

chung chung hơn sẽ là:

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

Một cách đơn giản để làm điều này là với xargs -phoặc gnu parallel --interactive.

Tôi thích hành vi của xargs tốt hơn một chút vì điều này thực thi từng lệnh ngay sau dấu nhắc như các lệnh unix tương tác khác, thay vì thu thập các yess để chạy ở cuối. (Bạn có thể Ctrl-C sau khi bạn vượt qua những cái bạn muốn.)

ví dụ,

echo *.xml | xargs -p -n 1 -J {} mv {} backup/

Không tệ, nhưng xargs --interactivebị giới hạn là có hoặc không. Miễn là đó là tất cả những gì bạn cần có thể là đủ, nhưng câu hỏi ban đầu của tôi đã đưa ra một ví dụ với ba kết quả có thể. Tôi thực sự thích rằng nó có thể truyền phát được; nhiều kịch bản phổ biến sẽ được hưởng lợi từ khả năng của nó được dẫn.
Myrddin Emrys

Tôi hiểu rồi. Suy nghĩ của tôi là "hủy bỏ" có nghĩa là chỉ đơn giản là dừng tất cả các thực thi tiếp theo, điều này hỗ trợ thông qua Ctrl-C, nhưng nếu bạn cần thực hiện một thao tác phức tạp hơn khi hủy (hoặc không) thì điều này sẽ không đủ.
Joshua Goldberg

3

Là bạn của lệnh một dòng, tôi đã sử dụng như sau:

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

Viết dạng dài, nó hoạt động như thế này:

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

Bạn có thể làm rõ việc sử dụng biến kịp thời? Có vẻ như tôi đã bị xóa sạch sau một lớp lót, vậy làm thế nào để bạn sử dụng dòng này để làm gì?
Myrddin Emrys

dấu nhắc được xóa sau vòng lặp while. Bởi vì tôi muốn biến nhắc được khởi tạo sau đó (vì tôi đang sử dụng câu lệnh thường xuyên hơn). Có dòng này trong shell-script sẽ chỉ tiến hành nếu y | Y được gõ và thoát nếu n | N được gõ hoặc lặp lại yêu cầu nhập liệu cho mọi thứ khác.
ccDict

3

Tôi đã sử dụng casecâu lệnh một vài lần trong một kịch bản như vậy, sử dụng trường hợp thống kê là một cách tốt để thực hiện nó. Một whilevòng lặp, bao trùm casekhối, sử dụng điều kiện boolean có thể được thực hiện để giữ quyền kiểm soát chương trình nhiều hơn và đáp ứng nhiều yêu cầu khác. Sau khi tất cả các điều kiện đã được đáp ứng, một breakcó thể được sử dụng sẽ chuyển điều khiển trở lại phần chính của chương trình. Ngoài ra, để đáp ứng các điều kiện khác, tất nhiên các câu lệnh có điều kiện có thể được thêm vào để đi kèm với các cấu trúc điều khiển: casecâu lệnh và whilevòng lặp có thể .

Ví dụ về việc sử dụng một casetuyên bố để thực hiện yêu cầu của bạn

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

Có / Không / Hủy

Chức năng

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

Sử dụng

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

Xác nhận với đầu vào người dùng sạch

Chức năng

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

Sử dụng

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

Điều này có vẻ phức tạp và mong manh. Làm thế nào về chỉyn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
tripleee

2

Đáp lại những người khác:

Bạn không cần chỉ định trường hợp trong BASH4, chỉ cần sử dụng ',,' để tạo chữ thường var. Ngoài ra, tôi không thích đặt mã bên trong khối đọc, nhận kết quả và xử lý nó bên ngoài khối đọc IMO. Đồng thời bao gồm 'q' để thoát IMO. Cuối cùng, tại sao gõ 'có' chỉ cần sử dụng -n1 và nhấn y.

Ví dụ: người dùng có thể nhấn y / n và cả q để thoát.

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

Hầu hết các trường hợp trong các tình huống như vậy, bạn cần tiếp tục thực thi tập lệnh cho đến khi người dùng tiếp tục nhập "có" và chỉ cần dừng khi người dùng nhập "không". Đoạn trích dưới đây sẽ giúp bạn đạt được điều này!

#!/bin/bash
input="yes"
while [ "$input" == "yes" ]
do
  echo "execute script functionality here..!!"
  echo "Do you want to continue (yes/no)?"
  read input
done
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.