How to Append to an Indirect Parameter Expansion of an Array in Bash

How do you append to an indirect parameter expansion of an array in BASH?

#!/bin/bash

var1="target"
var2="arrayname"
targetarrayname=( "one" "two" "three" )
builtarrayname="${var1}${var2}[@]"
echo ${!builtarrayname} # prints "one two three"

eval "${builtarrayname:0:-3}+=( 'foo' )"

echo ${!builtarrayname} # prints "one two three foo"

Do note though that eval could be considered evil :p
You need to be sure that you only have sanitized input to eval statements, to avoid the possibility of unintended code execution.

EDIT:

The :0:-3 in eval "${builtarrayname:0:-3}+=( 'foo' )" removes [@] from the literal string that $builtarrayname contains. Since assignment of arrays only use the variable name, we had to remove it. ( read more here to see how string manipulation in variables work )

and no, I don't think it can be done without eval, as that's how the left side of the assignment gets resolved before the assignment occurs (see this nice answer for more info on eval.

Indirect parameter expansion of an array argument in Bash

You can do it, but in an unexpected way: the placeholder var needs to include the array index:

multi_fruit() { 
(( $# != 1 )) && return 1
tmp="${1}[@]"
for i in "${!tmp}"; do
echo "$i"
done
}

Also, it's a bad idea to use only uppercase variable names. One day you'll accidentally overwrite PATH and wonder why your script is broken. Leave uppercase vars to the system.

Also note that putting braces around the variable is not the same as double quotes. For example, consider:

var="one two three"
printf "%s\n" ${var}
printf "%s\n" "$var"

Bash indirect array addressing?

I've already found a resolution, this can be done by:

$ Aref=A$J
$ echo ${!Aref}
building
$ Aref=A$J[1]
$ echo ${!Aref}
blocks
$ Aref=A$J[@]
$ echo "${!Aref}"
building blocks

Bash parameter expansion, indirect reference, and backgrounding

The problem is that read, when run in the background, isn't connected to a standard in.[details] Consider this simplified, working example with comment how to cripple it:

VALUES=( VALUE_A VALUE_B )
for value in "${VALUES[@]}"; do
read ${value} < <(echo ${RANDOM}) # add "&" and it stops working
done
echo "VALUE_A=${VALUE_A}"
echo "VALUE_B=${VALUE_B}"

You might be able to do this with coproc, or using read -u with automatic file descriptor allocation, but really this is a job for temporary files:

tmpdir=$(mktemp -d)

VALUES=( VALUE_A VALUE_B )
for value in "${VALUES[@]}"; do
(sleep 1; echo ${RANDOM} > "${tmpdir}"/"${value}") &
done
for value in "${VALUES[@]}"; do
wait_file "${tmpdir}"/"${value}" && {
read -r ${value} < "${tmpdir}"/"${value}";
}
done
echo "VALUE_A=${VALUE_A}"
echo "VALUE_B=${VALUE_B}"

rm -r "${tmpdir}"

This example uses wait_file helper, but you might use inotifywait if you don't mind some dependencies on OS.

How to iterate over an array using indirect reference?

${!ARRAYNAME[@]} means "the indices of ARRAYNAME". As stated in the bash man page since ARRAYNAME is set, but as a string, not an array, it returns 0.

Here's a solution using eval.

#!/usr/bin/env bash

ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )

eval array=\( \${${ARRAYNAME}[@]} \)

for fruit in "${array[@]}"; do
echo ${fruit}
done

What you were originally trying to do was create an Indirect Reference. These were introduced in bash version 2 and were meant to largely replace the need for eval when trying to achieve reflection-like behavior in the shell.

What you have to do when using indirect references with arrays is include the [@] in your guess at the variable name:

#!/usr/bin/env bash

ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )

array="${ARRAYNAME}[@]"
for fruit in "${!array}"; do
echo $fruit
done

All that said, it's one thing to use Indirect References in this trivial example, but, as indicated in the link provided by Dennis Williamson, you should be hesitant to use them in real-world scripts. They are all but guaranteed to make your code more confusing than necessary. Usually you can get the functionality you need with an Associative Array.

Appending to an array w/ its name passed as a bash function parameter

Assuming bash 4.3 or newer, thus having namevars (declare -n / local -n):

add_elem_to_array() {
local elem=$1 array_name=$2
local -n array=$array_name
array+=( "$elem" )
}

Supporting bash 3.x (particularly including 3.2, the oldest version in widespread use as of this writing):

add_elem_to_array() {
local elem=$1 array_name=$2
local cmd
printf -v cmd '%q+=( %q )' "$array_name" "$elem"
eval "$cmd"
}

That said -- given array+=( "$value" ) as an available syntax, there's little need for a function for the purpose, is there?

Bash: combine parameter indirection with arithmetic expansion?

If you create an array from the positional parameters, you can use array indexing to get the desired values.

args=( "$@" )
echo "${args[-1]}" # Last argument
echo "${args[-2]}" # Next to last argument

echo "${args[4]}" # Get the 5th argument (zero-based indexing)
echo ${args[SKIP+3]} # If SKIP=1, same as above.

Array indices are evaluated in an arithmetic context, the same as if you used $((...)).

What is indirect expansion? What does ${!var*} mean?

If you read the bash man page, it basically confirms what you have stated:

If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion.

However, reading on from there:

The exceptions to this are the expansions of ${!prefix*} and ${!name[@]} described below.

${!prefix*} Names matching prefix. Expands to the names of variables whose names begin with prefix, separated by the first character of the IFS special variable.

In other words, your particular example ${!N*} is an exception to the rule you quoted. It does, however, work as advertised in the expected cases, such as:

$ export xyzzy=plugh ; export plugh=cave

$ echo ${xyzzy} # normal, xyzzy to plugh
plugh

$ echo ${!xyzzy} # indirection, xyzzy to plugh to cave
cave


Related Topics



Leave a reply



Submit