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

6

19

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.

Ponton answered 9/6, 2015 at 0:46 Comment(8)
Why do you need nested for-loop? One loop level is enough: loop index up to the smaller of the two lengths.Nonagenarian
@Nonagenarian I thought I somehow could get it to work like that. I need the loops to build the indexes as I did in my solution.Ponton
Sorry, I don't read full scripts especially when they are not very readable. Not sure what you mean by "build the indices".Nonagenarian
@Nonagenarian The first loop will output the temp and condition (ex. 10°C \u2601). The second will output time for when that is valid (ex. 10:00). I use the loops to arrange them to the correct format I want. So I will have two "lists" of indices that I want zipper together so to speak.Ponton
"Two 'lists' of indices": I think most people say two arrays ("lists" is fine, although in Bash speak array is the right jagon) of elements. An "index" is a subscript. You meant "build the arrays", not "build the indices".Nonagenarian
@Nonagenarian Yes, well.. I meant building the indices of the arrays... but thought you would understand if I just said indices as we where talking about arrays.. The "lists" was just a way to rephrase and visualize it. Sorry if I was unclear on that.Ponton
What does this have to do with Zsh exactly? Are you looking for Zsh solutions as well as Bash?Towny
In general, tag only one shell or the other: zsh if an answer that works on zsh but doesn't work on bash will serve your needs; bash if an answer that works on bash but doesn't work on zsh will serve your needs. Neither of these shells is a proper superset of the other: bash accepts syntax zsh doesn't, zsh accepts syntax bash doesn't, and (much worse) there's a great deal of syntax and collected idioms correct on one but subtly buggy on the other, so it's actively harmful to categorize questions in such a way to lead folks who are looking for bash answers to use code written with zsh in mind.Petr
Y
13

Assuming both arrays are the same size,

unset result
for (( i=0; i<${#array1[*]}; ++i)); do
    result+=( "${array1[$i]}" "${array2[$i]}" )
done
Yesteryear answered 9/6, 2015 at 2:0 Comment(2)
Note that this approach assumes that the indices run from zero to the length of the array (minus one). Since Bash supports sparse arrays, this won't work. Also, it's not suitable for associative arrays. To iterate over the indices, use for index in ${!array1[@]}. Also, you should use [@] instead of [*] as a habit. The latter, if quoted, makes the array into one string separated by the first character of $IFS. Compare (IFS=XYZ; a=("red ball" "blue sky" "green grass"); for e in "${a[@]}"; do echo "$((i++)) $e"; done) to the same with an asterisk instead of an at sign.Fascia
@Paused Thanks! I posted my own answer incorporating your advice.Towny
B
5

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
Brownfield answered 11/6, 2015 at 18:3 Comment(1)
You can use the tr & cut to turn that into a one-liner: paste <(print -l $array1) <(print -l $array2) | tr -s "[:space:]" ' ' | cut -d' ' -f1-Bedight
T
4

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.

Towny answered 25/7, 2020 at 21:39 Comment(0)
W
2

< 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
Watercourse answered 27/3, 2020 at 17:55 Comment(2)
Using paste is a good idea. But at the moment the implementation is not tidy at all. It assumes that each entry ends with a newline and does not contain whitespace or backslashes. I think the following command should work reliably: paste -zd ' ' <(printf %s\\0 "${a[@]}") <(printf %s\\0 "${b[@]}") | tr '\0' ' ' (replace both ' ' with the output delimiter to be used. Note that there is always a trailing delimiter which can be removed by ... | head -c-1). Unfortunately, even this cannot be used to reliably read the output into an array again as paste cannot use a null byte for -d.Mikes
@Mikes Thanks so very much! I've incorporated your feedback into the post. Really appreciate you taking the time to work out an alternative solution that works with other data types! Very big thanks for all your feedback!Watercourse
P
2

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…
Peaceable answered 7/9, 2022 at 19:33 Comment(0)
H
0

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

Hunsaker answered 26/8, 2019 at 20:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.