3

I have a string tstArr2 which has the following content

'3 5 8'

Now in awk I want to parse a flat file

test my array which array is better array

INDIA USA SA NZ AUS ARG GER BRA
US AUS INDIA ENG NZ SRI PAK WI BAN NED IRE

at these numbered columns only. I tried the following

awk -vA="$tstArr2" 'BEGIN{split(A,B," ");} {if(NR>1){for(i=1; i<= length(B); i++){printf "%s ",B[i]}}print " "}' testUnix3.txt

But it says

awk: Cannot read the value of  B. It is an array name.

The input line number is 2. The file is testUnix3.txt.
The source line number is 1.

What am I missing ? If I try the following

awk -vA="$tstArr2" 'BEGIN{split(A,B," ");} {if(NR>1){for(i in B){printf "%s ",$B[i]}}print " "}' testUnix3.txt

it prints the outputs but they are not in order. I want them to be in order. Please explain. Desired output :

 SA AUS BRA

 INDIA NZ WI
1
  • I cannot reproduce the error you see with the first code sample. What version of awk are you using?
    – John1024
    Nov 20, 2014 at 8:02

3 Answers 3

5

POSIX defined length in awk is a string function, argument taken as a string. Using length with an array as argument is unspecified behavior.

In some implementations of awk like gawk (version >= 3.1.6), OS X version of AWK, you can use length with an array as argument, it will return number of elements in array.

Array in awk is associative array, looping through associative array does not guarantee anything about the order. In this case, you can take advantage of split function, which return number of fields to get the number elements of array.

POSIXly, you can try:

$ awk -vA="$tstArr2" '
  BEGIN{n = split(A,B," ");}
  {
    if(NR > 1) {
      for(i = 1;i <= n;i++) {
        printf "%s ",$B[i];
      }
    }
    print " ";
  }
' file

SA AUS BRA  
INDIA NZ WI
1
  • Oh !! Superb explanation . Glad you helped me through.
    – recmach
    Nov 20, 2014 at 8:32
2

(For non-GNU awk, please see @cuonglm's answer.)

With this as the test file:

$ cat testUnix3.txt 
test my array which array is better array

INDIA USA SA NZ AUS ARG GER BRA
US AUS INDIA ENG NZ SRI PAK WI BAN NED IRE

This code selects columns 3, 5, and 8:

$ tstArr2='3 5 8'
$ awk  -vA="$tstArr2" 'BEGIN{split(A,B," ");} NR>1{for(i=1; i<= length(B); i++) printf "%s ",$B[i]; print "";}' testUnix3.txt

SA AUS BRA 
INDIA NZ WI 

The above was tested with GNU awk.

awk loops and order

awk has associative arrays. As the Grymoire awk tutorial explains:

There is one minor problem with associative arrays, especially if you use the for command to output each element: you have no control over the order of output.

That is the reason why the other code that you cited, which uses a for(i in B) loop, may, at times, print columns out of order.

GNU awk has an extension to overcome this issue:

$ gawk -vA="$tstArr2" 'BEGIN{split(A,B," "); PROCINFO["sorted_in"]="@ind_num_asc"} {if(NR>1){for(i in B){printf "%s ",$B[i]}print " "}}' testUnix3.txt

SA AUS BRA  
INDIA NZ WI  

By setting PROCINFO["sorted_in"]="@ind_num_asc", the indices will be looped over in numeric ascending order. This GNU extension is documented here.

2
  • Using length with an array as argument is not defined by POSIX, see my answer.
    – cuonglm
    Nov 20, 2014 at 8:05
  • @cuonglm I think that you have correctly identified the OP's problem with the first code sample. My tests were done with GNU awk.
    – John1024
    Nov 20, 2014 at 8:06
1

There is much overhead to use so powerful instrument as awk for such simple task:

tstArr2='3,5,8'
tail -n+2 testUnix3.txt | cut -d' ' -f"$tstArr2"
1
  • nice and smooth one!!
    – recmach
    Nov 21, 2014 at 5:57

You must log in to answer this question.

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