Simplify dynamic menus in Bash

I am working on a custom bash script (with which I have very little experience) and I am looking for some help with a menu function. My script had several different menus performing different functions, so I decided to create one menu function that could be named and configured with the given variables. It's unclean and ugly, but I've learned a lot. Can anyone shed some light on where I could simplify or do something differently or more cleanly? Thanks in advance!

#!/bin/bash

# set colors
red='tput setaf 1'
blue='tput setaf 4'
green='tput setaf 2'
bold='tput bold'
normal='tput sgr0'

# define function to reset screen to title
function reset {
    clear
    $bold
    $blue
    echo -e "lineage build script\n\n"
    $normal
    }

function title {
    reset
    $bold
    echo -e "$1\n"
    $normal
    }

# function to create menu
# menu "<title(required)>" "<question(required)>" <all(required)> "<option1>"...
# <title> - Page title
# <question> - Question to be posed before the menu
# <all> - Whether or not to present a menu choice for "All"
# <option#> - List of options to present to the user
function menu {
    # set input at 255 (max error level for return function later)
    input=255
    # check input to see if it acceptable (within presented range and a number)
    while [[ $input -ge $(($counter+1)) ]] || [[ $((input)) != $input ]]; do
        # a call to a previously defind function establishing consistent page feel.
        title "$1"
        echo -e "$2"
        # present warning if input was invalid
        if ! [[ $input =~ "255" ]]; then
            $red
            echo -e "Invalid entry. Please try again.\n"
            $normal
        else
            echo -e "\n"
        fi
        counter=0
        # present list items for each variable passed to function starting at position 4
        # in order to skip the first 3 (title, question, all)
        for i in "${@:4}"; do
            counter=$(($counter+1))
            echo -e "$counter) $i"
        done
        # present "All" option if variable 3 is any form of yes
        if [[ $3 =~ (y|yes|Y|YES) ]]; then $green; counter=$(($counter+1)); echo -e "$counter) All"; $normal; fi
        # present "Exit" option
        $red; counter=$(($counter+1)); echo -e "$counter) Exit\n"; $normal
        # gather input
        read -N 1 -p "[1-$counter]: " input
    done
    # bail if exit was chosen
    if [[ $input == $counter ]]; then $red; echo -e "\nExit"; exit; fi
    # pass choice back to script as return code
    return $input
    }

# call the menu function for testing
menu "Main Menu" "What would you like to do?" y "option 1" "option 2" "option 3" "option 4" "option 5"
#echo return code just to verify function
echo -e "\nYou chose $?"

      

+3


source to share


1 answer


Let's see if that helps anyway (retro). Not just shell related scripts.

  • Naming

A function reset

is a verb with title

no quality . But it could be indicating what it is doing and that it is not a variable.

  1. Printing

You used echo -e

to print consistently . As Charles Duffy pointed out in the comment below, you are sacrificing POSIX compatibility by using -e

.

It is not possible to use echo portability on all POSIX systems if both -n (as the first argument) and escape sequences are not specified.
The printf utility can be used portable to emulate any traditional echo utility behavior as follows (assuming IFS is default or not set)
http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html

Whichever actual method you use for printing: how to do it? If you need to change, this is one place. Hopefully this doesn't lead you to a large list of overloaded functions (for various print options).

  1. Local

One of the reasons I liked your program is because you have stored the variables where they belong locally to functions. Perhaps you can do this by actually labeling (where it makes sense) their local.



local [option] name[=value]

      

https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html

  1. Shift

To continue with the previous point, I usually prefer to store / move the ordered parameters into local variables. For example title_text=$1

. In function, menu

this will allow you to use shift

. You will process the options "title", "menu name" and "all", after which you will only get a list of options in the variable $@

. List of option names. It is very convenient if you later want to refactor some parts of the function (without breaking the order) or if you want to pass the specified list to another function for working with parameters.

shift [n]

      

Move the positional parameters to the left by n. Positional parameters from n + 1 ... $ # are renamed to $ 1 ... $ # - n. parameters represented by digits $ # to $ # - n + 1 are not specified. n must be a non-negative number less than or equal to $ #. If n is zero or greater than $ #, the positional parameters are not changed. If n is not, it is assumed to be 1. The return state is zero if n is not greater than $ # or less than zero, otherwise nonzero.

https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html

  1. Exit codes

I found your use case for return values ​​(exit codes). It's a shame that the only situation where it is not being used is in the actual case where you are calling exit

. I mean, without going into the function menu

, I got a little confused as to why I never see "You selected" if I "exit".

0


source







All Articles