2

Consider the below variables which are dynamic and might change each time. Sometimes there might even be 5 variables, But the length of all the variables will be the same every time.

var1='a b c d e... upto z'
var2='1 2 3 4 5... upto 26'
var3='I II III IV V... upto XXVI'

I am looking for a generalized approach to iterate the variables in a for loop & My desired output should be like below.

a,1,I
b,2,II
c,3,III
d,4,IV
e,5,V
.
.
goes on upto
z,26,XXVI

If I use nested loops, then I get all possible combinations which is not the expected outcome.

Also, I know how to make this work for 2 variables using for loop and shift using below link https://unix.stackexchange.com/questions/390283/how-to-iterate-two-variables-in-a-sh-script

5
  • 1
    There's really no good way to do this in POSIX shell. In shells that support arrays, you can create 3 separate arrays and iterate over the indices. You should probably look into using a different language (with decent data structure support) if you need this kind of "zipping" behavior. Commented Oct 7, 2021 at 14:34
  • Are the fields separated by spaces? If yes you could assign these values to bash indexed arrays instead (var1=( a b c d e...z )) and use a loop over the indexes. Have a look at this other question maybe. Commented Oct 7, 2021 at 14:34
  • Does this answer your question? The ever changing variable Commented Oct 7, 2021 at 15:19
  • @AndrejPodzimek: No, that was too complex when comparing with my requirement. The paste option did it for me. Thanks for your response! Commented Oct 7, 2021 at 16:14
  • @AdarshR Indeed, paste solves this nicely, but I think the “nameref” solution in Bash also has its qualities — in particular, it is much easier to scale to an arbitrary number of variables (and requires just one command-line token per variable). I’ve added an answer showing what I mean. Commented Oct 7, 2021 at 19:02

4 Answers 4

5

With paste

paste -d , <(tr ' ' '\n' <<<"$var1") <(tr ' ' '\n' <<<"$var2") <(tr ' ' '\n' <<<"$var3")
a,1,I
b,2,II
c,3,III
d,4,IV
e...z,5...26,V...XXVI

But clearly having to add other parameter substitutions for more varN's is not scalable.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot. That was really simple & worked like a charm!
2

You need to "zip" two variables at a time.

var1='a b c d e...z'
var2='1 2 3 4 5...26'
var3='I II III IV V...XXVI'

zip_var1_var2 () {
    set $var1
    for v2 in $var2; do
        echo "$1,$v2"
        shift
    done
}

zip_var12_var3 () {
    set $(zip_var1_var2)
    for v3 in $var3; do
        echo "$1,$v3"
        shift
    done
}

for x in $(zip_var12_var3); do
  echo "$x"
done

If you are willing to use eval and are sure it is safe to do so, you can write a single function like

