Passing variable name as param to function in bash

794 Views Asked by At

I am trying to read up 3 similar files with different names to different arrays. Because i didn't want to use unnecessary code i am trying to create functions that would accept array names as params, but i am getting error 'command not found'.

hello.sh file code:

    #!/bin/bash
declare -a row_1
declare -a row_2
declare -a row_3


load_array()
{
ROW="$2"
let i=0
while read line; do
    for word in $line; do
    $ROW[$i]=$word        
((++i))
    done
done < $1
}

load_array $1 row_1
load_array $2 row_2
load_array $3 row_3

Calling this file from terminal with: sh hello.sh 1.txt 2.txt 3.txt

List of errors i am getting:

hello.sh: line 13: row_1[0]=9: command not found
hello.sh: line 13: row_1[1]=15: command not found
hello.sh: line 13: row_1[2]=13: command not found
hello.sh: line 13: row_2[0]=12: command not found
hello.sh: line 13: row_2[1]=67: command not found
hello.sh: line 13: row_2[2]=63: command not found
hello.sh: line 13: row_3[0]=75: command not found
hello.sh: line 13: row_3[1]=54: command not found
hello.sh: line 13: row_3[2]=23: command not found
2

There are 2 best solutions below

5
On

In the assignment syntax, what is to the left of the equal sign must be either a variable name (when assigning to a scalar), or a variable name followed by a word in square brackets (when assigning to an array element). In your code, $ROW[$i]=$word doesn't match this syntax (there's a $ at the beginning, so it can't possibly be an assignment); it's just a word that happens to contain the character =.

In bash, you can use the declare builtin to assign to a variable whose name is the result of some computation such as a variable expansion. Note that unlike for a straight assignment, you do need double quotes around the value part to prevent word splitting and filename expansion on $word. Pass the option -g if you're doing the assignment in a function and you want the value to remain after the function returns.

declare -g $ROW[$i]="$word"

If you're running an old version of bash that doesn't have declare -g, you can use eval instead. This would be the method to use to assign to a dynamically-named variable in plain sh. Take care of quoting things properly: if ROW is e.g. row_1, then the string passed as the argument to eval must be row_1[$i]=$word (the shell command to parse and execute).

eval "$ROW[\$i]=\$word"
0
On

The ideal way to do this with modern (bash 4.3+) syntax is thus:

load_array() {
  declare -n _load_array__row=$2
  declare -a _load_array__line
  _load_array__row=( )
  while read -r -a _load_array__line; do
    _load_array__row+=( "${_load_array__line[@]}" )
  done <"$1"
}

(The variable names are odd to reduce the chance of collisions with the calling function; the other answers you're given will have trouble if asked to load content into a variable named ROW or line, for instance, as they'll be referring to local variables rather than global ones in describing their destinations).


A similar mechanism compatible with bash 3.2 (the ancient release shipped by Apple), avoiding the performance hit associated with inner loops and the bugs associated with glob expansion (see what your original code does to a line containing *!) follows:

load_array() {
  local -a _load_array__array=( )
  local -a _load_array__line
  while read -r -a _load_array__line; do
    _load_array__array+=( "${_load_array__line[@]}" )
  done <"$1"
  eval "$2=( \"\${_load_array__array[@]}\" )"
}