4

I have a fileA.txt:

RS0255_RS0083:115,124,129,141,143,168,170,180
RS0343_RS0083:112,113,163,175,181
RS0343_RS0255:94,101,107,164,179,183

I would like to perform mathematical operations on the numbers after : For example, I want to add 10 to each number: Output:

RS0255_RS0083:125,134,139,151,153,178,180,190
RS0343_RS0083:122,123,173,185,191
RS0343_RS0255:104,111,117,174,189,193

I know how to do it in R but how to do this mathematical operation on numbers in a file in perl or awk?

4
  • 1
    "in linux" doesn't really make sense. What programming language do you want to use? python? perl? awk? C? Go? Ruby? Something else? – roaima Feb 14 at 16:40
  • 2
    I am afraid this is way too broad. There are dozens of tools that can be used but it depends on exactly what you need. – terdon Feb 14 at 16:40
  • 2
    yeah I meant perl or awk, sorry – Paolo Lorenzini Feb 14 at 16:47
13

There are quite literally dozens of tools you can use for different manipulation of text files. For the specific case you mention, I would probably use perl:

$ perl -pe 's/\b(\d+)\b/$1 + 10/ge' fileA.txt 
RS0255_RS0083:125,134,139,151,153,178,180,190
RS0343_RS0083:122,123,173,185,191
RS0343_RS0255:104,111,117,174,189,193
3
  • I tried multiplication, addition and subtraction, all ok, for the division how to do? let's suppose I want divide by 10? – Paolo Lorenzini Feb 14 at 17:00
  • 3
    @PaoloLorenzini, I'd use a different delimiter for s/pattern/repl/flags like s:pattern:repl:flags or s{pattern}{repl}flags – Stéphane Chazelas Feb 14 at 17:01
  • 3
    @PaoloLorenzini The division operator (/) happens to be equal to the delimiter used in the substitute perl command (s/.../.../). To be able to to use that symbol in the replacement part, either change the delimiter (s#...#...#) or escape the character (\/). Either perl -pe 's#\b(\d+)\b#$1/10#ge' file or perl -pe 's/\b(\d+)\b/$1\/10/ge' file – Isaac Feb 14 at 17:25
8
gawk '$0+0 == $0 {$0 += 10} {ORS = RT} 1' RS='[:,\n]' file
7

An awk answer: Parse each line as two :-delimited fields. Use split() on the second field to split it into fields on the commas, and modify the split-up fields in a loop, creating a new output record. Then output the new record with commas as delimiters, and with the original first field as a "prefix".

awk -F : '
    BEGIN { OFS = "," }
    {
        prefix = $1
        nf = split($2,a,",")
        $0 = ""

        for (i = 1; i <= nf; ++i)
            $i = a[i] + 10

        printf "%s:%s\n", prefix, $0
    }' fileA.txt

The output would be

RS0255_RS0083:125,134,139,151,153,178,180,190
RS0343_RS0083:122,123,173,185,191
RS0343_RS0255:104,111,117,174,189,193

More compact presentation of the above code (for those of you that thinks one-lines are somehow "better"):

awk -F: '{p=$1;n=split($2,a,",");$0="";for(i=1;i<n;++i)$i=a[i]+10;printf "%s:%s\n",p,$0}' OFS=, fileA.txt

Older answer, which is not as "nice" as the above one:

Using awk and interpreting each line as a list of fields delimited by : or ,, adding 10 to the 2nd field onward on each line:

awk -F '[:,]' 'BEGIN { OFS="," } { for (i = 2; i <= NF; ++i) $i += 10 }; 1' fileA.txt

This would give you

RS0255_RS0083,125,134,139,151,153,178,180,190
RS0343_RS0083,122,123,173,185,191
RS0343_RS0255,104,111,117,174,189,193

To change the first comma on each line back to a :, use sed 's/,/:/':

awk -F '[:,]' 'BEGIN { OFS="," } { for (i = 2; i <= NF; ++i) $i += 10 }; 1' fileA.txt |
sed 's/,/:/'
5
awk -F, -v OFS=, '{
    split($1, tmp, ":");
    $1=tmp[1] ":" tmp[2]+10;
    for(i=2; i<=NF; i++ ) $i+=10;
}1' infile
4

I propose this awk:

$ awk -F':|,' 'BEGIN { OFS="" } 
    $1 = $1":" { 
      OFS=""; 
      for(i=2; i<=NF; i++) { 
        $i = $i + 10; 
        if ( i != NF ) { 
          $i = $i"," 
        } 
      }; 
    }1' file
RS0255_RS0083:125,134,139,151,153,178,180,190
RS0343_RS0083:122,123,173,185,191
RS0343_RS0255:104,111,117,174,189,193


3

Using awk with a progressive slicing around comma we recreate fields dynamically.


awk -F: -v OFS=, '
{
  f1=$1 FS; f2=$2 OFS
  n=0; $0=""
  while (p=index(f2, OFS)) {
    $(++n) = 10 + substr(f2,1,p-1) 
    f2 = substr(f2, p+1)
  }
  $1=f1 $1
}1
' fileA.txt

Output:

RS0255_RS0083:125,134,139,151,153,178,180,190
RS0343_RS0083:122,123,173,185,191
RS0343_RS0255:104,111,117,174,189,193

perl -pe '/:/g;
  s/\G(\d+)(\D)/($1+10).$2/ge;
'fileA.txt 

#alternatively 
perl -ne '
  print !$|-- ? $_ : s/\d+/$&+10/reg
     for /(.*:)(.*)/s;
' fileA.txt

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.