Search code examples
arraysbashpass-by-referenceindirection

Update array passed by reference with BASH


I would like to write a function that takes an array variable name and updates the contents. For example:

ARRAY1=("test 1" "test 2" "test 3")
toUpper ARRAY1

for arg in "${ARRAY1[@]}"; do
  echo "arg=$arg"
done

# output
arg=TEST 1
arg=TEST 2
arg=TEST 3

I have a crude attempt at doing this which requires a copy of the input array. Using indirect references, I am able to create a copy of the input variable. The copy of the array is used to get the count of the elements. If there is a better way to do this please let me know.

function toUpper() {
  local ARRAY_NAME=$1
  local ARRAY_REF="$ARRAY_NAME[@]"
  # use an indirect reference to copy the array so we can get the count
  declare -a ARRAY=("${!ARRAY_REF}")

  local COUNT=${#ARRAY[@]}

  for ((i=0; i<$COUNT; i++)); do
    local VAL="${ARRAY[$i]}"
    VAL=$(echo $VAL | tr [:lower:] [:upper:])
    echo "ARRAY[$i]=\"$VAL\""
    eval "$ARRAY_NAME[$i]=\"$VAL\""
  done
}

ARRAY1=( "test" "test 1" "test 3" )

toUpper ARRAY1

echo
echo "Printing array contents"
for arg in "${ARRAY1[@]}"; do
  echo "arg=$arg"
done

Solution

  • Using BASH 4.3+ you can do

    arr=( "test" "test 1" "test 3" )
    toUpper() { declare -n tmp="$1"; printf "%s\n" "${tmp[@]^^}"; }
    
    toUpper arr
    TEST
    TEST 1
    TEST 3
    

    Update: To reflect the changes in original array:

    toUpper() {
       declare -n tmp="$1"; 
       for ((i=0; i<"${#tmp[@]}"; i++)); do
          tmp[i]="${tmp[i]^^}"
        done;
    }
    
    arr=( "test" "test 1" "test 3" )
    toUpper arr
    printf "%s\n" "${arr[@]}"
    TEST
    TEST 1
    TEST 3
    

    Update2: Here is a way to make it work in older BASH (prior to 4) versions without eval:

    upper() {
       len=$2
       for ((i=0; i<len; i++)); do
          elem="${1}[$i]"
          val=$(tr '[:lower:]' '[:upper:]' <<< "${!elem}")
          IFS= read -d '' -r "${1}[$i]" < <(printf '%s\0' "$val")
       done;
    }
    
    arr=( "test" "test 1" "test 3" )
    upper arr ${#arr[@]}
    printf "%s\n" "${arr[@]}"
    TEST
    TEST 1
    TEST 3