1

Can awk use variable operators for numerical comparison? The following code works with a hard coded operator, but not with a variable operator:

awk -v o="$operator" -v c="$comparison" '$1 o c'
radgokart
  • 45
  • 5

3 Answers3

3

No, that cannot work. Awk's -v option defines actual Awk variables, and not token-level macro substitutions.

It doesn't work for the same reason that this doesn't work:

awk 'BEGIN { o = "+"; print 2 o 2 }'  # hoping for 2 + 2

Awk is different from the POSIX shell and similar languages; it doesn't evaluate variables by means of textual substitution.

Since you're calling Awk from a shell command line, you can use the shell's substitution to generate the Awk syntax, thereby obtaining that effect:

awk -v c="$comparison" "\$1 $operator c"

We now need a backslash on the $1 because we switched to double quotes, inside of which $1 is now recognized by the shell itself.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • at this point there is no need for the variable either, move it in the quotes as well. – karakfa Jan 30 '19 at 00:30
  • @karakfa Without knowing the exact content of `comparison`, we cannot make that call. Consider `awk -v c='a"b' 'BEGIN { print c }'`. If we interpolate `a"b` into the body of the Awk script in place of `c`, that will generate a syntax error, even if we put it in quotes. On the other hand `$operator` is probably a valid Awk operator token, by design. The `-v` mechanism exists for a reason; it is more robust than interpolating text into the Awk syntax. – Kaz Jan 30 '19 at 02:59
1

Another way to the one proposed by Kaz would be to define your own mapping function which takes the two variables as argument and the corresponding operator string o:

awk -v o="$operator" -v c="$comparison" '
      function operator(arg1, arg2, op) {
          if (op == "==") return arg1 == arg2
          if (op == "!=") return arg1 != arg2
          if (op == "<") return arg1 < arg2 
          if (op == ">") return arg1 > arg2
          if (op == "<=") return arg1 <= arg2 
          if (op == ">=") return arg1 >= arg2 
      }
    { print operator($1,c,o) }'

This way you can also define your own operators.

kvantour
  • 25,269
  • 4
  • 47
  • 72
  • I like this, it allows `if (operator(a,op,b) { }` - very flexible for selecting with different criteria on each field. – FelixJN Jun 23 '21 at 11:37
0

No but you have a couple of options, the simplest being to let the shell expand one of the variables to become part of the awk script before awk runs on it:

$ operator='>'; comparison='3'
$ echo 5 | awk -v c="$comparison" '$1 '"$operator"' c'
5

Otherwise you can write your own eval-style function, e.g.:

$ cat tst.awk
cmp($1,o,c)

function cmp(x,y,z,      cmd,line,ret) {
    cmd = "awk \047BEGIN{print (" x " " y " " z ")}\047"
    if ( (cmd | getline line) > 0 ) {
        ret = line
    }
    close(cmd)
    return ret
}

$ echo 5 | awk -v c="$comparison" -v o="$operator" -f tst.awk
5

See https://stackoverflow.com/a/54161251/1745001. The latter would work even if your awk program was saved in a file while the former would not. If you want to mix a library of functions with command line scripts then here's one way with GNU awk for -i:

$ cat tst.awk
function cmp(x,y,z,      cmd,line,ret) {
    cmd = "awk \047BEGIN{print (" x " " y " " z ")}\047"
    if ( (cmd | getline line) > 0 ) {
        ret = line
    }
    close(cmd)
    return ret
}

$ awk -v c="$comparison" -v o="$operator" -i tst.awk 'cmp($1,o,c)'
5
Ed Morton
  • 188,023
  • 17
  • 78
  • 185