Search code examples
bashazure-cli2

BASH - Read value from array into command


I am relatively new to bash and I need to create 127 VNET security rules for an Azure MySQL server instance using the Azure CLI, which needs a rule name and associated subnet ID. The rule name is the subnet name. I can read the subnet name and ID into arrays and can see the arrays populated with

mapfile -t vnetRULEname < <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{name:name}" -o table)

mapfile -t vnetRULEid < <(az network vnet subnet list -g resourcegroup 
--vnet-name vnet --query "[].{objectID:id}" -o table)

I then want to run the following command so it creates the 127 rules using each name and ID in the arrays to create the rules.

az mysql server vnet-rule create -n <rule name from vnetRULEname> -g resourcegroup -s servername --subnet <subnet ID from vnetRULEid>

Would it be better to read both the subnet name and ID values into the same array?

Whats the best way to do this in a bash script and how do i tell it to ignore the column headers called Name and ID and the subnet called GatewaySubnet?

Sample of output (subnet names)

Name
-------------
GatewaySubnet
app-host-001
app-host-002
app-host-003
app-host-004
app-host-005

Sample of output (subnet ID's)

ObjectID
----------------------------------------------------------------------------- 
/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/resourceGroups/resourcegroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/GatewaySubnet
/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/resourceGroups/resourcegroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/app-host-001
/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/resourceGroups/resourcegroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/app-host-002
/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/resourceGroups/resourcegroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/app-host-003
/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/resourceGroups/resourcegroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/app-host-004
/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/resourceGroups/resourcegroup/providers/Microsoft.Network/virtualNetworks/vnet/subnets/app-host-005

Running

#!/bin/bash

mapfile -t vnetRULEname < <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{name:name}" -o table)
mapfile -t vnetRULEid < <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{objectID:id}" -o table)
echo "These are vnetRULEname: ${vnetRULEname[@]}"
echo "These are vnetRULEids : ${vnetRULEid[@]}"

Displays the contents of both arrays on screen as i would expect to see. But if i run

mapfile -t vnetRULEname < <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{name:name}" -o table)
mapfile -t vnetRULEid < <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{objectID:id}" -o table)
#echo "These are vnetRULEname: ${vnetRULEname[@]}"
#echo "These are vnetRULEids : ${vnetRULEid[@]}"
sizeofarrays=${#arr[@]}
for (( i=0 ; i < sizeofarrays ; i++ ))
do
    az mysql server vnet-rule create --name "${vnetRULEname[$i]}" --resource-group resourcegroup --server server --subnet "${vnetRULEid[$i]}"
done
echo ${arr[@]}  ## print all the array
echo ${#arr[@]} ## print its size

I get

0

0

Many thanks in advance,

Andrew


Solution

  • First, an approach correctly iterating through two arrays in lockstep, after ignoring the first two keys (corresponding to header lines):

    #!/usr/bin/env bash
    
    mapfile -t vnetRULEname < <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{name:name}" -o table)
    mapfile -t vnetRULEid < <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{objectID:id}" -o table)
    
    # Remove the headers from each
    unset 'vnetRULE'{name,id}{'[0]','[1]'}
    
    for idx in "${!vnetRULEname[@]}"; do
      name=${vnetRULEname[$idx]}
      id=${vnetRULEid[$idx]}
      az mysql server vnet-rule create \
        -n "$name" \
        -g resourcegroup \
        -s servername \
        --subnet "$id"
    done
    

    Second, an approach reading both pieces of data into a single associative array, storing the id as the key and the name as the value:

    #!/usr/bin/env bash
    
    declare -A vnets=( )
    {
      # consume header lines
      read <&3; read <&3; read <&4; read <&4
      while IFS= read -r name <&3 && IFS= read -r id <&4; do
        vnets[$id]=$name
      done
    } 3< <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{name:name}" -o table) 4< <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{objectID:id}" -o table)
    
    for id in "${!vnets[@]}"; do
      name=${vnets[$id]}
      az mysql server vnet-rule create \
        -n "$name" \
        -g resourcegroup \
        -s servername \
        --subnet "$id"
    done
    

    ...but of course, if you can do that, you can just call the command direct in the while read loop, and not need to store an array at all:

    {
      # consume header lines
      read <&3; read <&3; read <&4; read <&4
      while IFS= read -r name <&3 && IFS= read -r id <&4; do
      az mysql server vnet-rule create \
        -n "$name" \
        -g resourcegroup \
        -s servername \
        --subnet "$id"
      done
    } 3< <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{name:name}" -o table) 4< <(az network vnet subnet list -g resourcegroup --vnet-name vnet --query "[].{objectID:id}" -o table)