1

I have an input file with non-fixed column number on which I would like to do some arithmetic calculations on:

input.txt
ID1    4651455    234     4651765    392     4652423      470
ID2    16181020   176    16184958    869     16185889     347    16187777     231

The input file has tab-separated fields has always a unique ID in column $1 (not duplicated). Not all rows have the same number of columns.

What I would like to achieve is a tab-separated file as follows:

 output1.txt
 ID1     76    266   
 ID2     3762   62   1541  

Basically it would print the $1 of the original file, then it would start from the second even column of the file ($4) and subtract to its value the previous two columns ($4-$3-$2) then do the same with all the even columns of the input file (e.g., $6-$5-$4; $8-$7-$6; ...). In my knowledge, this can be done with awk print, but I only know how to deal with it when my file has a fixed number of columns in every row.

An even more ideal output for my needs would be the following:

output2.txt
ID1    234    76    392    266   470
ID2    176   3762   869    62    347   1541  231

Basically it would print the $1 of the original file, then interleave printing the odd columns from the input file to the columns as in output1.txt.

2
$ awk -v OFS='\t' -f script.awk file
ID1     76      266
ID2     3762    62      1541

Where script.awk is

{ printf("%s", $1) }
{ for (i=4; i<=NF; i+=2) { printf("%s%d", OFS, $i - $(i-1) - $(i-2)) } }
{ printf("%s", RS) }

All blocks will be executed for each line of input from the file.

The first block outputs the ID.

The second block iterates over the fields as you described and outputs data separated by OFS (the output field separator, set to a tab on the command line).

The final block simply outputs the record separator RS which is a newline by default.

Alternatively,

BEGIN { OFS = "\t" }
{ printf("%s", $1) }
{ for (i=4; i<=NF; i+=2) { printf("%s%d", OFS, $i - $(i-1) - $(i-2)) } }
{ printf("%s", RS) }

to get rid of the need for -v OFS='\t'.

As a "one-liner":

$ awk 'BEGIN { OFS = "\t" }
{ printf("%s", $1) }
{ for (i=4; i<=NF; i+=2) { printf("%s%d", OFS, $i - $(i-1) - $(i-2)) } }
{ printf("%s", RS) }' file

For the extended problem:

{ printf("%s", $1) }
{ for (i=4; i<=NF; i+=2) { printf("%s%d%s%d", OFS, $(i-1), OFS, $i - $(i-1) - $(i-2)) } }
{ printf("%s%d%s%s", OFS, $NF, OFS, RS) }

This would generate the following from the original file directly:

ID1     234     76      392     266     470
ID2     176     3762    869     62      347     1541    231
2

awk solution:

awk '{ r=$1; for(i=4;i<=NF;i+=2) r=r"\t"$i-$(i-1)-$(i-2); printf "%s\n",r }' OFS='\t' file
  • r=$1 - capturing the 1st field

  • for(i=4;i<=NF;i+=2) - iterating through even fields

  • $i-$(i-1)-$(i-2) - performing the needed substraction


The output:

ID1 76  266
ID2 3762    62  1541

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.