7

I want to create a function which iterates through an array and inserts the given value, if its not yet included. I am using this function on two different parts of my code and so I have to use different arrays. Therefore I am delivering the array names to the function. Now I can't figure out how to allocate a slot of the array to store the element at this place.

Here is my code:

name=("hello" "world")

function f {
    array_name=$2[@]
    array=("${!array_name}")
    length=${#array_name[@]}
    for (( i = 0; i <= $length; i++ )); do
        if [[ "${array[i]}" = "$1" ]];
            break;
        fi
        if [[ "$i" = "$length" ]]; then
            ${2[$length+1]=$1};
        fi
    done
}

f "test" "name"

Edit: I want the array to append the given value so something like this

for i in ${name[@]}
do
    echo $i
done

would have this output:

hello
world
test

but apparently "${2[$length+1]=$1}" is not working.

(Idea for passing the array is from here: bash how to pass array as an argument to a function)

Community
  • 1
  • 1
telina
  • 166
  • 1
  • 1
  • 11
  • I want to store the value of my parameter 1 at the position length+1 of the array in parameter 2. – telina Apr 22 '14 at 13:03
  • 1
    Interesting question, but missing a key definition, required output from sample input. (please add to question above, not as a comment). Good luck. – shellter Apr 22 '14 at 13:04

1 Answers1

6

If I understand correctly you want to append a value to an array if this value is not yet in the array, but the tricky part is that the array name is passed to the function.

Method 1

One possibility is to see the problem from a different viewpoint: you can pass to the function the value to be inserted and the full array, and the function will set a global variable that you will recover after its execution. For our test example we'll use:

array=( hello world "how are you today?" )

and we'll try to insert test and hello (so that hello will not be inserted):

f() {
# This function will set the global variable _f_out to an array obtained from
# the positional parameters starting from position 2, with $1 appended if and
# only if $1 doesn't appear in the subsequent positional parameters
    local v=$1 i
    shift
    _f_out=( "$@" )
    for i; do [[ $i = $v ]] && return; done
    _f_out+=( "$v" )
}

Let's use it:

$ array=( hello world "how are you today?" )
$ f test "${array[@]}"
$ array=( "${_f_out[@]}" )
$ printf '%s\n' "${array[@]}"
hello
world
how are you today?
test
$ f hello "${array[@]}"
$ array=( "${_f_out[@]}" )
$ printf '%s\n' "${array[@]}"
hello
world
how are you today?
test

It works.

Remark. I used for i; do. It's a nice shortcut for for i in "$@"; do.

Method 2

You really want to fiddle with simili-pointers and do the appending in place (this is not really in the spirit of Bash—that's why it's a bit clumsy). The tool for that is to use printf with the -v option: from help printf:

-v var    assign the output to shell variable VAR rather than
          display it on the standard output

The good thing is that it also works with array fields.

Warning: you might see other methods that use eval. Avoid them like the plague!

f() {
    local array_name=$2[@] i
    local array=( "${!array_name}" )
    for i in "${array[@]}"; do [[ $i = $1 ]] && return; done
    # $1 was not found in array, so let's append it
    printf -v "$2[${#array[@]}]" '%s' "$1"
}

and let's try it:

$ array=( hello world "how are you today?" )
$ f test array
$ printf '%s\n' "${array[@]}"
hello
world
how are you today?
test
$ f hello array
$ printf '%s\n' "${array[@]}"
hello
world
how are you today?
test

It works too.

Note. With both methods you can very easily have a return code of the function, e.g., 0 (success) if the value was inserted, and 1 (failure) if the value was already there (or the other way round)—the adaptation is straightforward and left as an exercise. This might be useful in Method 1 to determine whether you need to update array to the returned value _f_out or not. In that case, you could even slightly modify f so that it doesn't even set _f_out when the value is already in the array.

Caveat. In both methods shown I assumed that your arrays have contiguous indices that start at 0 (i.e., non-sparse arrays). I think it's a safe assumption here; but if it is not the case, these methods are broken: the first one will (after reassignment) transform the array into a non-sparse one, and the second one might overwrite fields (as, for an array a, ${#a[@]} expands to the number of elements in the array, not the highest index + 1 found in the array).

gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • First: thank you! I think my point of view is more object oriented. I am new to bash so I had some troubles understanding your code. What I want to achieve is, that the global array, which name I am passing to the function, is actually used and the passed value 'test' gets appended to it. I wanted to do this by substituting the string. As far as I understand the first method will create a new global array and not expand the array I already created. In method 2 I don't understand how the value is appended to the array. I also just figured out that you can append by += instead of array[i]=. – telina Apr 22 '14 at 13:46
  • 1
    @telina In method 2 it's the line: `printf -v "$2[${#array[@]}]" '%s' "$1" }` that appends the value: if your array name is `ary` and has 42 elements, `"$2[${#array[@]}]"` expands to `ary[42]`. – gniourf_gniourf Apr 22 '14 at 13:49
  • 1
    @telina You can `+=` to append to an array, but this does not work with indirection so is not an option here. – Adrian Frühwirth Apr 22 '14 at 13:49