0

I am writing shell script, to extract 2 variables from a file, and have used below FOR loop.

for i j in `cat list`
                do
                echo title
                echo total "$i" "," "$j"
                echo end
done

where cat list is

AAA 111
BBB 222
CCC 333

Getting error as `j' unexpected, I have used "while loop" but no luck.

expect output is

title
total AAA,111
total BBB,222
total CCC,333
end
Prvt_Yadav
  • 5,882
Sheldon
  • 15

3 Answers3

4

A for or while loop actually isn't needed. Either of these awk commands will work:

awk 'BEGIN {print "title"}
           {print "total",$1","$2}
       END {print "end"}' list

or:

awk 'BEGIN {print "title"}
           {printf "total %s,%s\n", $1, $2}
       END {print "end"}' list

The BEGIN{print "title"} prints the word title once, before the data in the file is read.

{print "total",$1","$2} prints the word total followed by an unquoted comma representing a space between the values in the columns columns and then the columns themselves. This block is executed for each input line.

END{print "end"} prints the word end and the END of the file. END tells it to execute the block when the final input from the file has been read.

For the second command:

The only difference is:

{printf "total %s,%s\n", $1, $2}

This uses printf instead of print where the first argument defines the format.

%s,%s tells it to print the values of $1 and $2 delimited by a comma and the \n represents a new line so that it moves to the next line and prints the values of $1 and $2 until the end of the file.

Personally, I would use the second command with printf as you can just use one set of quotes instead of quoting both total and the comma. It is also more modular as you'll find from reading its man page. I'm only including the two commands to show the two different ways.

The output

title
total AAA,111
total BBB,222
total CCC,333
end
Kusalananda
  • 333,661
Nasir Riley
  • 11,422
  • If you're using awk to do the parsing of the file, then use awk to do all the parsing of the file. It doesn't make sense to call awk for each individual line when you could do awk 'BEGIN { print "title" } { printf("total %s,%s\n", $1,$2) } END { print "end" }' list – Kusalananda Mar 20 '19 at 06:29
  • @Kusalananda I would actually make that an answer as it entirely eliminates the need for the for loop. – Nasir Riley Mar 20 '19 at 11:07
  • There is nothing stopping you from adding it to your already existing answer :-) – Kusalananda Mar 20 '19 at 11:16
  • @Kusalananda Thanks for your help! Let me know if there's anything in there where I've made a mistake or if it can be explained better. – Nasir Riley Mar 20 '19 at 11:40
  • thanks @Kusalananda, Nasir.. given awk method works, i have choosen to go for while method. – Sheldon Mar 21 '19 at 09:23
2

You can use, while read:

echo "title"
while read -r i j
do
    echo total "$i,$j"
done < list
echo "end"

It will give output:

title
total AAA,111
total BBB,222
total CCC,333
end
ilkkachu
  • 138,973
Prvt_Yadav
  • 5,882
1

While your specific problem is much better addressed by using a text processing utility like awk as shown by @Nasir (see also Why is using a shell loop to process text considered bad practice?), I'll answer the question in the title.

The for i j in words...; do something with $i and $j; done is zsh syntax, not supported by bash yet.

Also bash performs both splitting (which you want) and globbing (which you don't want) upon `cmd` (the obsolete form of command substitution, you may want to switch to $(cmd)), while zsh only does splitting as you'd expect.

To process two words at a time with bash or other POSIX-like shells, instead of for i j in words..., you can do:

set -- words...
while [ "$#" -gt 0 ]; do
  i=$1 j=$2 # or j=${2-missing}
  something with "$i" and "$j"
  shift "$(( 2 - ($# == 1) ))"
done

In your case, the set would look like:

set -o noglob # disable globbing
set -- $(cat list)

(assumes an unmodified $IFS which by default contains space, tab and newline).