├── LICENSE ├── demo.sh ├── bash-draw.sh ├── README.md └── bash-menu.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kris Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ensure we are running under bash 4 | if [ "$BASH_SOURCE" = "" ]; then 5 | /bin/bash "$0" 6 | exit 0 7 | fi 8 | 9 | # 10 | # Load bash-menu script 11 | # 12 | # NOTE: Ensure this is done before using 13 | # or overriding menu functions/variables. 14 | # 15 | . "bash-menu.sh" 16 | 17 | 18 | ################################ 19 | ## Example Menu Actions 20 | ## 21 | ## They should return 1 to indicate that the menu 22 | ## should continue, or return 0 to signify the menu 23 | ## should exit. 24 | ################################ 25 | actionA() { 26 | echo "Action A" 27 | 28 | echo -n "Press enter to continue ... " 29 | read response 30 | 31 | return 1 32 | } 33 | 34 | actionB() { 35 | echo "Action B" 36 | 37 | echo -n "Press enter to continue ... " 38 | read response 39 | 40 | return 1 41 | } 42 | 43 | actionC() { 44 | echo "Action C" 45 | 46 | echo -n "Press enter to continue ... " 47 | read response 48 | 49 | return 1 50 | } 51 | 52 | actionX() { 53 | return 0 54 | } 55 | 56 | 57 | ################################ 58 | ## Setup Example Menu 59 | ################################ 60 | 61 | ## Menu Item Text 62 | ## 63 | ## It makes sense to have "Exit" as the last item, 64 | ## as pressing Esc will jump to last item (and 65 | ## pressing Esc while on last item will perform the 66 | ## associated action). 67 | ## 68 | ## NOTE: If these are not all the same width 69 | ## the menu highlight will look wonky 70 | menuItems=( 71 | "1. Item 1" 72 | "2. Item 2" 73 | "3. Item 3" 74 | "A. Item A" 75 | "B. Item B" 76 | "Q. Exit " 77 | ) 78 | 79 | ## Menu Item Actions 80 | menuActions=( 81 | actionA 82 | actionB 83 | actionC 84 | actionA 85 | actionB 86 | actionX 87 | ) 88 | 89 | ## Override some menu defaults 90 | menuTitle=" Demo of bash-menu" 91 | menuFooter=" Enter=Select, Navigate via Up/Down/First number/letter" 92 | menuWidth=60 93 | menuLeft=25 94 | menuHighlight=$DRAW_COL_YELLOW 95 | 96 | 97 | ################################ 98 | ## Run Menu 99 | ################################ 100 | menuInit 101 | menuLoop 102 | 103 | 104 | exit 0 105 | -------------------------------------------------------------------------------- /bash-draw.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Public Functions: 5 | # 6 | # drawClear() 7 | # drawColour(colour = DRAW_COL_DEF, bgColour = DRAW_COL_DEF) 8 | # 9 | # drawPlain(text, newLine = 0) 10 | # drawSpecial(text, newLine = 0) 11 | # drawHighlight(text, newLine = 0) 12 | # drawPlainAt(left, top, text, newLine = 0) 13 | # drawHighlightAt(left, top, text, newLine = 0) 14 | # 15 | # 16 | # Colours 17 | # 18 | # DRAW_COL_DEF # Default colour 19 | # DRAW_COL_BLACK 20 | # DRAW_COL_WHITE 21 | # DRAW_COL_RED 22 | # DRAW_COL_GREEN 23 | # DRAW_COL_YELLOW 24 | # DRAW_COL_BLUE 25 | # DRAW_COL_GRAY # Light gray (grey?) 26 | # 27 | 28 | 29 | # Ensure we are running under bash (will not work under sh or dash etc) 30 | if [ "$BASH_SOURCE" = "" ]; then 31 | echo "ERROR: bash-draw requires to be running under bash" 32 | exit 1 33 | fi 34 | 35 | 36 | DRAW_COL_DEF=39 37 | DRAW_COL_BLACK=30 38 | DRAW_COL_WHITE=97 39 | DRAW_COL_RED=31 40 | DRAW_COL_GREEN=32 41 | DRAW_COL_YELLOW=33 42 | DRAW_COL_BLUE=34 43 | DRAW_COL_GRAY=37 44 | 45 | 46 | # drawClear() 47 | drawClear() { 48 | $ESC_WRITE "\033c" 49 | } 50 | 51 | # drawColour(colour = DRAW_COL_DEF, bgColour = DRAW_COL_DEF) 52 | drawColour() { 53 | local colour=$DRAW_COL_DEF 54 | local bgColour=$((DRAW_COL_DEF+10)) 55 | 56 | if [[ ! -z "$1" && "$1" != "" ]]; then 57 | colour="$1" 58 | fi 59 | 60 | if [[ ! -z "$2" && "$2" != "" ]]; then 61 | bgColour="$2" 62 | fi 63 | 64 | $ESC_ECHO "\033c\033[H\033[J\033[${colour};${bgColour}m\033[J" 65 | } 66 | 67 | # drawPlain(text, newLine = 0) 68 | drawPlain() { 69 | if [[ -z "$2" || "$2" -eq 0 ]]; then 70 | $ESC_WRITE "$1" 71 | else 72 | $ESC_ECHO "$1" 73 | fi 74 | } 75 | 76 | # drawSpecial(text, newLine = 0) 77 | drawSpecial() { 78 | [[ -z "$2" ]] && newLine=0 || newLine="$2" 79 | 80 | draw_SetDrawMode 81 | drawPlain "$1" "$newLine" 82 | draw_SetWriteMode 83 | } 84 | 85 | # drawHighlight(text, newLine = 0) 86 | drawHighlight() { 87 | [[ -z "$2" ]] && newLine=0 || newLine="$2" 88 | 89 | draw_StartHighlight 90 | drawPlain "$1" "$newLine" 91 | draw_EndHighlight 92 | } 93 | 94 | # drawPlainAt(left, top, text, newLine = 0) 95 | drawPlainAt() { 96 | [[ -z "$4" ]] && newLine=0 || newLine="$4" 97 | 98 | draw_MoveTo $1 $2 99 | drawPlain "$3" "$newLine" 100 | } 101 | 102 | # drawHighlightAt(left, top, text, newLine = 0) 103 | drawHighlightAt() { 104 | [[ -z "$4" ]] && newLine=0 || newLine="$4" 105 | 106 | draw_StartHighlight 107 | drawPlainAt "$1" "$2" "$3" "$newLine" 108 | draw_EndHighlight 109 | } 110 | 111 | 112 | # Write escape sequence with no newline 113 | ESC_WRITE='echo -en' 114 | 115 | # Write escape sequence adding newline 116 | ESC_ECHO='echo -e' 117 | 118 | 119 | # Move cursor to specified location 120 | draw_MoveTo() { 121 | $ESC_WRITE "\033[${1};${2}H" 122 | } 123 | 124 | draw_StartHighlight() { 125 | $ESC_WRITE "\033[7m" 126 | } 127 | 128 | draw_EndHighlight() { 129 | $ESC_WRITE "\033[27m" 130 | } 131 | 132 | draw_SetDrawMode() { 133 | $ESC_WRITE "\033%@\033(0" 134 | } 135 | 136 | draw_SetWriteMode() { 137 | $ESC_WRITE "\033(B" 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bash Menu 2 | 3 | Bash Menu is a Bash script to easily allow you to add a menu to your own scripts. 4 | 5 | You can add an interactive menu like the following to your own scripts with just a few simple steps: 6 | 7 | ![Bash Menu](https://raw.githubusercontent.com/barbw1re/bash-menu/bash-menu-meta/bash-menu.png) 8 | 9 | 10 | ## Features 11 | 12 | The Bash Menu will clear the screen and display a menu as shown above. You can select the menu item currently highlighted by pressing Enter. You can navigate the options using the following methods: 13 | 14 | * Up and Down arrow keys will move to previous and next menu item. 15 | * Pressing Up on the top menu item will move to the last menu item (and Down on the last will move you to the first item). 16 | * Pressing Esc will jump you to the last menu item (a convenience as we assume the last item is the Exit menu item). 17 | * Pressing Esc while on the last menu item will run that action, the same as pressing Enter on it (I like to double press Esc to quickly exit). 18 | * Entering the first letter/number (case-insensitive) of a menu item will jump you to that item - if you have multiple menu items starting with the same letter, it will jump you to the first occurrence. 19 | 20 | 21 | ## Prerequisites 22 | 23 | This script, unsurprisingly based on the name, requires to be run in a Bash shell and will terminate when run by `/bin/sh` or `/bin/dash` (for example). 24 | 25 | What I like to do at the top of my scripts is to ensure this by adding the code: 26 | 27 | ``` 28 | # Ensure we are running under bash 29 | if [ "$BASH_SOURCE" = "" ]; then 30 | /bin/bash "$0" 31 | exit 0 32 | fi 33 | ``` 34 | 35 | The `bash-menu.sh` script includes (and needs) the `bash-draw.sh` script and requires it to be located in the same directory as `bash-menu.sh`. These scripts do not need to be in the same directory as your main script, however, so it is perfectly fine to source the `bash-menu.sh` script into your scripts from any location. 36 | 37 | 38 | ## Usage 39 | 40 | See the `demo.sh` script for an introduction to how to incorporate Bash Menu in your own scripts, but the quick steps would be: 41 | 42 | 43 | ### Step 1. Download the Bash Menu scripts 44 | 45 | Download `bash-menu.sh` and `bash-draw.sh` and put them somewhere (in the same directory as each other). 46 | 47 | 48 | ### Step 2. Import Bash Menu into your own script 49 | 50 | Somewhere in your own Bash script, before you want to use or configure a menu, you should import the `bash-menu.sh` script, either via (assuming the same directory as your script): 51 | 52 | ``` 53 | source bash-menu.sh 54 | ``` 55 | 56 | or simply: 57 | 58 | ``` 59 | . bash-menu.sh 60 | ``` 61 | 62 | 63 | ### Step 3. Create handlers for each menu item 64 | 65 | Create a handler function for each menu item. 66 | 67 | When the corresponding menu item is selected, the screen will be cleared and the function called. The function is provided no parameters and should return either `1` or `0`. 68 | 69 | * **1** indicates that the menu should resume 70 | * **0** indicates that the menu should quit 71 | 72 | 73 | ### Step 4. Setup menu items 74 | 75 | Two arrays need to be populated for the menu; one containing the menu item labels, and the other containing the associated handler functions as created in the previous step. 76 | 77 | ``` 78 | menuItems=( 79 | "1. Item 1" 80 | "2. Item 2" 81 | "Q. Exit " 82 | ) 83 | menuActions=( 84 | actionA 85 | actionB 86 | actionExit 87 | ) 88 | ``` 89 | 90 | **NOTE**: If the menu item labels are different lengths, the menu highlighting of the active menu item will look a bit odd, so it is best to ensure they are the same length by adding spaces to the end as needed. 91 | 92 | **NOTE**: It is your responsibility to ensure there are the same number of menu item labels and actions. 93 | 94 | 95 | ### Step 5. Override menu configuration as needed 96 | 97 | There are a few variables used by Bash Menu which you can override and affect how the menu is displayed. These can also be found at the top of `bash-menu.sh`. 98 | 99 | **Layout** 100 | 101 | * menuTop - The top row of menu (defaults to row 2, ie 1 blank line at the top). 102 | * menuLeft - The left column where the menu item label text should start (defaults to column 15). 103 | * menuWidth - The width of the menu box - not including the left margin, but including the border (defaults to 42 columns). 104 | * menuMargin - The left gap before drawing the menu border (defaults to column 4) 105 | 106 | **Colours** 107 | (See top of `bash-draw.sh` for available colours) 108 | 109 | * menuColour - The colour of menu text (defaults to $DRAW_COL_WHITE). 110 | * menuHighlight - The highlight colour for menu (defaults to $DRAW_COL_GREEN). 111 | 112 | **Labels** 113 | 114 | * menuTitle - Title of menu. 115 | * menuFooter - Text to display at bottom of menu 116 | 117 | 118 | ### Step 6. Display the Menu 119 | 120 | Now that everything is setup - displaying the menu is as simple as adding the following to your script: 121 | 122 | ``` 123 | menuInit 124 | menuLoop 125 | ``` 126 | 127 | 128 | The `menuLoop` function will continue until a menu item is selected which returns `0` - at which time the screen is cleared and control returns to the next line in your script. 129 | 130 | 131 | ## Versioning 132 | 133 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/barbw1re/bash-menu/tags). 134 | 135 | 136 | ## Authors 137 | 138 | * **Kris Johnson** - *Initial work* - [barbw1re](https://github.com/barbw1re) 139 | 140 | See also the list of [contributors](https://github.com/barbw1re/bash-menu/contributors) who participated in this project. 141 | 142 | 143 | ## License 144 | 145 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 146 | 147 | 148 | ## Acknowledgments 149 | 150 | Bash Menu was only made possible (or at least, was made with less wheel re-inventing) by the knowledge provided by this [top-scripts blog post](http://top-scripts.blogspot.com/2011/01/blog-post.html). 151 | 152 | -------------------------------------------------------------------------------- /bash-menu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Public Functions 5 | # 6 | # menuInit() 7 | # menuLoop() 8 | # 9 | # 10 | # Public Variables to Override 11 | # 12 | # Should these get passed into menuInit() rather than be set as global 13 | # script variables? 14 | # 15 | # menuTop # Top row of menu (defaults to row 2) 16 | # menuLeft # Left offset for menu item text (defaults to column 15) 17 | # menuWidth # Width of menu (defaults to 42 columns) 18 | # menuMargin # Left offset for menu border (defaults to column 4) 19 | # 20 | # menuColour # Colour of menu text (defaults to DRAW_COL_WHITE) 21 | # menuHighlight # Highlight colour for menu (defaults to DRAW_COL_GREEN) 22 | # 23 | # menuTitle # Title of menu 24 | # menuFooter # Footer text of menu 25 | # 26 | # menuItems # Array containing menu item text 27 | # menuActions # Array containing functions to call upon menu item selection 28 | # 29 | 30 | 31 | # Ensure we are running under bash (will not work under sh or dash etc) 32 | if [ "$BASH_SOURCE" = "" ]; then 33 | echo "ERROR: bash-menu requires to be running under bash" 34 | exit 1 35 | fi 36 | 37 | # Get script root (as we are sourced from another script, $0 will not be us) 38 | declare -r menuScript=$(readlink -f ${BASH_SOURCE[0]}) 39 | menuRoot=$(dirname "$menuScript") 40 | 41 | # Ensure we can access our dependencies 42 | if [ ! -s "$menuRoot/bash-draw.sh" ]; then 43 | echo "ERROR: Missing required draw.sh script" 44 | exit 1 45 | fi 46 | 47 | # Load terminal drawing functions 48 | . "$menuRoot/bash-draw.sh" 49 | 50 | 51 | ################################ 52 | # Private Variables 53 | # 54 | # These should not be overridden 55 | ################################ 56 | declare -a menuItems 57 | declare -a menuActions 58 | 59 | menuHeaderText="" 60 | menuFooterText="" 61 | menuBorderText="" 62 | 63 | 64 | ################################ 65 | # Setup Menu 66 | # 67 | # These are defaults which should 68 | # be overridden as required. 69 | ################################ 70 | 71 | # Top of menu (row 2) 72 | menuTop=2 73 | 74 | # Left offset for menu items (not border) 75 | menuLeft=15 76 | 77 | # Width of menu 78 | menuWidth=42 79 | 80 | # Left offset for menu border (not menu items) 81 | menuMargin=4 82 | 83 | menuItems[0]="Exit" 84 | menuActions[0]="return 0" 85 | 86 | menuItemCount=1 87 | menuLastItem=0 88 | 89 | menuColour=$DRAW_COL_WHITE 90 | menuHighlight=$DRAW_COL_GREEN 91 | 92 | menuTitle=" Super Bash Menu System" 93 | menuFooter=" Enter=Select, Up/Down=Prev/Next Option" 94 | 95 | 96 | ################################ 97 | # Initialise Menu 98 | ################################ 99 | menuInit() { 100 | menuItemCount=${#menuItems[@]} 101 | menuLastItem=$((menuItemCount-1)) 102 | 103 | # Ensure header and footer are padded appropriately 104 | menuHeaderText=`printf "%-${menuWidth}s" "$menuTitle"` 105 | menuFooterText=`printf "%-${menuWidth}s" "$menuFooter"` 106 | 107 | # Menu (side) borders 108 | local marginSpaces=$((menuMargin-1)) 109 | local menuSpaces=$((menuWidth-2)) 110 | local leftGap=`printf "%${marginSpaces}s" ""` 111 | local midGap=`printf "%${menuSpaces}s" ""` 112 | menuBorderText="${leftGap}x${midGap}x" 113 | } 114 | 115 | 116 | ################################ 117 | # Show Menu 118 | ################################ 119 | menu_Display() { 120 | local menuSize=$((menuItemCount+2)) 121 | local menuEnd=$((menuSize+menuTop+1)) 122 | 123 | drawClear 124 | drawColour $menuColour $menuHighlight 125 | 126 | # Menu header 127 | drawHighlightAt $menuTop $menuMargin "$menuHeaderText" 1 128 | 129 | # Menu (side) borders 130 | for row in $(seq 1 $menuSize); do 131 | drawSpecial "$menuBorderText" 1 132 | done 133 | 134 | # Menu footer 135 | drawHighlightAt $menuEnd $menuMargin "$menuFooterText" 1 136 | 137 | # Menu items 138 | for item in $(seq 0 $menuLastItem); do 139 | menu_ClearItem $item 140 | done 141 | } 142 | 143 | 144 | ################################ 145 | # Mark Menu Items 146 | ################################ 147 | 148 | # Ensure menu item is not highlighted 149 | menu_ClearItem() { 150 | local item=$1 151 | local top=$((menuTop+item+2)) 152 | local menuText=${menuItems[$item]} 153 | 154 | drawPlainAt $top $menuLeft "$menuText" 155 | } 156 | 157 | # Highlight menu item 158 | menu_HighlightItem() { 159 | local item=$1 160 | local top=$((menuTop+item+2)) 161 | local menuText=${menuItems[$item]} 162 | 163 | drawHighlightAt $top $menuLeft "$menuText" 164 | } 165 | 166 | 167 | ################################ 168 | # Wait for and process user input 169 | ################################ 170 | menu_HandleInput() { 171 | local choice=$1 172 | 173 | local after=$((choice+1)) 174 | [[ $after -gt $menuLastItem ]] && after=0 175 | 176 | local before=$((choice-1)) 177 | [[ $before -lt 0 ]] && before=$menuLastItem 178 | 179 | # Clear highlight from prev/next menu items 180 | menu_ClearItem $before 181 | menu_ClearItem $after 182 | 183 | # Highlight current menu item 184 | menu_HighlightItem $choice 185 | 186 | # Get keyboard input 187 | local key="" 188 | local extra="" 189 | 190 | read -s -n1 key 2> /dev/null >&2 191 | while read -s -n1 -t .05 extra 2> /dev/null >&2 ; do 192 | key="$key$extra" 193 | done 194 | 195 | # Handle known keys 196 | local escKey=`echo -en "\033"` 197 | local upKey=`echo -en "\033[A"` 198 | local downKey=`echo -en "\033[B"` 199 | 200 | if [[ $key = $upKey ]]; then 201 | return $before 202 | elif [[ $key = $downKey ]]; then 203 | return $after 204 | elif [[ $key = $escKey ]]; then 205 | if [[ $choice -eq $menuLastItem ]]; then 206 | # Pressing Esc while on last menu item will trigger action 207 | # This is a helper as we assume the last menu option is exit 208 | key="" 209 | else 210 | # Jumping possibly more than 1 (next/prev) item 211 | menu_ClearItem $choice 212 | return $menuLastItem 213 | fi 214 | elif [[ ${#key} -eq 1 ]]; then 215 | # See if we wanrt to jump to a menu item 216 | # by entering the first character 217 | for index in $(seq 0 $menuLastItem) ; do 218 | local item=${menuItems[$index]} 219 | local startChar=${item:0:1} 220 | if [[ "$key" = "$startChar" ]]; then 221 | # Jumping possibly more than 1 (next/prev) item 222 | menu_ClearItem $choice 223 | return $index 224 | fi 225 | done 226 | fi 227 | 228 | if [[ "$key" = "" ]]; then 229 | # Notify that Enter key was pressed 230 | return 255 231 | fi 232 | 233 | return $choice 234 | } 235 | 236 | 237 | ################################ 238 | # Main Menu Loop 239 | ################################ 240 | menuLoop() { 241 | local choice=0 242 | local running=1 243 | 244 | menu_Display 245 | 246 | while [[ $running -eq 1 ]]; do 247 | # Enable case insensitive matching 248 | local caseMatch=`shopt -p nocasematch` 249 | shopt -s nocasematch 250 | 251 | menu_HandleInput $choice 252 | local newChoice=$? 253 | 254 | # Revert to previous case matching 255 | $caseMatch 256 | 257 | if [[ $newChoice -eq 255 ]]; then 258 | # Enter pressed - run menu action 259 | drawClear 260 | action=${menuActions[$choice]} 261 | $action 262 | running=$? 263 | 264 | # Back from action 265 | # If we are still running, redraw menu 266 | [[ $running -eq 1 ]] && menu_Display 267 | 268 | elif [[ $newChoice -lt $menuItemCount ]]; then 269 | # Update selected menu item 270 | choice=$newChoice 271 | fi 272 | done 273 | 274 | # Cleanup screen 275 | drawClear 276 | } 277 | --------------------------------------------------------------------------------