Як я можу створити вибране меню в сценарії оболонки?


134

Я створюю простий скрипт bash, і я хочу створити в ньому вибране меню, наприклад:

$./script

echo "Choose your option:"

1) Option 1  
2) Option 2  
3) Option 3  
4) Quit  

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


1
Питання старе і захищене, але я використовую fzf. Спробуйте seq 10 | fzf. Недолік - fzf не встановлений за замовчуванням. Ви можете знайти fzf тут: github.com/junegunn/fzf
Лінч

Відповіді:


152
#!/bin/bash
# Bash Menu Script Example

PS3='Please enter your choice: '
options=("Option 1" "Option 2" "Option 3" "Quit")
select opt in "${options[@]}"
do
    case $opt in
        "Option 1")
            echo "you chose choice 1"
            ;;
        "Option 2")
            echo "you chose choice 2"
            ;;
        "Option 3")
            echo "you chose choice $REPLY which is $opt"
            ;;
        "Quit")
            break
            ;;
        *) echo "invalid option $REPLY";;
    esac
done

Додавайте breakзаяви, куди вам потрібно selectцикл для виходу. Якщо a breakне виконується, selectоператор завершується циклом і знову відображається меню.

У третьому варіанті я включив змінні, які встановлюються selectоператором, щоб продемонструвати, що у вас є доступ до цих значень. Якщо ви виберете його, воно виведе:

you chose choice 3 which is Option 3

Ви можете бачити, що $REPLYмістить рядок, яку ви ввели у запиті. Він використовується як індекс в масиві, ${options[@]}як якщо б масив був на основі 1. Змінна $optмістить рядок із цього індексу в масиві.

Зауважте, що вибір може бути простим списком безпосередньо у selectвиписці, як це:

select opt in foo bar baz 'multi word choice'

але ви не можете помістити такий список у скалярну змінну через пробіли в одному з варіантів.

Ви також можете використовувати глобулювання файлів, якщо ви вибираєте серед файлів:

select file in *.tar.gz

@Abdull: Це призначена поведінка. Параметр "Вийти" виконує функцію, breakяка виривається з selectциклу. Ви можете додати його breakкуди завгодно. У посібнику Bash зазначено: "Команди виконуються після кожного вибору до тих пір, поки не буде виконана команда break, після чого команда select буде завершена."
Денніс Вільямсон

FWIW, виконуючи це 14.04 з GNU bash 4.3.11, видає помилки в рядку 9: ./test.sh: line 9: syntax error near unexpected token "Варіант 1" './test.sh: рядок 9: `" Варіант 1 ")' '
Брайан Мортон

@BrianMorton: Я не можу це відтворити. Ймовірно, ви пропустили цитату чи якогось іншого символу раніше в сценарії (або маєте додатковий).
Денніс Вільямсон

2
@dtmland: PS3підказка для selectкоманди. Він використовується автоматично і не потребує прямого посилання. PS3та selectдокументація.
Денніс Вільямсон

1
@Christian: Ні, це не повинно (але це могло б, якщо я використовую індекси $optionsу caseвиписці замість значень). Я думаю, що використання значень краще документує функціональність розділів caseзаяви.
Денніс Вільямсон

56

Не нова відповідь сама по собі , але оскільки ще немає прийнятої відповіді, ось декілька порад та рекомендацій щодо кодування як для вибору, так і для рівня:

title="Select example"
prompt="Pick an option:"
options=("A" "B" "C")

echo "$title"
PS3="$prompt "
select opt in "${options[@]}" "Quit"; 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 )) ) echo "Goodbye!"; break;;
    *) echo "Invalid option. Try another one.";continue;;

    esac

done

while opt=$(zenity --title="$title" --text="$prompt" --list \
                   --column="Options" "${options[@]}"); do

    case "$opt" in
    "${options[0]}" ) zenity --info --text="You picked $opt, option 1";;
    "${options[1]}" ) zenity --info --text="You picked $opt, option 2";;
    "${options[2]}" ) zenity --info --text="You picked $opt, option 3";;
    *) zenity --error --text="Invalid option. Try another one.";;
    esac

done

