How to print column1,column3 and column 9 to column12 of the csv using AWK command?

Viewed 125

This below command prints 9-12 column of the csv files perfectly

grep -i introd *.csv | awk 'BEGIN { first = 9; last = 12 }{ for (i = first; i < last; i++) {printf("%s ", $i) } print $last }

but I also want to print the column1, column3 and column 30-35.

4 Answers

With a small number of fields, it's easy to just do them explicitly, something like:

{ print $1","$3","$9","$10","$11","$12","$30","$31","$32","$33","$34","$35 }

That's the preferred solution if the number of fields is not too large.


If there are a larger number of fields (and you may, unlike me, consider twelve fields to be included in that), you can always create an associative array indicating which fields should be printed, including allowing for ranges like n-m. The code in this section of the answer illustrates that, by, first in the BEGIN section:

  • splitting the COLS variable on commas, each element is either a single value or range.
  • for each of those:
    • splitting on hyphens so that range bounds can be established.
    • if only one value, add that to the list of fields needed.
    • if more than one, use the first and second as inclusive bounds and add each field in that range.
  • work out the lowest and highest field needed.

Then, in the per-input-line section:

  • print out each field (from lowest to highest) if and only if it was one of the fields marked as needed.

The code to do that is:

BEGIN {
    split(COLS, ranges, ",")
    for (range in ranges) {
        split(ranges[range], values, "-")
        if (length(values) == 1) {
            use[values[1]] = 1
        } else {
            for (value = values[1]; value <= values[2]; value++) {
                use[value] = 1
            }
        }
    }

    hi = -1
    for (val in use) {
        if (hi  < 0) {
            lo = val
            hi = val
        } else if (val < lo) {
            lo = val
        } else if (val > hi) {
            hi = val
        }
    }
}
{
    sep = ""
    for (idx = lo; idx <= hi; idx++) {
        if (use[idx] == 1) {
            printf "%s%s", sep, $idx
            sep = ","
        }
    }
    print ""
}

This output shows how it works:

pax> echo 101 202 303 404 505 606 707 808 909 | awk -v COLS=2-4,7 -f myprog.awk
202,303,404,707

Just two notes. First, you cannot use that script to print duplicates of fields, such as 1-5,3-7. I haven't allowed for that since there's nothing in your question to indicate that's a requirement but, if you do need that capability, you'll need to use a different method.

Second, it's likely to produce unexpected results or fail spectacularly if you do not provide a COLS or provide one that's not in the expected format. I'll leave that as an exercise for the reader if they wish to make it truly bullet-proof :-)

Neither of those points were addressed since I still maintain the first option presented is the best for you particular scenario.

Here is a Generic solution, where you could pass a number of Ranges separated by , and range values should be separated by - within themselves. Also in case you want to pass multiple .csv file yes you can pass all of them like: *.csv to this program too.

awk -v range="9-12,30-35" '
BEGIN{
  FS=OFS=","
  num=split(range,arr,",")
}
{
  delete arr1
  value=""
  for(i=1;i<=num;i++){
    split(arr[i],arr1,"-")
    for(start=arr1[1];start<=arr1[2];start++){
       value=(value?value OFS:"")$start
    }
  }
  print value
}
'  Input_file

Explanation: Adding detailed explanation for above.

awk -v range="9-12,30-35" '         ##Starting awk program from here and setting range variable which has different ranges, in this case 9 to 12 AND 30 to 35 field numbers here.
BEGIN{                              ##Starting BEGIN section of this program from here.
  FS=OFS=","                        ##Setting FS and OFS as comma here.
  num=split(range,arr,",")          ##Splitting range to arr array with separator of comma here.
}
{
  delete arr1                       ##Deleting arr1 for safer side.
  value=""                          ##Nullifying value here.
  for(i=1;i<=num;i++){              ##Running for loop till value of num here.
    split(arr[i],arr1,"-")          ##Splitting arr[i[ value into arr1 here with separator of - here.
    for(start=arr1[1];start<=arr1[2];start++){ ##Running for loop from arr[1] value to arr1[2] value.
       value=(value?value OFS:"")$start  ##Creating value which has fields value in it.
    }
  }
  print value                       ##printing value here.
}
'  Input_file                       ##Mentioning Input_file name here.

Something like this (untested due to no input/output provided to test against) should do it:

awk -v r='1 3 9-12 30-35' '
    BEGIN {
        numRanges = split(r,ranges)
        for (rangeNr=1; rangeNr<=numRanges; rangeNr++) {
            n = split(ranges[rangeNr],begEnd,/-/)
            for (inFldNr=begEnd[1]; inFldNr<=begEnd[n]; inFldNr++) {
                out2in[++numOutFlds] = inFldNr
            }
        }
    }
    {
        for (outFldNr=1; outFldNr<=numOutFlds; outFldNr++) {
            inFldNr = out2in[outFldNr]
            printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
        }
    }
' file
grep -i introd *.csv | awk 'BEGIN { first = 9; last = 12 }{ print $1, $3 for (i = first; i < last; i++) {printf("%s ", $i) } print $last
Related