Q:

bash script: use command output to dynamically create menu and arrays?

I'm trying to create a script to run a command and take that output and use it to create a menu dynamically. I also need to access parts of each output line for specific values.
I am using the command:
lsblk --nodeps -no name,serial,size | grep "sd"

output:
sda   600XXXXXXXXXXXXXXXXXXXXXXXXXX872 512G
sdb   600XXXXXXXXXXXXXXXXXXXXXXXXXXf34 127G

I need to create a menu that looks like:
Available Drives:
1) sda   600XXXXXXXXXXXXXXXXXXXXXXXXXX872 512G
2) sdb   600XXXXXXXXXXXXXXXXXXXXXXXXXXf34 127G
Please select a drive: 

(note: there can be any number of drives, this menu would be constructed dynamically from the available drives array)
When the user selects the menu number I need to be able to access the drive id (sdb) and drive serial number (600XXXXXXXXXXXXXXXXXXXXXXXXXXf34) for the selected drive.
Any assistance would be greatly appreciated.
Please let me know if any clarification is needed.

A:

#!/usr/bin/env bash

# Read command output line by line into array ${lines [@]}
# Bash 3.x: use the following instead:
#   IFS=$'\n' read -d '' -ra lines < <(lsblk --nodeps -no name,serial,size | grep "sd")
readarray -t lines < <(lsblk --nodeps -no name,serial,size | grep "sd")

# Prompt the user to select one of the lines.
echo "Please select a drive:"
select choice in "${lines[@]}"; do
  [[ -n $choice ]] || { echo "Invalid choice. Please try again." >&2; continue; }
  break # valid choice was made; exit prompt.
done

# Split the chosen line into ID and serial number.
read -r id sn unused <<<"$choice"

echo "id: [$id]; s/n: [$sn]"

As for what you tried: using an unquoted command substitution ($(...)) inside an array constructor (( ... )) makes the tokens in the command's output subject to word splitting and globbing, which means that, by default, each whitespace-separated token becomes its own array element, and may expand to matching filenames.
Filling arrays in this manner is fragile, and even though you can fix that by setting IFS and turning off globbing (set -f), the better approach is to use readarray -t (Bash v4+) or IFS=$'\n' read -d '' -ra (Bash v3.x) with a process substitution to fill an array with the (unmodified) lines output by a command.

