roady
roady

Reputation: 597

Bash menus from external file

Don't know where to ask for help so trying here. I'm creating a bash menu script with some operations and reading lots of bash tutorials but think my brain is starting to melt with all different syntax and ways to do it, can't fully wrap my head around bash/sh. End script will run on OSX for an art team.

What this is

A script to upload/download files with rsync. The script that will grab latest 'config/menu' from remote server. That file menu.txt will create an menu that lists project and when selected you get the option to download / upload.

Issue

Where I'm stuck is how to handle arrays to menus. Tried 2d arrays with no luck so now it's split into 3 arrays to hold the values I need. However when trying to display the menus I can't get it to work correctly. Look at the bottom for how to test, what is shows and what it should show.

more info: create_menus function

This function parse the menu.txt to build an array that is used when showing a menu,

Project Title5, source1dir, destination1dir
Project Title6, source2dir, destination2dir
Project Title7, source3dir, destination3dir

Instead of gettin selection 1 'Project Title5' a menu will display

1) Project
2) Title5Project
3) Title6Project
4) Title7Quit

Script:

function create_menus() {
    #operations for project
    MENU_OPERATIONS=(
      "Get latest from remote"
      "Show changes from remote"
      "Send latest from me to remote"
      "Show changes from me to remote"
      "Return to main menu"
  )
    #projects to choose from, load from textfile
    declare -a t; declare -a s; declare -a d;
    while IFS= read -r line; do
        IFS=',' read -ra obj <<< "$line"
        #TODO 2d array nicer than 3 arrays!
        eval "t+=\"${obj[0]}\""
        eval "s+=\"${obj[1]}\""
        eval "d+=\"${obj[2]}\""
    done <$FILE_MENU
    t+="Quit" #add quit
    MENU_MAIN=($t)
    PROJECT_SOURCE=($s)
    PROJECT_TARGET=($d)
}

then to show the main menu main_menu "${MENU_MAIN[@]}"

function main_menu
{
    #clear
    #header
    PS3="Select project: "
    select option; do # in "$@" is the default
        if [ "$REPLY" -eq "$#" ];
        then
            echo "Exiting..."
            break;
        elif [ 1 -le "$REPLY" ] && [ "$REPLY" -le $(($#-1)) ];
        then
            # $REPLY = index
            # $option = text
            echo "You selected $option which is option $REPLY"
            SELETED_PROJECT_TITLE=${MENU_MAIN[$REPLY]}
            SELETED_PROJECT_SOURCE=${PROJECT_SOURCE[$REPLY]}
            SELETED_PROJECT_TARGET=${PROJECT_TARGET[$REPLY]}
            echo "Sel title $SELETED_PROJECT_TITLE"
            echo "Sel source $SELETED_PROJECT_SOURCE"
            echo "Sel target $SELETED_PROJECT_TARGET"
            project_menu "${MENU_OPERATIONS[@]}" "$SELETED_PROJECT_TITLE" "$SELETED_PROJECT_SOURCE" "$SELETED_PROJECT_TARGET"
            break;
        else
            echo "Incorrect Input: Select a number 1-$#"
        fi
    done
}

Here's full code https://github.com/fbacker/BigFileProjectsSync/blob/master/app.sh

ADDED MORE DESCRIPTION

To test:

git clone https://github.com/fbacker/BigFileProjectsSync.git
cd BigFileProjectsSync/
./app.sh

What happens:

Shows a menu with options:
1) Project
2) Title5Project
3) Title6Project
4) Title7Quit

Should happen:

Shows a menu with options:
1) Project Title5
2) Project Title6
3) Project Title7
4) Quit

app.sh > function create_menus() > This should create a menu based on the menu.txt file.

menu.txt one line is a project: first value is project name, second value is source directory and third is target directory.

Upvotes: 1

Views: 761

Answers (1)

mklement0
mklement0

Reputation: 438093

Here's a fixed version of your create_menus() function, which should do the trick:

function create_menus() {

    #operations for project
    # MENU_OPERATIONS=( # ... OMITTED FOR BREVITY

    #projects to choose from
    local -a titles sources destinations
    local title source destination
    while IFS='|' read -r title source destination; do
        titles+=( "$title" )
        sources+=( "$source" )
        destinations+=( "$destination" )
    done < <(sed 's/, /|/g' "$FILE_MENU")

    # Copy to global arrays
    MENU_MAIN+=( "${titles[@]}" )
    PROJECT_SOURCE+=( "${sources[@]}" )
    PROJECT_TARGET+=( "${destinations[@]}" )

    MENU_MAIN+=( "Quit" ) #add quit
}

There were 2 crucial problems with your approach (I'll assume an array variable $arr below):

  • In order to append a new element to an array, the new element must itself be specified as an array too; i.e., it must be enclosed in (...):

    • arr+=( "$newElement" ) - OK: value is appended as new element
    • arr+=$newElement - BROKEN: String-appends the value of $newElement to $arr's first element(!), without adding a new one.
      • arr=( 1 2 ); arr+=3; declare -p arr -> declare -a arr='([0]="13" [1]="2")'
  • You can't copy a whole array with arrCopy=( $arr ) - all that does is to create a single-item array containing only $arr's first element. To refer to an array as a whole, you must use "${arr[@]}" (enclosing in "..." ensures that no word-splitting is applied):

    • arrCopy=( "${arr[@]}" ) - OK
    • arrCopy=( $arr ) - BROKEN - only copies 1st element

Also note that it's better not to use all-uppercase shell-variable names in order to avoid conflicts with environment variables and special shell variables.


Upvotes: 1

Related Questions