1

I am refactoring a script that displays a text menu similar to this:

Select a mounted BTRFS device on your local machine to backup to.
1) 123456789abc-def012345-6789abcdef012 (/)
2) 123456789abc-def012345-6789abcdef012 (/home)
...
26) 3e3456789abc-def012345-6789abcdef369 (/mnt/backup2)
27) 223456789abc-def012345-6789abcdef246 (/mnt/backup3)

Note that the menu displays two items of information: the UUID and the target path. Replicating that with the select command is one of the things I do not know how to do.

In this menu, the user enters a number (e.g. 26) and the script performs a BTRFS send|receive to that device & path. I want the script menu to continue working the same way i.e., to display the UUID plus path and take a simple 1 or 2 digit or char input.

However, I want to refactor the code. I did not write the original code. It uses two arrays to display the menu and to handle the selection.

I want to use an associative array. This is purely an exercise for me to gain experience with associative arrays. The script is working as is.

I would like to use the following code which I wrote as a basis for the menu:

declare -A UUID_TARGETS

for target in $TARGETS; do
    if ! [[ "$target" =~ ^/mnt|^/backup ]] ; then
        echo "skipped $target"
    else
        UUID_TARGETS["$target"]=$(findmnt -n -t btrfs -o UUID -M "$target")
    fi
done

for target in "${!UUID_TARGETS[@]}"; do
    #testing
    echo UUID [${UUID_TARGETS["$target"]}] has mountpoint [$target]
done

The bash select command doesn't seem to make the menu I want because I need two items of information displayed in the menu: the UUID and the target path. The naive use of select like this isn't quite sufficient:

PS3="Please enter your choice (q to quit): "
select target in "${!UUID_TARGETS[@]}" "quit";
do
    case "$target" in
        "quit")
            echo "Exited"
            break
            ;;
        *)
            selected_uuid=${UUID_TARGETS["$target"]}
            selected_mnt="$target"
            ;;
    esac
done

I'm hoping someone here has an elegant solution for making a menu like this from an associative array. I want to do this in bash without extra packages.

In the end, I still need to assign the correct value to selected_uuid and selected_mnt. The original code does it like this:

selected_uuid="${UUIDS_ARRAY[$((disk))]}"
selected_mnt="${TARGETS_ARRAY[$((disk))]}"

For reference, here is the whole section of original code:

TARGETS="$(findmnt -n -l -t btrfs -o TARGET --list -F /etc/fstab)"
UUIDS="$(findmnt -n -l -t btrfs -o UUID --list -F /etc/fstab)"

declare -a TARGETS_ARRAY
declare -a UUIDS_ARRAY

i=0
disk=-1
disk_count=0
for x in $UUIDS; do
    UUIDS_ARRAY[$i]=$x
    if [[ "$x" == "$uuid_cmdline" ]]; then
        disk=$i
        disk_count=$(($disk_count+1))
    fi
    i=$((i+1))
done

i=0
for x in $TARGETS; do
    TARGETS_ARRAY[$i]=$x
    i=$((i+1))
done

if [[ "$disk_count" > 1 ]]; then
    disk="-1"
fi

if [[ "$disk" == -1 ]]; then
    if [[ "$disk_count" == 0 && "$uuid_cmdline" != "none" ]]; then
        error "A device with UUID $uuid_cmdline was not found to be mounted, or it is not a BTRFS device."
    fi
    if [[ -z $ssh ]]; then
        printf "Select a mounted BTRFS device on your local machine to backup to.\n"
    else
        printf "Select a mounted BTRFS device on $remote to backup to.\n"
    fi
    while [[ $disk -lt 0 || $disk -gt $i ]]; do
        for x in "${!TARGETS_ARRAY[@]}"; do
            printf "%4s) %s (%s)\n" "$((x+1))" "${UUIDS_ARRAY[$x]}" "${TARGETS_ARRAY[$x]}"
        done
        printf "%4s) Exit\n" "0"
        read -r -p "Enter a number: " disk
        if ! [[ $disk == ?(-)+([0-9]) ]]; then
            printf "\nNo disk selected. Select a disk to continue.\n"
            disk=-1
        fi
    done
    if [[ $disk == 0 ]]; then
        exit 0
    fi
    disk=$(($disk-1))
fi

selected_uuid="${UUIDS_ARRAY[$((disk))]}"
selected_mnt="${TARGETS_ARRAY[$((disk))]}"
printf "\nYou selected the disk with UUID %s.\n" "$selected_uuid" | tee $PIPE
printf "The disk is mounted at %s.\n" "$selected_mnt" | tee $PIPE
2
  • Why not create a new array with the merged entries, i.e. 123456789abc-def012345-6789abcdef012 (/) and do the "naive select" on that array? Commented Dec 9, 2019 at 2:44
  • @icarus I guess I could, but I was looking for a solution that didn't need another array (if such a solution exists). I was just hoping to learn of an elegant solution, if there is one. Commented Dec 9, 2019 at 3:09

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.