Learn to use Bash with the Azure CLI

Azure CLI reference commands can execute in several different shell environments, but Microsoft Docs primarily use the Bash environment. If you are new to Bash and also the Azure CLI, you will find this article a great place to begin your learning journey. Work through this article much like you would a tutorial, and you'll soon be using the Azure CLI in a Bash environment with ease.

In this article, you will learn how to do the following:

  • Query results as JSON dictionaries or arrays
  • Format output as JSON, table, or TSV
  • Query, filter, and format single and multiple values
  • Use if/exists/then and case syntax
  • Use for loops
  • Use grep, sed, paste, and bc commands
  • Populate and use shell and environment variables

If you don't have an Azure subscription, create an Azure free account before you begin.

Starting Bash

Start Bash using Azure Cloud Shell or a local install of the Azure CLI. This article assumes that you are running Bash either using Azure Cloud Shell or running Azure CLI locally in a docker container.

Querying dictionary results

A command that always returns only a single object returns a JSON dictionary. Dictionaries are unordered objects accessed with keys. For this article, we are going to start by querying the Account object using the Account Show command.

az account show
az account show --output json # JSON is the default format

The following JSON dictionary output has some fields omitted for brevity, and identifying information has been removed or genericized.

bash-5.1# az account show
{
  "environmentName": "AzureCloud",
  "isDefault": true,
  "managedByTenants": [],
  "name": "My test subscription",
  "state": "Enabled",
  "user": {
    "name": "user@contoso.com",
    "type": "user"
  }
}

Formatting the output as YAML

Use the --output yaml argument (or -o yaml) to format the output in yaml format, a plain-text data serialization format. YAML tends to be easier to read than JSON, and easily maps to that format. Some applications and CLI commands take YAML as configuration input, instead of JSON.

az account show --output yaml

For more information about formatting the output as yaml, see YAML output format.

Formatting the output as a table

Use the --output table argument (or -o table) to format the output as an ASCII table. Nested objects aren't included in table output, but can still be filtered as part of a query.

az account show --output table

For more information about formatting the output as a table, see Table output format.

Querying and formatting single values and nested values

The following queries demonstrate querying single values, including nested values in a JSON dictionary output. The final query in this set demonstrates formatting the output using the -o tsv argument. This argument returns the results as tab- and newline-separated values. This is useful for removing quotation marks in the value returned - which is useful to consume the output into other commands and tools that need to process the text in some form (as we will demonstrate later in this article).

az account show --query name # Querying a single value
az account show --query name -o tsv # Removes quotation marks from the output

az account show --query user.name # Querying a nested value
az account show --query user.name -o tsv # Removes quotation marks from the output

Querying and formatting multiple values, including nested values

To get more than one property, put expressions in square brackets [ ] (a multiselect list) as a comma-separated list. The following queries demonstrate querying multiple values in a JSON dictionary output, using multiple output formats.

az account show --query [name,id,user.name] # return multiple values
az account show --query [name,id,user.name] -o table # return multiple values as a table

For more information about returning multiple values, see Get multiple values.

Renaming properties in a query

The following queries demonstrate the use of the { } (multiselect hash) operator to get a dictionary instead of an array when querying for multiple values. It also demonstrates renaming properties in the query result.

az account show --query "{SubscriptionName: name, SubscriptionId: id, UserName: user.name}" # Rename the values returned
az account show --query "{SubscriptionName: name, SubscriptionId: id, UserName: user.name}" -o table # Rename the values returned in a table

For more information on renaming properties in a query, see Rename properties in a query.

Querying boolean values

Boolean values are assumed to be true, so the "[?isDefault]" query syntax for the az account list command returns the current default subscription. To get the false values, you must use an escape character, such as \.

The following queries demonstrates querying all accounts in a subscription, potentially returning a JSON array if there are multiple subscriptions for a given account, and then querying for which account is the default subscription. It also demonstrates querying for the accounts that are not the default subscription. These queries build on what you learned previously to filter and format the results. Finally, the final query demonstrates storing the query results in a variable.

az account list
az account list --query "[?isDefault]" # Returns the default subscription
az account list --query "[?isDefault]" -o table # Returns the default subscription as a table
az account list --query "[?isDefault].[name,id]" # Returns the name and id of the default subscription
az account list --query "[?isDefault].[name,id]" -o table # Returns the name and id of the default subscription as a table
az account list --query "[?isDefault].{SubscriptionName: name, SubscriptionId: id}" -o table # Returns the name and id of the default subscription as a table with friendly names

