How to add an integer to a difference calculation and print it to the end of a line?

Viewed 875

Goal: To print the difference between two fields separated by semicolons ($3 and $2) and add an integer (+1) to that calculated value at the end of each line beginning with ">".

Representative sample of my file:

>lcl|ORF1_      17609   17804   (+):21:131 unnamed protein product
MEKVKNKFDENDIKVPFVPSSLLFNNTGNLNTMDKR
>lcl|ORF2_      17609   17804   (+):70:111 unnamed protein product
MFLLHYYLIIQVI
>lcl|ORF3_      17609   17804   (+):112:147 unnamed protein product
MQWIKDKVLIK
>lcl|ORF4_      17609   17804   (+):129:91 unnamed protein product
MFYPLYLDYLYY
>lcl|ORF5_      17609   17804   (+):90:1 unnamed protein product, partial
MIMKKEQMELLYHSHQIYFLPFPLHQNIHP

Desired Output:

>lcl|ORF1_      17609   17804   (+):21:131 unnamed protein product:111
MEKVKNKFDENDIKVPFVPSSLLFNNTGNLNTMDKR
>lcl|ORF2_      17609   17804   (+):70:111 unnamed protein product:42
MFLLHYYLIIQVI
>lcl|ORF3_      17609   17804   (+):112:147 unnamed protein product:36
MQWIKDKVLIK
>lcl|ORF4_      17609   17804   (+):129:91 unnamed protein product:39
MFYPLYLDYLYY
>lcl|ORF5_      17609   17804   (+):90:1 unnamed protein product, partial:90
MIMKKEQMELLYHSHQIYFLPFPLHQNIHP

My current awk script gets me very close by printing the difference between $3 and $2 at the end of each line, but does not include the +1 addition step (required) and is not specific to lines beginning with ">", despite my attempt with /^ *>/ (not required, but nice):

$ awk -F":" 'BEGIN {OFS=FS} /^ *>/ {$4=$3-$2} $4<0 {$4=-$4} 1' file

>lcl|ORF1_      17609   17804   (+):21:131 unnamed protein product:110
MEKVKNKFDENDIKVPFVPSSLLFNNTGNLNTMDKR:::0
>lcl|ORF2_      17609   17804   (+):70:111 unnamed protein product:41
MFLLHYYLIIQVI:::0
>lcl|ORF3_      17609   17804   (+):112:147 unnamed protein product:35
MQWIKDKVLIK:::0
>lcl|ORF4_      17609   17804   (+):129:91 unnamed protein product:38
MFYPLYLDYLYY:::0
>lcl|ORF5_      17609   17804   (+):90:1 unnamed protein product, partial:89
MIMKKEQMELLYHSHQIYFLPFPLHQNIHP:::0

Attempts to add the integer (+1) to the difference calculation:

$ awk -F":" 'BEGIN {OFS=FS} /^ *>/ {$4+1=$3-$2} $4<0 {$4=-$4} 1' file
awk: line 1: syntax error at or near =

$ awk -F":" 'BEGIN {OFS=FS} /^ *>/ {$4+=1=$3-$2} $4<0 {$4=-$4} 1' file
awk: line 1: syntax error at or near =

$ awk -F":" -v n=1 'BEGIN {OFS=FS} /^ *>/ {$4+n=$3-$2} $4<0 {$4=-$4} 1' file
awk: line 1: syntax error at or near =

And although I'm not sure how to implement functions using awk, I think there could be some utility in using something similar to this:

$ function add_one (number) {
      return number + 1
  }
$ awk -F":" 'BEGIN {OFS=FS} /^ *>/ {add_one($4)=$3-$2} $4<0 {$4=-$4} 1' file

While I have been attempting to use awk to solve this problem, I am interested in any solution (e.g., since I am attempting to perform this calculation line-by-line, perhaps there is a more efficient solution with sed?).

3 Answers

With your shown samples, could you please try following.

awk -F':|[[:space:]]+' -v OFS=":" '/^>/{$0=$0 OFS ($6>$5?($6-$5+1):($5-$6+1))} 1' Input_file

OR

awk -F':|[[:space:]]+' -v OFS=":" '/^>/{print $0,($6>$5?($6-$5+1):($5-$6+1));next} 1' Input_file

Explanation: Adding detailed explanation for above.

awk -F':|[[:space:]]+' -v OFS=":" '      ##Setting field separator as : OR spaces setting OFS as colon here.
/^>/{                                    ##Checking if line starts from >
  $0=$0 OFS ($6>$5?($6-$5+1):($5-$6+1))  ##Re-setting value to current line which has current line and difference of 6th and 5th field(based on condition using ternary operators take difference as per whichever value is greater than other)
}
1                                        ##printing current line here.
' Input_file                             ##Mentioning Input_file name here.


EDIT: Since OP mentioned OP has mawk and above code is not exactly working for OP so adding 1 more way of doing this here:

awk -v OFS=":" '
{
  gsub(/\r/,"")
}
/^>/ && match($0,/[0-9]+:[0-9]+/){
  split(substr($0,RSTART,RLENGTH),arr,":")
  print $0,1+(arr[1]>arr[2]?(arr[1]-arr[2]):(arr[2]-arr[1]))
  next
}
1' Input_file

Here is an alternative awk solution that should work on all awk versions:

awk 'BEGIN {FS=OFS=":"} /^>/ {
   v3=$3+0
   diff = 1 + (v3 > $2 ? v3-$2 : $2-v3)
   $0 = $0 OFS diff
} 1' file

>lcl|ORF1_      17609   17804   (+):21:131 unnamed protein product:111
MEKVKNKFDENDIKVPFVPSSLLFNNTGNLNTMDKR
>lcl|ORF2_      17609   17804   (+):70:111 unnamed protein product:42
MFLLHYYLIIQVI
>lcl|ORF3_      17609   17804   (+):112:147 unnamed protein product:36
MQWIKDKVLIK
>lcl|ORF4_      17609   17804   (+):129:91 unnamed protein product:39
MFYPLYLDYLYY
>lcl|ORF5_      17609   17804   (+):90:1 unnamed protein product, partial:90
MIMKKEQMELLYHSHQIYFLPFPLHQNIHP

PS: Make sure to remove DOS line breaks from your input file before running this awk.

This might work for you (GNU sed):

sed -E 's/^>.*:(.*):(\S*).*/echo "&:$(((\1<\2?\2-\1:\1-\2)+1))"/e' file

Pick out the two fields for an arithmetic expression and tag the result to the line.

N.B. The RHS of the substitution is echoed via the e flag which evaluates the string &:$(((\1<\2?\2-\1:\1-\2)+1) where & is the original line and \1 and \2 the integers. The expr?expr:expr is an if-then-else control flow.

Related