zip () {
    if [ $# -eq 1 ]; then
        eval echo \$$1
        return
    fi

    a1=$1
    shift
    x=$*

    set $(eval echo \$$a1)
    for v in $(zip $x); do
        printf '=== %s\n' "$1,$v" >&2
        echo "$1,$v"
        shift
    done
}

zip var1 var2 var3  # Note the arguments are the *names* of the variables to zip

If you can use arrays, then (for example, in bash)

var1=(a b c d e)
var2=(1 2 3 4 5)
var3=(I II III IV V)

for i in "${!var1[@]}"; do
    printf '%s,%s,%s\n' "${var1[i]}" "${var2[i]}" "${var3[i]}"
done

1 Comment

I tried out implementing the arrays option with no luck. I might be missing something or we may be using a POSIX shell. Anyway, Thanks a lot for your response. Much appreciated!
0

Use this Perl one-liner:

perl -le '@in = map { [split] } @ARGV; for $i ( 0..$#{ $in[0] } ) { print join ",", map { $in[$_][$i] } 0..$#in; }' "$var1" "$var2" "$var3"

Prints:

a,1,I
b,2,II
c,3,III
d,4,IV
e,5,V
z,26,XXVI

The Perl one-liner uses these command line flags:
-e : Tells Perl to look for code in-line, instead of in a file.
-l : Strip the input line separator ("\n" on *NIX by default) before executing the code in-line, and append it when printing.

The input variables must be quoted with double quotes "like so", to keep the blank-separated words from being treated as separate arguments.
@ARGV is an array of the command line arguments, here $var1, $var2, $var3.
@in is an array of 3 elements, each element being a reference to an array obtained as a result of splitting the corresponding element of @ARGV on whitespace. Note that split splits the string on whitespace by default, but you can specify a different delimiter, it accepts regexes.
The subsequent for loop prints @in elements separated by comma.

SEE ALSO:
perldoc perlrun: how to execute the Perl interpreter: command line switches
perldoc perlvar: Perl predefined variables

Comments

0

The following is (almost) a copy of this answer with a few tweaks that make it fit this question.

The Original Question

First let’s assign a few variables to play with, 26 tokens in each of them:

var1="$(echo {a..z})"
var2="$(echo {1..26})"
var3="$(echo I II III IV \
             V{,I,II,III} IX \
             X{,I,II,III} XIV \
             XV{,I,II,III} XIX \
             XX{,I,II,III} XXIV \
             XXV XXVI)"
var4="$(echo {A..Z})"
var5="$(echo {010101..262626..10101})"

Now we want a “magic” function that zips an arbitrary number of variables, ideally in pure Bash:

zip_vars var1       # a trivial test
zip_vars var{1..2}  # a slightly less trivial test
zip_vars var{1..3}  # the original question
zip_vars var{1..4}  # more vars, becasuse we can
zip_vars var{1..5}  # more vars, because why not

What could zip_vars look like? Here’s one in pure Bash, without any external commands:

zip_vars() {
  local var
  for var in "$@"; do
    local -a "array_${var}"
    local -n array_ref="array_${var}"
    array_ref=(${!var})
    local -ar "array_${var}"
  done
  local -n array_ref="array_${1}"
  local -ir size="${#array_ref[@]}"
  local -i i
  local output
  for ((i = 0; i < size; ++i)); do
    output=
    for var in "$@"; do
      local -n array_ref="array_${var}"
      output+=",${array_ref[i]}"
    done
    printf '%s\n' "${output:1}"
  done
}

How it works:

  1. It splits all variables (passed by reference (by variable name)) into arrays. For each variable varX it creates a local array array_varX.
    • It would be actually way easier if the input variables were already Bash arrays to start with (see below), but … we stick with the original question initially.
  2. It determines the size of the first array and then blindly expects all arrays to be of that size.
  3. For each index i from 0 to size - 1 it concatenates the ith elements of all arrays, separated by ,.

Arrays Make Things Easier

If you use Bash arrays from the very start, the script will be shorter and look simpler and there won’t be any string-to-array conversions.

zip_arrays() {
  local -n array_ref="$1"
  local -ir size="${#array_ref[@]}"
  local -i i
  local output
  for ((i = 0; i < size; ++i)); do
    output=
    for arr in "$@"; do
      local -n array_ref="$arr"
      output+=",${array_ref[i]}"
    done
    printf '%s\n' "${output:1}"
  done
}

arr1=({a..z})
arr2=({1..26})
arr3=(    I II III    IV
       V{,I,II,III}   IX
       X{,I,II,III}  XIV
      XV{,I,II,III}  XIX
      XX{,I,II,III} XXIV
                    XXV
                    XXVI)
arr4=({A..Z})
arr5=({010101..262626..10101})

zip_arrays arr1       # a trivial test
zip_arrays arr{1..2}  # a slightly less trivial test
zip_arrays arr{1..3}  # (almost) the original question
zip_arrays arr{1..4}  # more arrays, becasuse we can
zip_arrays arr{1..5}  # more arrays, because why not

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.