az account list --query "[?isDefault == \`false\`]" # Returns all non-default subscriptions, if any
az account list --query "[?isDefault == \`false\`].name" -o table # Returns all non-default subscriptions, if any, as a table

az account list --query "[?isDefault].id" -o tsv # Returns the subscription id without quotation marks
subscriptionId="$(az account list --query "[?isDefault].id" -o tsv)" # Captures the subscription id as a variable.
echo $subscriptionId # Returns the contents of the variable.
az account list --query "[? contains(name, 'Test')].id" -o tsv # Returns the subscription id of a non-default subscription containing the substring 'Test'
subscriptionId="$(az account list --query "[? contains(name, 'Test')].id" -o tsv) # Captures the subscription id as a variable. 
az account set -s $subscriptionId # Sets the current active subscription

Creating objects using variables and randomization

Setting a random value for use in subsequent commands

Setting and using a random value for use in variables allows you to run scripts multiple times without naming conflicts. Naming conflicts can occur because a value must be unique across the service or because an object you have deleted still exists within Azure until the deletion process is complete.

$RANDOM is a bash function (not a constant) that returns a random signed 16 bit integer (from 0 through 32767). The let command is a built-in Bash command to evaluate arithmetic expressions. Using the following command creates a sufficiently unique value for most purposes.

let "randomIdentifier=$RANDOM*$RANDOM"

Working with spaces and quotation marks

Spaces are used for separating commands, options, and arguments. Use quote marks to tell the Bash shell to ignore all special characters, of which a white space is a special character. When the Bash shell sees the first quote mark, it ignores special characters until the closing quote mark. However, sometimes you want the Bash shell to parse certain special characters, such as dollar signs, back quotes, and backslashes. For this, use double quotes.

The following commands use the az group create command to illustrate the use of single and double quote marks to handle spaces and evaluate special characters when working with variables and creating an object.

resourceGroup='msdocs-learn-bash-$randomIdentifier'
echo $resourceGroup # The $ is ignored in the creation of the $resourceGroup variable
resourceGroup="msdocs-learn-bash-$randomIdentifier"
echo $resourceGroup # The $randomIdentifier is evaluated when defining the $resourceGroup variable
location="East US" # The space is ignored when defining the $location variable
echo The value of the location variable is $location # The value of the $location variable is evaluated
echo "The value of the location variable is $location" # The value of the $location variable is evaluated
echo "The value of the location variable is \$location" # The value of the $location variable is not evaluated
echo 'The value of the location variable is $location' # The value of the $location variable is not evaluated
az group create --name $resourceGroup --location $location # Notice that the space in the $location variable is not ignored and the command fails as it treats the value after the space as a new command 
az group create --name $resourceGroup --location "$location" # Notice that the space in the $location variable is ignored and the location argument accepts the entire string as the value 

In the JSON dictionary output, review the properties of the resource group that was just created.

Using If Then Else to determine if variable is null

To evaluate strings, use != and to evaluate numbers use -ne. The following If Then Else statement evaluates whether the $resourceGroup variable has been set. If yes, it returns the value of the variable. If no, it sets the variable.

if [ $resourceGroup != '' ]; then
   echo $resourceGroup
else
   resourceGroup="msdocs-learn-bash-$randomIdentifier"
fi

Using If Then to create or delete a resource group

The following script creates a new resource group only if one with the specified name does not already exist.

if [ $(az group exists --name $resourceGroup) = false ]; then 
   az group create --name $resourceGroup --location "$location" 
else
   echo $resourceGroup
fi

The following script deletes an existing new resource group if one with the specified name already exists. You could use the --no-wait argument to return control without waiting for the command to complete. However, for this article, we want to wait for the resource group to be deleted before continuing. For more information on asynchronous operations, see Asynchronous operations. We will demonstrate the use of the --no-wait argument at the end of this article.

if [ $(az group exists --name $resourceGroup) = true ]; then 
   az group delete --name $resourceGroup -y # --no-wait
else
   echo The $resourceGroup resource group does not exist
fi

Using Grep to determine if a resource group exists, and create the resource group if it does not

The following command pipes the output of the az group list command to the grep command. If the specified resource group does not exist, the command creates the resource group using the previously defined variables.

az group list --output tsv | grep $resourceGroup -q || az group create --name $resourceGroup --location "$location"

Using CASE statement to determine if a resource group exists, and create the resource group if it does not

The following CASE statement creates a new resource group only if one with the specified name does not already exist. If one with the specified name exists, the CASE statement echoes that the resource group exists.

