Как я могу создать меню выбора в сценарии оболочки?

134

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

$./script

echo "Choose your option:"

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

И по выбору пользователя я хочу, чтобы выполнялись разные действия. Я noob, пишущий для bash, я искал ответы в Интернете, но ничего конкретного не получил.

Даниэль Родригес
источник
1
Вопрос старый и защищенный, но я использую fzf. Попробуй seq 10 | fzf. Недостатком является то, что fzf не установлен по умолчанию. Вы можете найти fzf здесь: github.com/junegunn/fzf
Lynch

Ответы:

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 гласит: «Команды выполняются после каждого выбора до тех пор, пока не будет выполнена команда останова, после чего команда выбора завершится».
Деннис Уильямсон
FWIW, работает это на 14.04 с GNU Баш 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
@ Кристиан: Нет, не должен (но мог бы, если бы вместо значений использовались индексы $optionsв caseвыражении). Я думаю, что использование значений лучше документирует функциональность разделов caseзаявления.
Деннис Уильямсон
56

Не новый ответ сам по себе , но так как нет ответа еще принято, вот несколько кодирования советы и рекомендации, как для выбора и Zenity:

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

Стоит отметить:

  • Оба будут зациклены, пока пользователь явно не выберет Выход (или Отмена для zenity). Это хороший подход для интерактивных меню сценариев: после выбора и выполнения действия снова открывается меню для другого выбора. Если выбор должен быть одноразовым, используйте только breakпосле esac(подход zenity может быть дополнительно сокращен)

  • Оба caseоснованы на индексе, а не на стоимости. Я думаю, что это легче кодировать и поддерживать

  • Массив также используется для zenityподхода.

  • Опция «Выйти» не входит в число первоначальных, оригинальных опций. Он добавляется при необходимости, поэтому ваш массив остается чистым. В конце концов, «Выход» не нужен для zenity, пользователь может просто нажать «Отмена» (или закрыть окно), чтобы выйти. Обратите внимание, как оба используют один и тот же нетронутый массив параметров.

  • PS3и REPLYvars не может быть переименован. selectжестко запрограммирован, чтобы использовать их. Все остальные переменные в скрипте (opt, options, prompt, title) могут иметь любые имена, которые вы хотите, при условии, что вы делаете корректировки

MestreLion
источник
Потрясающее объяснение. Спасибо. Этот вопрос по-прежнему занимает высокое место в Google, так что очень плохо, что он закрыт.
MountainX
Вы можете использовать ту же caseструктуру для selectверсии , которую вы используете для zenityверсии: case "$opt" in . . . "${options[0]}" ) . . .(вместо $REPLYи индексов 1, 2и 3).
Деннис Уильямсон
@ DennisWilliamson, да, я мог бы, и в «реальном» коде было бы предпочтительно использовать одну и ту же структуру в обоих случаях. Я намеренно хотел показать связь между $REPLYиндексами и значениями.
MestreLion
55

Используя dialog, команда будет выглядеть так:

dialog --clear --backtitle "Backtitle here" --title "Заголовок здесь" --menu "Выберите один из следующих параметров:" 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
Это название синего экрана на заднем плане. Вы можете увидеть его в левом верхнем углу экрана, на котором написано «Backtitle here».
Алаа Али
14

Вы можете использовать этот простой скрипт для создания опций

#! / Bin / Баш
echo "выберите операцию ************"
эхо "1) операция 1"
эхо "2) операция 2"
эхо "3) операция 3"
эхо "4) операция 4" 
читай дело $ n в 1) эхо "Вы выбрали вариант 1" ;; 2) эхо "Вы выбрали вариант 2" ;; 3) эхо "Вы выбрали вариант 3" ;; 4) эхо "Вы выбрали вариант 4" ;; *) echo "неверная опция" ;; ESAC

jibin
источник
8

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

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
HarlemSquirrel
источник
7

Так как это нацелено на Ubuntu, вы должны использовать любой бэкэнд debconf, настроенный для использования. Вы можете узнать бэкэнд debconf с помощью:

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

