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.

DisplayName
  • 11,688

9 Answers9

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
Dani_l
  • 4,943
  • 2
    how are i and j read from $INPUTFILE ? – Archemar Apr 28 '16 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 Apr 28 '16 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
  • Is there any way to do it without using a new fd (<&3) and what does - after that do? Asking out of curiosity. –  Aug 03 '17 at 00: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. – Stéphane Chazelas Aug 03 '17 at 08: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
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

  • 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 Aug 07 '20 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.
1

GNU Parallel can often be a solution for this:

parallel -a $INPUTFILE --colsep '|' echo {1}:{2}
Ole Tange
  • 35,514
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
Ben Usman
  • 199
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 $().

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
αғsнιη
  • 41,407
  • 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 Dec 24 '21 at 09:50