17

I am implementing as below code using for loop but wrong output coming after running the script.

for i in `awk -F"|" '{print $1}' $INPUTFILE`, j in `awk -F"|" '{print $2}' $INPUTFILE`
do
echo $i:$j
done  

Please help me to use multiple variables in single for loop in shell script.

1

9 Answers 9

25

bash allows for loops with more than one variable, but only in C like syntax:

for ((i=0,j=10;i<=j;i++,j--))
do
   echo "i=$i"
   echo "j=$j"
done
2
  • 2
    how are i and j read from $INPUTFILE ?
    – Archemar
    Commented Apr 28, 2016 at 15:43
  • 1
    @Archemar They're not. The question, I believe, is wrong. Using for loops on awk is usually wrong as you can usually accomplish whatever you wanted in pure awk, and see Stéphane Chazelas's answer. I just answered the question in the title.
    – Dani_l
    Commented Apr 28, 2016 at 16:03
24

Maybe you actually want:

while IFS='|' read -r i j rest <&3; do
  {
    printf '%s\n' "something with $i and $j"
  } 3<&-
done 3< "$INPUTFILE"

But using a shell loop to process text is often the wrong way to go.

Here, it sounds like you just need:

awk -F '|' '{print $1 ":" $2}' < "$INPUTFILE"

Now as an answer to the question in the title, for a shell with for loops taking more than one variable, you've got zsh (you seem to already be using zsh syntax by not quoting your variables or not disabling globbing when splitting command substitution):

$ for i j in {1..6}; do echo $i:$j; done
1:2
3:4
5:6

Or the shorter form:

for i j ({1..6}) echo $i:$j

The equivalent with POSIX shells:

set -- 1 2 3 4 5 6
## or:
# IFS='
# ' # split on newline
# set -o noglob # disable globbing
# set -- $(awk ...) # split the output of awk or other command
while [ "$#" -gt 0 ]; do
  printf '%s\n' "$1:$2"
  shift 2
done
2
  • Is there any way to do it without using a new fd (<&3) and what does - after that do? Asking out of curiosity.
    – user80158
    Commented Aug 3, 2017 at 0:57
  • @sdkks, you can use any fd you like (between 0 and 9, some shells allowing more, some even allowing allocating them dynamically), but it's better to avoid 0, 1 and 2 as those are special. That's stdin, stdout and stderr and the commands inside the loop may need to use them (in the case of printf only 1 and possibly 2 for errors). 3<&- is the syntax to close a fd. We're closing 3 for the commands inside the loop as they don't need access to (and shouldn't use) that resource. Commented Aug 3, 2017 at 8:25
6

You can store an output of a file in a variable and then break it into two. example:

in example file contents are:

a:1
b:2
c:3
d:4

now script.

#!/bin/bash
for n in $(cat example)
do
    first=$(echo $n | cut -d ":" -f 1)
    second=$(echo $n | cut -d ":" -f 2)
    echo $first
    echo $second
done
0
6
$ cat inputfile.txt
foo|bar
baz|foobar
$ cat inputfile.txt | while IFS='|' read i j ; do echo $i:$j ; done
foo:bar
baz:foobar

this answer is a lot like this answer but it also answers the comment about not having to use file descriptors (<&3) by @sdkks

1
  • I needed to iterate over input from another process, receiving lines over the network, and I'm not sure how to attach a process to a file descriptor, so this was helpful to me.
    – adfaklsdjf
    Commented Aug 7, 2020 at 18:58
2

On embedded systems, you are often crippled on what is available. EG. for can only have one variable; awk & printf are simply not available; or IFS is not supported; etc.

In these circumstances you can achieve your goal like this:

export i=xxx
for j in `cat INPUTFILE` ; do \
    [ $i == xxx ] && export i=$j && continue ; \
    echo "$i:$j" ; \
    export i=xxx ; \
done
unset i

A more general form of this solution might look like:

export A=xxx
export B=xxx
for i in 1 2 3 4 5 6 7 8 9 10 11 ; do \
    [ $A == xxx ] && export A=$i && continue ; \
    [ $B == xxx ] && export B=$i && continue ; \
    echo "$A:$B:$i" ; \
    export A=xxx ; \
    export B=xxx ; \
done
unset A
unset B
  • Assumes the string "xxx" never appears in your data-set ...If it does/might, pick something else.
  • You can have any number of "fields" per "record" ...Just duplicate each of the FOUR lines which handle the fields {A, B, ...}.
  • The last field is always called $i ...Feel free to add an export C=$i just before the echo if it helps.
  • If the number of strings in the data-set is not a multiple of the record-size, the "remainder" (ie. |set| % |record|) will be lost.
0
1

GNU Parallel can often be a solution for this:

parallel -a $INPUTFILE --colsep '|' echo {1}:{2}
1

The following can be used as a one-liner inside the bash shell to iterate over two sets of variables in parallel (as zip(a, b) would do in python):

a=(x y z); b=(q w e); for i in ${!a[@]}; do echo ${a[i]}-${b[i]}; done
0

You'll need multiple loops if you want to iterate over more than one variable, like for i in 1 2 3; do for j in a b c; do ...; done; done. when the list of things to iterate over is the output of a another command, you'll need to wrap that other command in $().

2
  • I am not sure OP want a cross product, more likely OP want i,j from first line of $INPUTFILE, then i,j from second line and so on.
    – Archemar
    Commented Apr 28, 2016 at 15:42
  • I opted for not trying to parse the awk parts of the question, but after re-reading it, I think you're right an that Stephane's answer is right on. Commented Apr 28, 2016 at 17:10
0
#! /bin/bash

num="num.txt"
ch="char.txt"
int1=0

for i in `cat $num`
do
 int1=$(( $int1+1 ))

 for j in `cat $ch|tail -n +$int1`
 do
  echo $i " " $j
 break
 done

done

Output:

1   a
2   b
3   c
4   d
5   e
6   f
7   g
1
  • 2
    Note that the user in the question has a single input file with two |-delimited fields and that they try to output the two fields with a : in-between them.
    – Kusalananda
    Commented Dec 24, 2021 at 9:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .