1

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

  • There's arguably more than one question here -- generally, that means something should be split into multiple smaller questions (checking for each one whether there's already a knowledgebase entry covering the ground). As for avoiding headers, we can't tell you how to do that until we know their format -- if they're only on the first line, skipping one line (or deleting it from the array after-the-fact) is easy. – Charles Duffy Jul 31 '18 at 14:14
  • ...anyhow, in terms of "how do I read output from *these commands* into a single array?", it becomes much easier to test an answer if we know (1) what format that output is in, and (2) what you want the array to look like, if given the sample output included in the question. – Charles Duffy Jul 31 '18 at 14:16
  • Hi and thank you for your reply. I'm new ish on here so thanks for the tips. I did have a dig round via the search box but couldn't quite find what i was looking for. – Naughty-Old-Jarvis Jul 31 '18 at 14:24
  • Could you [edit] samples of the output of each command into the question, and then an example of what the combined array you want to generate should look like? That way you can get help from people who know bash but not Azure. – Charles Duffy Jul 31 '18 at 14:25
  • The output from the commands looks like
    Name ------------- GatewaySubnet app-host-001 app-host-002 app-host-003 app-host-004
    – Naughty-Old-Jarvis Jul 31 '18 at 14:29
  • Apologies for the terrible formatting. So basically it outputs a table with the first line as Name the second as ---------- the third as GatewaySubnet and then the names of the subnets which are the values i need. The ID's are outputted in the same way. What i then want to do is use each name and corresponding subnet ID value to run the rule create command and create the rules. – Naughty-Old-Jarvis Jul 31 '18 at 14:32
  • An edit into the question (as opposed to a comment) both makes that information... well... a canonical part of the question, and also is much easier to format. :) – Charles Duffy Jul 31 '18 at 14:34
  • BTW, when it comes to debugging, `bash -x yourscript` will log each operation, so you can see what is or isn't working. Similarly, you can run `declare -p arrayName >&2` to log the contents of an array, so you can make sure it's what you expect. Using those may make it easier to ask a question focused around the exact place you're getting stuck, improving the chance that answers will be helpful/applicable. (BTW, I'm about to get on the train, so I'll only be picking back up on this question if it's still open/unsolved in an hour or so). – Charles Duffy Jul 31 '18 at 14:35
  • Hi, thank you for that. Apologies for the late reply. I've updated the question with outputs from each command. Part of the problem is i am not sure what kind of array i need to best do this. As you can see from the outputs the commands return the data in a table format. I can run the command and have both the subnet name and ID in the same table in two different columns too, which is why i also wondered if having both elements in the same array would be better maybe? – Naughty-Old-Jarvis Aug 01 '18 at 07:08
  • Umm. `${#arr[@]}` should be `${#vnetRULEname[@]}`; ie. `arr` was the standin for the name of a specific array. – Charles Duffy Aug 01 '18 at 13:07

2 Answers2

0

What about a for loop?

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 -n "${vnetRULEname[$i]}" -g resourcegroup -s servername --subnet "${vnetRULEid[$i]}"
done

Hope it works for you! :)

Edit :

This is how you get the array size and elements

echo ${arr[@]}  ## print all the array
echo ${#arr[@]} ## print its size

## so in your case sizeofarray should be equal to ${#arr[@]}
Matias Barrios
  • 4,674
  • 3
  • 22
  • 49
  • Hi and thank you for that. That's what i was thinking i need but just couldn't work out how to do it. Unfortunately when i add that to the script and run it i get no output in the terminal and no rules get added to the server when i check the portal in Azure. Any ideas? – Naughty-Old-Jarvis Jul 31 '18 at 14:17
  • I have further explained in my edit. You can add that in your code to troubleshoot. You were mentioning that your arrays were populating correctly so I answered accordingly. But in order to be sure, those echoes will help you @Naughty-Old-Jarvis – Matias Barrios Jul 31 '18 at 14:47
  • Thank you for that. I'll give it a go this morning. – Naughty-Old-Jarvis Aug 01 '18 at 07:09
  • Main question updated with outputs. When i run the echoes from your update it doesn't show anything but when i echo the arrays i can see the values in them. Or have i got the command order the wrong way round? – Naughty-Old-Jarvis Aug 01 '18 at 09:30
0

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)
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thank you very much for those. I did also manage to get it working after some investigation into the output types the AZ CLI supports and some further digging then running the following – Naughty-Old-Jarvis Aug 02 '18 at 07:26
  • #!/bin/bash #Defines and populates the arrays for the rule name and subnet ID's array=( $(az network vnet subnet list -g resourcegroup --vnet-name vnet -o tsv | grep app-host- | cut -f5) ) array2=( $(az network vnet subnet list -g resourcegroup --vnet-name vnet -o tsv | grep app-host- | cut -f3) ) #The For loop which creates the rules for ((i=0;i<${#array[@]};++i)); do az mysql server vnet-rule create --resource-group resourcegroup --server servername --name "${array[i]}" --subnet "${array2[i]}" done – Naughty-Old-Jarvis Aug 02 '18 at 07:28
  • For some reason my comments don't seem to want to code block! I'm sure the code i managed to cobble together is a bad way to things so thank you again for your much more elegant solutions. – Naughty-Old-Jarvis Aug 02 '18 at 07:30
  • See [BashPitfalls #50](http://mywiki.wooledge.org/BashPitfalls#hosts.3D.28_.24.28aws_....29_.29) about why `array=( $(...) )` is best avoided. `readarray -t array < <(...)` can be used with `cut`, as in your comment. – Charles Duffy Aug 02 '18 at 11:08