Варто згадати:

  • Обидва будуть циклічно, поки користувач явно не вибере Quit (або Скасувати для zenity). Це хороший підхід для інтерактивних меню сценаріїв: після вибору і виконання дії, меню знову відображається для іншого вибору. Якщо вибір мається на увазі лише одноразовий, просто скористайтеся breakпісля esac(підхід zenity також може бути зменшений)

  • Обидва caseє на основі індексу, а не на основі вартості. Я думаю, що це простіше кодувати і підтримувати

  • Масив також використовується для zenityпідходу.

  • Варіант "Вийти" не є серед початкових, оригінальних варіантів. Він "додається" при необхідності, щоб ваш масив залишався чистим. Якщо все-таки "Вийти" не потрібен для бажання в будь-якому випадку, користувач може просто натиснути "Скасувати" (або закрити вікно), щоб вийти. Зверніть увагу, як обидва використовують один і той же, недоторканий масив параметрів.

  • PS3і REPLYвари можуть НЕ бути перейменовані. selectїх важко кодувати. Усі інші змінні в скрипті (опція, параметри, підказка, заголовок) можуть мати будь-які імена, які ви хочете, за умови, що ви виконаєте корективи


Дивовижне пояснення. Дякую. Це питання все ще займає високу позицію в Google, тому дуже погано його закрито.
MountainX

Ви можете використовувати ту ж caseструктуру для selectверсії , яку ви використовуєте для zenityверсії: case "$opt" in . . . "${options[0]}" ) . . .(замість $REPLYі індексів 1, 2і 3).
Денніс Вільямсон

@DennisWilliamson, так, я міг би, і в "реальному" коді було б краще використовувати однакову структуру в обох випадках. Я навмисно хотів показати зв’язок між $REPLY, показниками та значеннями.
MestreLion

55

Використовуючи dialog, команда виглядатиме так:

діалогове вікно - ясний - зворотний бік "Зворотній зв'язок тут" --заголовок "Заголовок тут" - меню "Виберіть один із наступних варіантів:" 15 40 4 \
1 "Варіант 1" \
2 "Варіант 2" \
3 "Варіант 3"

введіть тут опис зображення

Введення цього сценарію:

#!/bin/bash

HEIGHT=15
WIDTH=40
CHOICE_HEIGHT=4
BACKTITLE="Backtitle here"
TITLE="Title here"
MENU="Choose one of the following options:"

OPTIONS=(1 "Option 1"
         2 "Option 2"
         3 "Option 3")

CHOICE=$(dialog --clear \
                --backtitle "$BACKTITLE" \
                --title "$TITLE" \
                --menu "$MENU" \
                $HEIGHT $WIDTH $CHOICE_HEIGHT \
                "${OPTIONS[@]}" \
                2>&1 >/dev/tty)

clear
case $CHOICE in
        1)
            echo "You chose Option 1"
            ;;
        2)
            echo "You chose Option 2"
            ;;
        3)
            echo "You chose Option 3"
            ;;
esac

Я хотів би відзначити , що я поставив лінію TERMINAL=$(tty)у верхній частині мого сценарію , а потім в CHOICEвизначенні змінного я змінив , 2>&1 >/dev/ttyщоб 2>&1 >$TERMINALуникнути проблем з перенаправленням , якщо сценарій був запущений в іншому терміналі контексту.
Shrout1

1
Що робить --backtitleпараметр?
TRiG

2
Це назва bluescreen на задньому плані. Ви можете бачити це у верхньому лівому куті скріншота, на якому написано "Зворотній зв'язок тут".
Алаа Алі

14

Цей простий скрипт можна використовувати для створення параметрів

#! / бін / баш
ехо "вибрати операцію ************"
відлуння "1) операція 1"
відлуння "2) операція 2"
відлуння "3) операція 3"
відлуння "4) операція 4" 
читати н випадок $ n в 1) відлуння "Ви вибрали Варіант 1" ;; 2) відлуння "Ви вибрали Варіант 2" ;; 3) відлуння "Ви вибрали Варіант 3" ;; 4) відлуння "Ви вибрали Варіант 4" ;; *) відлуння "недійсний варіант" ;; есак


8

У мене є ще один варіант, який є сумішшю цих відповідей, але те, що робить його приємним, це те, що вам потрібно лише натиснути одну клавішу, а потім сценарій продовжується завдяки -nможливості читання. У цьому прикладі ми пропонуємо вимкнути, перезавантажити або просто вийти зі скрипту, використовуючи ANSяк нашу змінну, і користувачеві потрібно лише натиснути клавішу E, R або S. Я також встановив за замовчуванням вихід, тому якщо натиснути клавішу, то сценарій вийде.

read -n 1 -p "Would you like to exit, reboot, or shutdown? (E/r/s) " ans;

case $ans in
    r|R)
        sudo reboot;;
    s|S)
        sudo poweroff;;
    *)
        exit;;
esac

7

Оскільки це орієнтоване на Ubuntu, ви повинні використовувати все те, що налаштовано для запуску debconf. Ви можете дізнатись, що для бекенда debconf можна:

