1

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
4
  • What is your BASH version? Commented Apr 6, 2015 at 14:34
  • Using BASH 4 but would be nice if the solution would work with version 3 if possible. Commented Apr 6, 2015 at 16:27
  • BTW, all-uppercase variable names for non-exported shell variables is bad form. Per the POSIX spec (pubs.opengroup.org/onlinepubs/009695399/basedefs/…): "The name space of environment variable names containing lowercase letters is reserved for applications"; since environment and local variables share a namespace, using lower-case names prevents overwriting environment variables and builtins by accident. Commented Apr 6, 2015 at 17:11
  • As an aside -- the function keyword is best avoided. It has no semantic use -- function foo() { ...; } has precisely the same effect as foo() { ...; } in bash -- but needlessly makes code incompatible with POSIX sh. Commented Apr 6, 2015 at 22:03

2 Answers 2

6

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
Sign up to request clarification or add additional context in comments.

16 Comments

Not just 4+, but 4.3+; that said, when targeting a new enough bash, this is certainly the best choice.
I believe it is declare -n (reference variable) that was added in 4.3
I'd suggest using read -r if not wanting to have side effects on the data (expanding backslash escape sequences).
Quoting [:lower:] and [:upper:] may also be appropriate, to prevent those strings from being glob-matched before being passed to tr. I note that I made the same mistake there myself.
@anubhava, if we don't want to truncate at the first newline in the string. IFS controls characters used for trimming words, and characters considered whitespace to be trimmed from the end; -d controls the characters which ends the content read by a single invocation of read. Compare IFS= read -r <<<$'one\ntwo'; printf '%q\n' "$REPLY" and IFS= read -r -d '' <<<$'one\ntwo'; printf '%q\n' "${REPLY%$'\n'}".
|
1

anubhava's answer is ideal for bash 4.3 or newer. To support bash 3, one can use eval (very cautiously, with strings generated with printf %q) to replace the use of namevars, and tr to replace the ${foo^^} expansion for upper case:

toUpper() {
  declare -a indexes
  local cmd idx item result
  printf -v cmd 'indexes=( "${!%q[@]}" )' "$1"; eval "$cmd"
  for idx in "${indexes[@]}"; do
     printf -v cmd 'item=${%q[%q]}' "$1" "$idx"; eval "$cmd"
     result=$(tr '[:lower:]' '[:upper:]' <<<"$item")
     printf -v cmd '%q[%q]=%q' "$1" "$idx" "$result"; eval "$cmd"
  done
}

1 Comment

(Notably, in newer versions of bash, printf -v can assign directly to an array element; however, that's a 4.2 feature, not available in 3.x -- but still can be helpful if one isn't quite running 4.3).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.