Если он говорит «диалог», то, скорее всего, использует whiptailили dialog. На Люсиде это whiptail.

Если это не поможет, используйте bash «select», как объяснил Деннис Уильямсон.

Ли Ло
источник
3
Это, вероятно, излишне для этого вопроса, но +1 для упоминания хлыстовика и диалога! Я не знал об этих командах ... очень мило!
МестреЛион
7
#! / Bin / ш
show_menu () {
    normal = `echo" \ 033 [m "`
    menu = `echo" \ 033 [36m "` #Blue
    number = `echo" \ 033 [33m "` #yellow
    bgred = `echo" \ 033 [41m "`
    fgred = `echo" \ 033 [31m "`
    printf "\ n $ {menu} ****************************************** *** $ {нормальный} \ п»
    printf "$ {menu} ** $ {number} 1) $ {menu} Смонтировать Dropbox $ {normal} \ n"
    printf "$ {menu} ** $ {number} 2) $ {menu} Монтирование USB 500 Gig Drive $ {normal} \ n"
    printf "$ {menu} ** $ {number} 3) $ {menu} Перезапустите Apache $ {normal} \ n"
    printf "$ {menu} ** $ {number} 4) $ {menu} ssh Frost TomCat Server $ {normal} \ n"
    printf "$ {menu} ** $ {number} 5) $ {menu} Некоторые другие команды $ {normal} \ n"
    printf "$ {menu} ******************************************** * $ {нормальный} \ п»
    printf "Пожалуйста, введите опцию меню и введите или $ {fgred} x для выхода. $ {normal}"
    читать опт
}

option_picked () {
    msgcolor = `echo" \ 033 [01; 31m "` # жирный красный
    normal = `echo" \ 033 [00; 00m "` # normal white
    message = $ {@: - "Ошибка $ {normal}: сообщение не передано"}
    printf "$ {msgcolor} $ {message} $ {normal} \ n"
}

Чисто
show_menu
пока [$ opt! = '']
    делать
    if [$ opt = '']; тогда
      Выход;
    еще
      case $ opt in
        1) очистить;
            option_picked "Выбранный вариант 1";
            printf "sudo mount / dev / sdh1 / mnt / DropBox /; # 3 терабайта";
            show_menu;
        ;;
        2) очистить;
            option_picked "Option 2 Picked";
            printf "sudo mount / dev / sdi1 / mnt / usbDrive; # диск на 500 гигов";
            show_menu;
        ;;
        3) очистить;
            option_picked "Выбранный вариант 3";
            printf "sudo service apache2 restart";
            show_menu;
        ;;
        4) очистить;
            option_picked "Выбранный вариант 4";
            printf "ssh lmesser @ -p 2010";
            show_menu;
        ;;
        х) выход;
        ;;
        \ П) выход;
        ;;
        *)Чисто;
            option_picked «Выбрать опцию из меню»;
            show_menu;
        ;;
      ESAC
    фи
сделанный
Алекс Лукард
источник
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
LazyEchidna
источник
К сожалению! извините за появление этого фрагмента, первое время публикации и, возможно, придется отключить HTML, возможно
LazyEchidna
Лучше включил «пример кода» при редактировании, извините за это
LazyEchidna
5

Bash необычное меню

Попробуйте сначала, а затем посетите мою страницу для подробного описания ... Нет необходимости во внешних библиотеках или программах, таких как диалог или zenity ...

#/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
user360154
источник
1
Это работает только с Bash, круто.
Лейтон Эверсон
2
отлично! "тогда посетите мою страницу для подробного описания" есть ли ссылка?
дуб
2

Там уже тот же вопрос в ответе сервера . Решение там использует хлыст .

txwikinger
источник
Спасибо, но так как мой скрипт предназначен для массового потребления, я не хочу, чтобы у него были какие-либо дополнительные зависимости. Но я отмечу это для использования в будущем, кто знает.
Даниэль Родригес
-1

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

Жоау Пинту
источник
1
Как и во многих других фрагментах из расширенного руководства по написанию сценариев, эти фрагменты содержат ошибки и плохую практику.
Гейра