sudo -s "echo get debconf/frontend | debconf-communicate"

Якщо він говорить "діалог", то він, ймовірно, використовує whiptailабо dialog. На Lucid це whiptail.

Якщо це не вдається, використовуйте bash "select", як пояснив Денніс Вільямсон.


3
Це, мабуть, надмірне значення для цього питання, але +1 для згадки про whiptail та діалог! Я не знав цих команд ... Дуже мило!
MestreLion

7
#! / бін / ш
show_menu () {
    normal = `echo" \ 033 [m "`
    menu = `echo" \ 033 [36m "` #Blue
    число = `відлуння" \ 033 [33m "` #yellow
    bgred = `ехо" \ 033 [41м "`
    fgred = `echo" \ 033 [31m "`
    printf "\ n $ {menu} ******************************************** *** $ {нормально} \ n "
    printf "$ {menu} ** $ {номер} 1) $ {menu} Змонтувати віконку $ {normal} \ n"
    printf "$ {menu} ** $ {номер} 2) $ {menu} Підключити USB 500 Gig Drive $ {звичайний} \ n"
    printf "$ {menu} ** $ {номер} 3) $ {menu} Перезапустити Apache $ {normal} \ n"
    printf "$ {menu} ** $ {номер} 4) $ {menu} ssh Frost TomCat Server $ {normal} \ n"
    printf "$ {menu} ** $ {номер} 5) $ {menu} Деякі інші команди $ {normal} \ n"
    printf "$ {menu} ********************************************** * $ {нормально} \ n "
    printf "Будь ласка, введіть опцію меню та введіть або $ {fgred} x для виходу. $ {normal}"
    читати опт
}

option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # жирний червоний
    normal = `echo" \ 033 [00; 00m "` # нормальний білий
    message = $ {@: - "$ {normal} Помилка: повідомлення не передано"}
    printf "$ {msgcolor} $ {message} $ {нормальний} \ n"
}

ясний
show_menu
поки [$ opt! = '']
    робити
    якщо [$ opt = '']; тоді
      Вхід;
    ще
      case $ opt
        1) чіткий;
            option_picked "Варіант 1 вибраний";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; # 3 терабайти";
            show_menu;
        ;;
        2) чіткий;
            option_picked "Варіант 2 Вибрано";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; # 500 gig drive";
            show_menu;
        ;;
        3) чіткий;
            option_picked "Варіант 3 вибраний";
            printf "sudo service apache2 restart";
            show_menu;
        ;;
        4) чіткий;
            option_picked "Варіант 4 Вибраний";
            printf "ssh lmesser @ -p 2010";
            show_menu;
        ;;
        х) вихід;
        ;;
        \ n) вихід;
        ;;
        *) чіткий;
            option_picked "Виберіть параметр з меню";
            show_menu;
        ;;
      есак
    фі
зроблено

2
Я знаю, що це старе, але для його компіляції потрібен перший рядок, щоб прочитати #! / Bin / bash.
JClar

Огляд коду: $відсутня від $optзмінна в whileоператорі. ifЗаява є зайвим. Невідповідні відступи. Використання menuв деяких місцях, де воно повинно бути "show_menu . show_menu", можна поставити вгорі петлі, а не повторювати в кожному case. Невідповідні відступи. Використання одномісних квадратних дужок та подвійних. Використання жорстко кодованих послідовностей ANSI замість tput. Не рекомендується використовувати назви вар з усіма кришками. FGREDслід називати bgred. Використання задників замість $(). Визначення функції повинно бути послідовним і не використовувати ...
Денніс Вільямсон

... обидві форми разом. Термінальні крапки з комою не потрібні. Деякі кольори тощо визначаються двічі. Справа з \nніколи не буде виконана. Можливо, більше.
Денніс Вільямсон

6

Я використав Zenity, який, здається, завжди є в Ubuntu, працює дуже добре і має багато можливостей. Це ескіз можливого меню:

#! /bin/bash

selection=$(zenity --list "Option 1" "Option 2" "Option 3" --column="" --text="Text above column(s)" --title="My menu")

case "$selection" in
"Option 1")zenity --info --text="Do something here for No1";;
"Option 2")zenity --info --text="Do something here for No2";;
"Option 3")zenity --info --text="Do something here for No3";;
esac

На жаль! вибачте за появу цього фрагмента, який публікує перший раз, і, здається, доведеться відключити HTML, можливо
LazyEchidna

Краще,
включивши

5

Баш фантазійне меню

Спробуйте спочатку, а потім відвідайте мою сторінку для детального опису ... Не потрібно жодних зовнішніх бібліотек чи програм, таких як діалог чи зен ...

