BASH pointer juggling

The introduction

BASH, like many programming and scripting languages, has pointer types. The word pointer says it all, it points to something else, it references something. You can use the contents of any scalar value in BASH as reference by using the ${!varname} syntax.

To create an actual pointer variable though you need to declare -n it.

Once you declare a variable as pointer you can use the ${!varname} syntax to find out where it points to

An example

#!/bin/bash
# save as /tmp/bashpointer.sh

targetVariable="the_target"

the_target="the value being pointed at"

echo "The contents of the ${targetVariable} variable is '${!targetVariable}'"

declare -n aRealPointer="the_target"

echo "The contents of the ${!aRealPointer} variable is '${aRealPointer}'"
$ sh /tmp/bashpointer.sh 
The contents of the the_target variable is 'the value being pointed at'
The contents of the the_target variable is 'the value being pointed at'

A task that I come across often is the processing of configuration files. We check if a variable is defined and copy it to the actual variable used throughout the script. This job is quite tedious and error-prone for anything more than a single variable.

This can be solved easily enough though with BASH pointers.

Let's make an array where each even-numbered index contains the variable to set, and each odd-numbered index contains the variable to read:

CHECKS=(
  "name" "profile_name"
 "value" "profile_value"
)

So we go through the list and check if the odd-numbered variable is set and if it is copy it to the even-numbered variable:

A first attempt

#!/bin/bash
# save as /tmp/refs.sh

profile_name="The name of the profile"
profile_value="The value of the profile"
name="___name"
value="___value"

CHECKS=(
  "name" "profile_name"
 "value" "profile_value"
)

set - ${CHECKS[@]}

typeset -n theref

while [ $# -gt 0 ]; do

 theref=$1

 [ -n "${!2}" ] && theref="$2"

 shift 2

done

echo " Name: ${name}"
echo "Value: ${value}"
$ sh /tmp/refs.sh
  Name: The value of the profile
 Value: ___value

That is unexpected! What is going on? The Value variable did not get set, and the Name variable has got the wrong value!

If we analyse this a bit more though, we can make the following observations:

  • The value of the Name variable changed therefore we have successfully created a pointer to the Name variable
  • The value meant for the Value variable has been put in the Name variable
  • The Value variable has not changed which means we have never been able to create a pointer to it

All of these observations spell out one thing: Once set, the pointer's target has not changed

This is corroborated by the BASH manual for declare -n:

declare [-aAfFgilnrtux] [-p] [name[=value] ...] typeset [-aAfFgilnrtux] [-p] [name[=value] ...] ... -n Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references, assignments, and attribute modifications to name, except those using or changing the -n attribute itself, are performed on the variable referenced by name's value. The nameref attribute cannot be applied to array variables.

OK, according to this we can only update the value being pointed at by removing the -n attribute first:

Second attempt

#!/bin/bash
# save as /tmp/refs2.sh

profile_name="The name of the profile"
profile_value="The value of the profile"
name="___name"
value="___value"

CHECKS=(
  "name" "profile_name"
 "value" "profile_value"
)

set - ${CHECKS[@]}

while [ $# -gt 0 ]; do

 typeset -n theref="$1"

 [ -n "${!2}" ] && theref="$2"

 shift 2
 
 typeset +n theref

done

echo " Name: ${name}"
echo "Value: ${value}"
$ sh /tmp/refs2.sh 
 Name: profile_name
Value: profile_value

Success!!

In conclusion

There is no understating the merrits of pointers, yet the age old addage about great power and responsibility applies here too. Being responsible with BASH pointers means flipping the -n property prior to changing its target, or, using a local variable that goes out of scope between calls

.

No comments:

Post a Comment

BASH: singular or plural done easy, a story of ternaries

The introduction To avoid looking silly and being made fun the world over for having made a program that prints hair-raising butchered Eng...