Search code examples
stringbashvariablesarguments

How to insert value of variable into a string that already contains brackets and quotation marks in a bash script?


Usually what one would do is

a='valueA'
b='valueB'
c="${a} ${b}"
echo "${c}"
> valueA valueB

when inserting the value of a variable into a string. In my case however the curly brackets are already present and - at least this is what I think - it messes up things.

How can I do this if echo already has curly brackets, e.g. something like (pseudo-code)

echo '{"values" : [ { "a" : "${a}" }, { "b" : "${b}" } }'

where ${a} is the retrieval of the value of variable a and ${b} - for b. I tried also with simply using $a but neither works and the value is not exposed.


Background information

I have a bash script that automatically patches Kubernetes (here Canonical microk8s) services that are listed in a separate CSV file:

#!/bin/bash

# Retrieve currently assigned IP address for the interface that exposes the guest to the host
LOCAL_IP_EXTERNAL=`ip -f inet addr show enp1s0 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p'`
echo Local IP for external access is $LOCAL_IP_EXTERNAL

echo Updating externalIP property for services found in configuration file "automatic_update_of_externalIP_services.csv"
echo ""

# Parse the CSV and apply the external IP address patch to each service listed inside
while IFS="," read -r namespace service
do
  echo "Namespace: $namespace"
  echo "Service: $service"
  echo "Patching..."
  microk8s kubectl -n $namespace patch svc $service --patch='{"spec":{"externalIPs":["$LOCAL_IP_EXTERNAL"]}}'
done < <(tail -n +2 automatic_update_of_externalIP_services.csv)

with the CSV looking similar to this:

automatic_update_of_externalIP_services.csv

namespace,service
geo,geoserver
geo,pgsql11
geo,django

The important part is '{"spec":{"externalIPs":["$LOCAL_IP_EXTERNAL"]}}'. I tried using ${LOCAL_IP_EXTERNAL}. I also tried changing the name of the variable (not that that would change something in my case :D). It always ends up with

The Service "geo-service-pgsql12" is invalid: spec.externalIPs[0]: Invalid value: "$LOCAL_IP_EXTERNAL": must be a valid IP address, (e.g. 10.9.8.7 or 2001:db8::ffff)

for the non-curly-brackets version or

The Service "geo-service-pgsql12" is invalid: spec.externalIPs[0]: Invalid value: "${LOCAL_IP_EXTERNAL}": must be a valid IP address, (e.g. 10.9.8.7 or 2001:db8::ffff)

for the curly-brackets version.

What the error clearly shows is that the value of LOCAL_IP_EXTERNAL is not exposed and all I am doing is pass a simple string without any semantic information behind it.

The VM where my cluster node is running is connected to the host and uses default DHCP. Needless to say this leads to an issue when the IP address changes and externalIP still has the old one. I don't want to tinker with the DHCP or setup a DNS neither on the host, nor on the guest. In addition my VM will be migrated to a different network soon so any static configuration will probably be useless or even harmful (especially if I forget to remove it :D).


Solution

  • As pointed out in the comments, you used single quotes (') and therefore the variable $LOCAL_IP_EXTERNAL was never expanded.

    A quick fix would be to use either one double quoted string in which you have to escape the literal double quotes, or a combination of single and double quoted strings:

    microk8s ... --patch="{\"spec\":{\"externalIPs\":[\"$LOCAL_IP_EXTERNAL\"]}}"
    microk8s ... --patch='{"spec":{"externalIPs":["'"$LOCAL_IP_EXTERNAL"'"]}}'
    #                    ╰------single-quoted------╯╰--double-quoted---╯╰-sq-╯
    

    Either way, the quoting/escaping can become really hard to read, resulting in bugs. So in general, I'd suggest to use either a HereDoc (<<EOF) or printf to generate your patch.

    printf -v patch '{"spec": {"externalIPs": ["%s"]}}' "$LOCAL_IP_EXTERNAL"
    microk8s ... --patch="$patch"