var=$(az group list --query "[? contains(name, '$resourceGroup')].name" --output tsv)
case $resourceGroup in
$var)
echo The $resourceGroup resource group already exists.;;
*)
az group create --name $resourceGroup --location "$location";;
esac

Using for loops and querying arrays

In this section of the article, we will create a storage account and then use for loops to create a number of blobs and containers. We will also demonstrate querying JSON arrays and working with environment variables.

Create storage account

The following command uses the az storage account create command to create a storage account that we will use when creating storage containers.

storageAccount="learnbash$randomIdentifier"
az storage account create --name $storageAccount --location "$location" --resource-group $resourceGroup --sku Standard_LRS --encryption-services blob

Get the storage account keys

The following commands use the az storage account keys list command to return storage account key values. We then store a key value in a variable for use when creating storage containers.

az storage account keys list --resource-group $resourceGroup --account-name $storageAccount --query "[].value" -o tsv # returns both storage account key values

az storage account keys list --resource-group $resourceGroup --account-name $storageAccount --query "[0].value" -o tsv # returns a single storage account key value

accountKey=$(az storage account keys list --resource-group $resourceGroup --account-name $storageAccount --query "[0].value" -o tsv)

echo $accountKey

Create storage container

We will start by using the az storage container create to create a single storage container and then use the az storage container list to query the name of the created container.

container="learningbash"
az storage container create --account-name $storageAccount --account-key $accountKey --name $container

az storage container list --account-name $storageAccount --account-key $accountKey --query [].name

Upload data to container

The following script creates three sample files using a for loop.

for i in `seq 1 3`; do
    echo $randomIdentifier > container_size_sample_file_$i.txt
done

The following script uses the az storage blob upload-batch command to upload the blobs to the storage container.

az storage blob upload-batch \
    --pattern "container_size_sample_file_*.txt" \
    --source . \
    --destination $container \
    --account-key $accountKey \
    --account-name $storageAccount

The following script uses the az storage blob list command to list the blobs in the container.

az storage blob list \
    --container-name $container \
    --account-key $accountKey \
    --account-name $storageAccount \
    --query "[].name"

The following script displays the total bytes in the storage container.

bytes=`az storage blob list \
    --container-name $container \
    --account-key $accountKey \
    --account-name $storageAccount \
    --query "[*].[properties.contentLength]" \
    --output tsv | paste -s -d+ | bc`

echo "Total bytes in container: $bytes"
echo $bytes

Create many containers using loops

Next, we will create multiple containers using a loop demonstrating a couple of ways to write the loop.

for i in `seq 1 4`; do 
az storage container create --account-name $storageAccount --account-key $accountKey --name learnbash-$i
done

for value in {5..8}
for (( i=5; i<10; i++))
do
az storage container create --account-name $storageAccount --account-key $accountKey --name learnbash-$i
done

az storage container list --account-name $storageAccount --account-key $accountKey --query [].name

Use EXPORT to define environment variables

In the preceding storage container scripts, we specified the account name and account key with every command. Instead, you can store your authentication credentials using the corresponding environment variables: AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_KEY. To do this, use EXPORT.

export AZURE_STORAGE_ACCOUNT=$storageAccount
export AZURE_STORAGE_KEY=$accountKey
az storage container list # Uses the environment variables to display the list of containers.

The following script creates a metadata string and then uses the az storage container metadata update command to update a container with that string, again using the environment variables.

metadata="key=value pie=delicious" # Define metadata
az storage container metadata update \
    --name $container \
    --metadata $metadata # Update the metadata
az storage container metadata show \
    --name $containerName # Show the metadata

The following command uses the az storage container delete command to delete a single named container and then delete multiple containers in a loop.

az storage container delete \
    --name $container

Get list of containers containing a specific prefix and store results into a variable.

containerPrefix="learnbash"
containerList=$(az storage container list \
    --query "[].name" \
    --prefix $containerPrefix \
    --output tsv)

Delete the list of containers in a loop using the --prefix argument.

for row in $containerList
do
    tmpName=$(echo $row | sed -e 's/\r//g')
    az storage container delete \
    --name $tmpName 
done

Error handling

To exit a script immediately if a command returns a non-zero status, run the following command:

set -e

For more information about setting shell options and other help topics, run the following commands:

help set
help help

Clean up resources

When you are finished this article, delete the resource group and all resources within it. Use the --no-wait argument.

if [ $(az group exists --name $resourceGroup) = true ]; then 
   az group delete --name $resourceGroup -y  --no-wait
else
   echo The $resourceGroup resource group does not exist
fi

See also