23

I am trying to replace the word "Apples" with "Cantaloupe" in my 'copy' array. What I am doing now is not throwing errors, but no change occurs in the copy array.

#!/bin/bash

fruits=("Oranges" "Apples" "Bananas" "Grapes")
echo "Original list:"
echo "${fruits[@]}"

copy=("${fruits[@]}")

for i in ${copy[@]}
do
        if [[ copy[$i] == "Apples" ]]; then
                copy[$i]="Canteloupe"
        fi
done

echo "Copied list:"
echo "${copy[@]}"

My output:

Original list:
Oranges Apples Bananas Grapes
Copied list:
Oranges Apples Bananas Grapes
nhershy
  • 643
  • 1
  • 6
  • 20

2 Answers2

34

In your original approach you are looping over the keys in the array using which you would not be able to get the index of that element to replace.

You need to change to modify the logic to loop over indices of the array as

for i in "${!copy[@]}"; do
    if [[ ${copy[$i]} == "Apples" ]]; then
        copy[$i]="Canteloupe"
    fi
done

should solve your problem.

The construct for i in "${!copy[@]}"; do is for looping with indices of the array starting from 0 to size of the array which lets you to replace the element in the index where you find the required string.


Expanding the answer to point out the difference when using either of array iteration ways.

Looping over indices

for i in "${!copy[@]}"; do 
  printf "%s\t%s\n" "$i" "${copy[$i]}"
done

prints

0       Oranges
1       Apples
2       Bananas
3       Grapes

and over keys

for i in "${copy[@]}"; do 
  printf "%s\n" "$i"
done

produces,

Oranges
Apples
Bananas
Grapes
Inian
  • 80,270
  • 14
  • 142
  • 161
  • 1
    That did indeed work. So it is the exclamation point that determines if you are looping indices vs keys? Would you mind clarifying what the difference between all of these are: "${copy[@]}", ${copy[@]}, $copy[@], copy[@] – nhershy Jul 20 '17 at 19:47
  • Thank you for your wisdom, master Yoda. – Nate T Nov 07 '20 at 10:23
15

The solution explained in the accepted answer to this similar question might be preferable:

array=("${array[@]/Apples/Canteloupe}")

This depends on your general attitude towards Bash trickery. In technical terms, there are no downsides to manually iterating over the elements.

Sergey Shevchenko
  • 1,750
  • 14
  • 13
  • 1
    NB: If you have IFS=$'\n' (to handle names with whitespace) then this command will not keep the original array structure. It will just make array contain only 1 item (a string list), with all the changed strings. As seen if you run: `declare -p array` afterwards. – Magne Apr 28 '21 at 12:57
  • @Magne You are demonstrably wrong. First of all, IFS=$'\n' instructs bash to split strings on newlines, not "handle names with whitespace" (I'm not sure what you meant by "handle"). Secondly, the replacement command works just fine with that IFS value. For an array of 2 elements with spaces and newlines, `declare -a array=("Apples and \nPears" "Oranges\n and Apples")`, a `declare -p array` afterwards prints the expected `array=([0]=$'Canteloupe and \nPears' [1]=$'Oranges\n and Canteloupe')`. – Sergey Shevchenko May 04 '21 at 21:01
  • 1
    Try it with Bash 3.2. It is a bug there. Run `array=("Apples and \nPears" "Oranges\n and Apples"); IFS=$'\n' array=("${array[@]/Apples/Canteloupe}"); declare -p array;`. See comments at: https://stackoverflow.com/a/30971572/380607 (Apple shipped macOS with that version of Bash until they encouraged, but not enforced, users to upgrade in Catalina.) I said "handle whitespace" since someone might want to use IFS=$'\n' so that the input for each array element is not split into more elements if it contains whitespace (since IFS by default splits at space, tab and newline). – Magne May 05 '21 at 13:27
  • 1
    Gotcha, all cleared up now. I didn't know about the bug in the older Bash -- thanks. – Sergey Shevchenko May 11 '21 at 17:09