How to merge two arrays in a zipper like fashion in Bash?

10.5k views Asked by At

I am trying to merge two arrays into one in a zipper like fashion. I have difficulty to make that happen.

array1=(one three five seven)
array2=(two four six eight)

I have tried with nested for-loops but can't figure it out. I don't want the output to be 13572468 but 12345678.

The actual script I am working on is here (http://ix.io/iZR).. but it is obviously not working as intended. I either get the whole of array2 printed (ex. 124683) or just the first index like if the loop didn't work (ex. 12325272).

So how do I get the output:

one two three four five six seven eight

with above two arrays?

Edit: I was able to solve it with two for-loops and paste (http://ix.io/iZU). It would still be interesting to see if someone have a better solution. So if you have time please take a look.

6

There are 6 answers

2
RTLinuxSW On BEST ANSWER

Assuming both arrays are the same size,

unset result
for (( i=0; i<${#array1[*]}; ++i)); do
    result+=( "${array1[$i]}" "${array2[$i]}" )
done
1
Micah Elliott On

I've found more common the case where I want to zip two arrays into two columns. This isn't as natively Zsh as the "RTLinuxSW" answer, but for this case, I use paste.

% tabs 16
% paste <(print -l $array1) <(print -l $array2)
one     two
three   four
five    six
seven   eight

And then can shove that into another array to get the intended output:

% array3=( `!!`«tab»«tab» )
% print $array3
one two three four five six seven eight
0
wjandrea On

This is based on RTLinuxSW's answer, with the improvement from Paused until further notice's comment, which adds support for sparse and associative arrays.

for index in "${!array1[@]}"; do  # Also, quote indices
    result+=( "${array1[$index]}" "${array2[$index]}" )
done

After:

$ echo "${result[@]}"
one two three four five six seven eight
$ declare -p result
declare -a result=([0]="one" [1]="two" [2]="three" [3]="four" [4]="five" [5]="six" [6]="seven" [7]="eight")

This assumes the indices of the two arrays are identical.

2
F1Linux On

< Update >This below solution is designed to work with data delimited by newlines: each value to be loaded into the array on a separate line in each file. Works perfectly as written, but if your data is organized differently, please see @Socowi 's alternative using paste with printf in the comments. Much thanks to @Socowi for both raising the issue & offering a workaround for data delimited in other ways! < / Update >

Here's another solution to interleave data from (2) arrays which are populated with data delimited by newlines in separate files. This solution uses paste, echo & xargs:

Array Data: I feed files to arrays because I like to disaggregate data from code. Following files with each value delimited by a newline will be consumed by readarray:

test1.txt:

one
three
five
seven

test2.txt:

two
four
six
eight

Put it all together:

#!/bin/bash

readarray arrayTest1 < /root/test1.txt
readarray arrayTest2 < /root/test2.txt

paste <( echo "${arrayTest1[*]}" ) <( echo "${arrayTest2[*]}" ) | xargs

Output:

one two three four five six seven eight
0
LozanoMatheus On

You can easily read the files, create an array with their content, check who is the bigger one and make the loop.

#!/usr/bin/env bash

## Writting a until d into the file file01 and writing 1 until 3 into the file file02.
echo {a..d} | tee file01
echo {1..3} | tee file02

## Declaring two arrays (FILE01 and FILE02) and a variable as integer.
declare -a FILE01=($(<file1))
declare -a FILE02=($(<file2))
declare -i COUNT=0

## Checking who is the biggest array and declaring the ARRAY_SIZE.
[[ "${#FILE01[@]}" -ge "${#FILE02[@]}" ]] && declare -i ARRAY_SIZE="${#FILE01[@]}" || declare -i ARRAY_SIZE="${#FILE02[@]}"

## Creating the loop (COUNT must be lesser or equal ARRAY_SIZE) and print each element of each array (FILE01 and FILE02).
while [ ${COUNT} -le ${ARRAY_SIZE} ]; do
  echo -n "${FILE01[$COUNT]} ${FILE02[$COUNT]} "
  ((COUNT++))
done

declare -a -> It creates an array

declare -i -> It declares the var as integer

${#FILE01[@]} -> It's to get the array size

0
darw On

In ZSH, it could be achieved via parameter expansion:

${array1:^array2}

as described in zshexpn(1):

${name:^arrayname}
${name:^^arrayname}
       Zips two arrays, such that the output array is twice as long 
       as the shortest (longest for `:^^') of name and arrayname…