129

I have an array of applications, initialized like this:

depends=$(cat ~/Depends.txt)

When I try to parse the list and copy it to a new array using,

for i in "${depends[@]}"; do
   if [ $i #isn't installed ]; then
      newDepends+=("$i")
   fi
done

What happens is that only the first element of depends winds up on newDepends.

for i in "${newDepends[@]}"; do
   echo $i
done

^^ This would output just one thing. So I'm trying to figure out why my for loop is is only moving the first element. The whole list is originally on depends, so it's not that, but I'm all out of ideas.

9
  • That looks fine to me. Are you sure your "isn't installed" test is working correctly? Commented Oct 17, 2013 at 3:09
  • 4
    Is there a typo in your question? depends will consist of 2 words, cat and ~/Depends, not the contents of ~/Depends.txt. Commented Oct 17, 2013 at 12:03
  • In depends=$(cat ~/Depends.txt) the variable depends is a scalar, not an array. Maybe you meant depends=( $(cat ~/Depends.txt) ) but that would be a bad way to try to populate depends vs readarray or read -a. Commented Jun 17, 2024 at 11:54
  • 1
    @Moberg quoting the $(cat foo) would create a single element in x containing the entire contents of the file foo, not an element per line of input as you'd get with readarray. Add a second line to foo, run each command to populate x and call declare -p x after each to see the difference. Commented Nov 1 at 13:36
  • 1
    @Moberg you might be interested in mywiki.wooledge.org/BashFAQ/… and stackoverflow.com/q/30988586/1745001 Commented Nov 1 at 13:47

12 Answers 12

214
a=(foo bar "foo 1" "bar two")  #create an array
b=("${a[@]}")                  #copy the array in another one 

for value in "${b[@]}" ; do    #print the new array 
echo "$value" 
done   
Sign up to request clarification or add additional context in comments.

6 Comments

Is it ok to just write b=(${a[@]}) ?
try to execute the command with your modifications , you will undestand why it's not correct to write without quoting... the value "foo 1" will not displayed like it shoudl but it will create more value for the array foo AND 1 , bar AND two , instead of "foo 1" and "bar two"
@Indicator You will have problems with spaces!
Also works for copying the command line arguments, as in b=("$@").
this changes the order of elements if some elements are empty.
|
67

The simplest way to copy a non-associative array in bash is to:

arrayClone=("${oldArray[@]}")

or to add elements to a preexistent array:

someArray+=("${oldArray[@]}")

Newlines/spaces/IFS in the elements will be preserved.

For copying associative arrays, Isaac's solutions work great.

4 Comments

his did not seem to work for me: ``` $ a1=( big harry man ); $ a2=( "${!a1[@]}" ); $ echo "${a2[0]}"; 0 $ echo "${a2[1]}"; 1; $ ```
hey @FelipeAlvarez - indeed the ! was superfluous, I've corrected my answer
this changes the order of elements if some elements are empty.
Thank you very much (you saved me from my absent-mindedness) for reminding that "${oldArray[@]}" is crucially different from "${oldArray[*]}" with regard to word expansion, i.e. that (from Bash manual) "${name[@]}" expands each element of name to a separate word whereas "${name[*]}" expands to a single word with the value of each array member separated by the first character of the IFS special variable .
12

The solutions given in the other answers won't work for associative arrays, or for arrays with non-contiguous indices. Here are is a more general solution:

declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
temp=$(declare -p arr)
eval "${temp/arr=/newarr=}"

diff <(echo "$temp") <(declare -p newarr | sed 's/newarr=/arr=/')
# no output

And another:

declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
declare -A newarr
for idx in "${!arr[@]}"; do
    newarr[$idx]=${arr[$idx]}
done

diff <(echo "$temp") <(declare -p newarr | sed 's/newarr=/arr=/')
# no output

3 Comments

Your first solution cannot become a function, because eval "declare -A newarr=…" will effectively declare a local array newarr shadowing the real variable from parent environment. It may be an option to remove declare -A so that only newarr=… is left, but there are escaping problems down that path.
Could you explain what you are doing in the one-line solution? What is the pipe in the parentheses for example? thanks
this worked great to keep the elements in order
6

Try this: arrayClone=("${oldArray[@]}")

This works easily.

1 Comment

but not for sparse arrays, that will compress a sparse array
3

You can copy an array by inserting the elements of the first array into the copy by specifying the index:

#!/bin/bash

array=( One Two Three Go! );
array_copy( );

let j=0;
for (( i=0; i<${#array[@]}; i++)
do
    if [[ $i -ne 1 ]]; then # change the test here to your 'isn't installed' test
        array_copy[$j]="${array[$i]}
        let i+=1;
    fi
done

for k in "${array_copy[@]}"; do
    echo $k
done

The output of this would be:

One
Three
Go!

A useful document on bash arrays is on TLDP.

1 Comment

The TLDP site is great, but bash has moved on significantly, and much of what it recommends is now considered bad style. Unless you are trying to be bash-2.x or dash/busybox compatible, you probably want to find a more recent reference. Hopefully they will update at some point.
3
array_copy() {
    set -- "$(declare -p $1)" "$2"
    eval "$2=${1#*=}"
}

# Usage examples:

these=(apple banana catalog dormant eagle fruit goose hat icicle)
array_copy these those
declare -p those

declare -A src dest
source=(["It's a 15\" spike"]="and it's 1\" thick" [foo]=bar [baz]=qux)
array_copy src dest
declare -p dest

Note: when copying associative arrays, the destination must already exist as an associative array. If not, array_copy() will create it as a standard array and try to interpret the key names from the associative source as arithmetic variable names, with ugly results.

Isaac Schwabacher's solution is more robust in this regard, but it can't be tidily wrapped up in a function because its eval step evaluates an entire declare statement and bash treats those as equivalent to local when they're inside a function. This could be worked around by wedging the -g option into the evaluated declare but that might give the destination array more scope than it's supposed to have. Better, I think, to have array_copy() perform only the actual copy into an explicitly scoped destination.

Comments

2

Problem is to copy array in function to be visible in parent code. This solution works for indexed arrays and if before copying are predefined as declare -A ARRAY, works also for associative arrays.

function array_copy
# $1 original array name
# $2 new array name with the same content
{
    local INDEX
    eval "
        for INDEX in \"\${!$1[@]}\"
        do
            $2[\"\$INDEX\"]=\"\${$1[\$INDEX]}\"
        done
    "
}

Comments

1

Starting with Bash 4.3, you can do this

$ alpha=(bravo charlie 'delta  3' '' foxtrot)

$ declare -n golf=alpha

$ echo "${golf[2]}"
delta  3

1 Comment

This answer is incorrect. While declare -n is useful, it only creates a reference to the original array, it doesn't copy it, which is what the question is asking about.
0

Managed to copy an array into another.

firstArray=()
secondArray=()

firstArray+=("Element1")
firstArray+=("Element2")

secondArray+=("${firstArray[@]}")

for element in "${secondArray[@]}"; do
  echo "${element}"
done

Comments

0

Isaac's solution (with declare -p) is quite good, but it has an unnecessary subshell. We can use an obscure bash feature to avoid that:

declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
temp="${arr[@]@A}"
eval "${temp/arr=/newarr=}"

If one wanted to avoid eval, it's also possible to do it like this:

declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
temp="${arr[@]@A}"
declare -A newarr="${temp#*=}"

This has the advantage of making it easier to add more flags to the declare, such as -g.

If you do not want to hardcode the fact that the array is associative, you can do the following:

declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
temp="${arr[@]@A}"
declare -${arr@a} newarr="${temp#*=}"

Comments

0

I'm going to one-up this and add indirection with a sweet, safe and, simple solution as long as you have a recent enough version of bash:

eval "$varname=("${srcarr[@]@Q}")"

If you want this as a safe shell, general-purpose function then it gets a little more complicated as you should first check for a valid variable name

# Meets IEEE 1003.1-2017 §3.235 Name
is_posix_name() {
    local LC_CTYPE=POSIX
    for n in "$@"; do
        case "$n" in ""|[0-9]*|*[!A-Za-z0-9_]*) return 1;; esac
    done
}

copyarr() {
    local dest="$1"
    is_posix_name || {
        printf "Invalid variable name '%s\n" "$dest" >2
        return 1
    }
    shift
    eval "$dest=("${@@Q}")"
}

src=("jack and" "jill went up" the hill)
copyarr mycopy "${src[@]}"

Comments

-2

I've found that this works for me (mostly :)) ...

eval $(declare -p base | sed "s,base,target,")

extending the sed command to edit any switches as necessary e.g. if the new structure has to be writeable, to edit out read-only (-r).

Comments

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.