-1

How can I write an awk script that can add or subtract based on what the line in the input file is asking?  I figured out how to do either only addition or subtraction, but not either/or in one script.

For example, I would want this input:

ADD 5,10,20
SUB 30,5,20

to produce this output:

35
-55

This is what I have written so far:

#!/bin/awk 
BEGIN {
    FS=","
}

{
    for(i=1;i<=NF;i++) 
        sum+=$i; 
    print sum; sum=0}

but, obviously, it does addition only, and it doesn't even get all the numbers.

2
  • Is the two-lines "input" example you showed one file with two lines, or is it two files with one line each? Can the input files have more than one line? Do you want to process the values per-line, or actually produce a grand total at end-of-file?
    – AdminBee
    Sep 16 at 10:38
  • 1
    based on what condition subtraction result of these 3numbers 30,5,20 become -55? it's not you want 30-5-20 will be 5? or -1,-2,-3 will be -1-(-2)-(-3)=4? Sep 16 at 18:05
5

Some tips:

  • use either space or comma as the field separator: FS = "[ ,]"
  • then add up the numbers like you do, except start with i = 2
  • if the first field is SUB, multiply the sum by -1
1

Here's one way of implementing glenn jackman's algorithm, using an associative array to hold the multiplication values (1 or -1):

$ awk -F ',|[[:blank:]]+' '
    BEGIN {
      mult["ADD"] =  1;
      mult["SUB"] = -1;
    };

    $1 ~ /^(ADD|SUB)$/ {
      sum = 0;
      for (i=2; i <= NF; i++) { sum += $i };
      print sum * mult[$1];
    }' input.txt
35
-55

This also provides the beginnings of a framework to add other keywords - e.g. "MULT" or "DIV" or "EXP" (for these, you would have sum=$2 and start the for loop with i=3 instead of i=2).

0
0
$ awk -F'[ ,]' '{sum=0; for (i=2; i<=NF; i++) sum+=$i; print ($1=="ADD" ? 1 : -1) * sum}' file
35
-55

If you had to support more operations than just ADD and SUB, e.g.:

$ cat file
ADD 5,10,20
SUB 30,5,20
MULT 2,3,4
DIV 20,2,2

you could use GNU awk and define functions for every op you want to support and call them indirectly:

$ cat tst.awk
function ADD(n)  { sum += n }
function SUB(n)  { sum -= n }
function DIV(n)  { if (n==0) { printf "%s[%d]: Divide by zero error in %s\n", FILENAME, FNR, $0 | "cat>&2"; exit 1 } sum /= n }
function MULT(n) { sum *= n }

BEGIN { FS="[ ,]" }
{
    op = $1
    sum = (op=="SUB" ? -1 : 1) * $2
    for (i=3; i<=NF; i++) {
        @op($i)
    }
    print sum
}

$ awk -f tst.awk file
35
-55
24
5

I had to special-case "SUB" to get the output you want from the input you provided for that as it gets treated differently from the rest (i.e. all numbers subtracted from zero rather than all numbers added/multiplied/divided by each other). If the first number on the SUB line was -30 or 0 instead of 30 then all operations could be treated exactly the same way, e.g.:

$ cat file
ADD 5,10,20
SUB 0,30,5,20
MULT 2,3,4
DIV 20,2,2

$ cat tst.awk
function ADD(n)  { sum += n }
function SUB(n)  { sum -= n }
function DIV(n)  { if (n==0) { printf "%s[%d]: Divide by zero error in %s\n", FILENAME, FNR, $0 | "cat>&2"; exit 1 } sum /= n }
function MULT(n) { sum *= n }

BEGIN { FS="[ ,]" }
{
    op = $1
    sum = $2
    for (i=3; i<=NF; i++) {
        @op($i)
    }
    print sum
}

$ awk -f tst.awk file
35
-55
24
5
0

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.