Joshua Jarman Joshua Jarman - 24 days ago 4
Bash Question

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.

My initial start is breaking up up the array incorrectly:

number_of_drives=0;
declare -a drives=();
declare -a drives_ids=();
declare -a drives_serialnumbers=();

drives=( $(lsblk --nodeps -o name,serial,size | grep "sd") );
number_of_drives=${#drives[@]};


Unfortunately this results in the array:

drives[1] = sda
drives[2] = 600XXXXXXXXXXXXXXXXXXXXXXXXXX872
drives[3] = 512G
drives[4] = sdb
drives[5] = 600XXXXXXXXXXXXXXXXXXXXXXXXXXf34
drives[6] = 127G


so i'm already off to a bad start. :-( Thanks!

Answer
#!/usr/bin/env bash

# Read command output line by line into array ${lines [@]}
# Bash 3.x: use IFS=$'\n' read -d '' -ra lines ...` instead.
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.

Comments