0

I have a log file and I would like to divide the result of one grep and count by another grep and count.

$ echo $((cat log2.txt | grep timed\|error\|Error | wc -l)/(cat log2.txt | grep Duration | wc -l))
zsh: bad math expression: operator expected at `log2.txt |...'

It's ugly, doesn't work and I can probably do it in a better way but I don't know how.

Also I would like to know if it possible to id incrementaly on a log stream read by tail for example.

chepner
  • 497,756
  • 71
  • 530
  • 681
Mio
  • 1,412
  • 2
  • 19
  • 41
  • 1
    What output do you expect? Bash/sh can't do floating point arithmetic directly. I'd reach for a scripting language (Perl/Ruby/Python). – choroba Jan 05 '17 at 14:41
  • Possible duplicate of [Divide two variables in bash](http://stackoverflow.com/questions/30398014/divide-two-variables-in-bash) – Chem-man17 Jan 05 '17 at 14:42
  • You haven't escaped the `|` character enough. You've only escaped it to avoid the shell treating it as part of a pipeline, but `grep` still treats it as a literal character, not an alteration operator, unless it is escaped *again*. Either `grep timed\\\|\\\|error\\\|Error` or (more simply) `grep 'timed\|error\|Error'` or (simplest of all) `grep -E 'timed|error|Error'` (extended regular expressions *do* treat unescaped `|` as the alteration operator). – chepner Jan 05 '17 at 17:06
  • You *also* need to run the commands in a command substitution to use the output as operands in the arithmetic expression: `$(( $(cat ... wc -l) / $(cat ... wc -l) ))`. – chepner Jan 05 '17 at 17:07

2 Answers2

2

First of all, you should know that, both grep|wc -l will count number of matched lines instead of occurrences, I hope this is what you really want.

Regarding your requirement, indeed, your approach is ugly (7 processes), apart from the mistakes. The job can be done by a single awk line:

awk '/timed|[Ee]rror/{a++}/Duration/{b++}END{printf "%.2f\n",a/b}' log2.txt

The above line calculates the result based on matched number of lines, same as your grep|wc -l.

Kent
  • 189,393
  • 32
  • 233
  • 301
1

You have several problems:

  1. You are trying to run shell commands directly inside an arithmetic expression.
  2. You aren't passing the correct regular expression to grep.
  3. You need to make sure at least one of the operands is a floating-point value to trigger zsh's floating-point division.

Each pipeline can also be reduced to a single command; use input redirection instead of cat, and use the -c option to get the number of lines that match the regular expression.

echo $(( 1.0 * $(grep -c 'timed\|error\|Error' log2.txt) / $(grep -c Duration log2.txt))

Basic regular expressions treat unescaped | as a literal character, not an alteration operator.

$ echo foo | grep foo\|bar
$ echo foo | grep foo\\\|bar   # Pass a literal backslash as part of the regex
foo
$ echo foo | grep 'foo\|bar'   # Use '...' instead of explicitly escaping \ and |
foo
$ echo foo | grep -E 'foo|bar'  # Use extended regular expressions instead
chepner
  • 497,756
  • 71
  • 530
  • 681