#/bin/bash
# by oToGamez
# www.pro-toolz.net

      E='echo -e';e='echo -en';trap "R;exit" 2
    ESC=$( $e "\e")
   TPUT(){ $e "\e[${1};${2}H";}
  CLEAR(){ $e "\ec";}
  CIVIS(){ $e "\e[?25l";}
   DRAW(){ $e "\e%@\e(0";}
  WRITE(){ $e "\e(B";}
   MARK(){ $e "\e[7m";}
 UNMARK(){ $e "\e[27m";}
      R(){ CLEAR ;stty sane;$e "\ec\e[37;44m\e[J";};
   HEAD(){ DRAW
           for each in $(seq 1 13);do
           $E "   x                                          x"
           done
           WRITE;MARK;TPUT 1 5
           $E "BASH SELECTION MENU                       ";UNMARK;}
           i=0; CLEAR; CIVIS;NULL=/dev/null
   FOOT(){ MARK;TPUT 13 5
           printf "ENTER - SELECT,NEXT                       ";UNMARK;}
  ARROW(){ read -s -n3 key 2>/dev/null >&2
           if [[ $key = $ESC[A ]];then echo up;fi
           if [[ $key = $ESC[B ]];then echo dn;fi;}
     M0(){ TPUT  4 20; $e "Login info";}
     M1(){ TPUT  5 20; $e "Network";}
     M2(){ TPUT  6 20; $e "Disk";}
     M3(){ TPUT  7 20; $e "Routing";}
     M4(){ TPUT  8 20; $e "Time";}
     M5(){ TPUT  9 20; $e "ABOUT  ";}
     M6(){ TPUT 10 20; $e "EXIT   ";}
      LM=6
   MENU(){ for each in $(seq 0 $LM);do M${each};done;}
    POS(){ if [[ $cur == up ]];then ((i--));fi
           if [[ $cur == dn ]];then ((i++));fi
           if [[ $i -lt 0   ]];then i=$LM;fi
           if [[ $i -gt $LM ]];then i=0;fi;}
REFRESH(){ after=$((i+1)); before=$((i-1))
           if [[ $before -lt 0  ]];then before=$LM;fi
           if [[ $after -gt $LM ]];then after=0;fi
           if [[ $j -lt $i      ]];then UNMARK;M$before;else UNMARK;M$after;fi
           if [[ $after -eq 0 ]] || [ $before -eq $LM ];then
           UNMARK; M$before; M$after;fi;j=$i;UNMARK;M$before;M$after;}
   INIT(){ R;HEAD;FOOT;MENU;}
     SC(){ REFRESH;MARK;$S;$b;cur=`ARROW`;}
     ES(){ MARK;$e "ENTER = main menu ";$b;read;INIT;};INIT
  while [[ "$O" != " " ]]; do case $i in
        0) S=M0;SC;if [[ $cur == "" ]];then R;$e "\n$(w        )\n";ES;fi;;
        1) S=M1;SC;if [[ $cur == "" ]];then R;$e "\n$(ifconfig )\n";ES;fi;;
        2) S=M2;SC;if [[ $cur == "" ]];then R;$e "\n$(df -h    )\n";ES;fi;;
        3) S=M3;SC;if [[ $cur == "" ]];then R;$e "\n$(route -n )\n";ES;fi;;
        4) S=M4;SC;if [[ $cur == "" ]];then R;$e "\n$(date     )\n";ES;fi;;
        5) S=M5;SC;if [[ $cur == "" ]];then R;$e "\n$($e by oTo)\n";ES;fi;;
        6) S=M6;SC;if [[ $cur == "" ]];then R;exit 0;fi;;
 esac;POS;done

1
Це працює лише з bash, свого роду круто.
Лейтон Еверсон

2
приємно! "Тоді відвідайте мою сторінку для детального опису" чи є посилання?
дуб


2

У сервісному відповіді вже є те саме питання . Розчин там використовує батог .


Дякую, але оскільки мій сценарій призначений для споживання, я не хочу, щоб він мав додаткові залежності. Але я закладаю це закладкою для використання в майбутньому, хто знає.
Даніель Родрігес

-1

Припустимо, що ви хочете використовувати меню простого скрипта оболонки (не фантазійний інтерфейс користувача), перегляньте приклад меню на веб-сторінці http://www.tldp.org/LDP/abs/html/testbranch.html .


1
Як і у більшості інших фрагментів у вдосконаленому посібнику з написання сценаріїв, ці фрагменти містять помилки та погану практику.
geirha
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.