user2510065
user2510065

Reputation: 15

Bash: Compound grep in

Assume a file $tmpdir$results_tmp containing:

==========================
UNIT 1 OK
UNIT 2 OK
UNIT 3 OK
UNIT 4 OK

I want to conditionally grep the lines that begin with a digit, then grep those for "OK". If any of the lines that begin with a digit don't contain "OK", throw an error.

while read -r line
do
        if [ grep '^[0-9]' | grep -qv "OK" ]  <<< "$line"; then
                printf "$line"
                results=$results"BAD UNIT DETECTED: $line\n\n"
                PASS=false
        fi
done < $tmpdir$results_tmp

My assumption is that the first grep pulls out the lines that begin with a digit and the second grep evaluates those to see if they contain "OK", but that doesn't seem to be what's happening.

Can anyone see where I've made my misstep?e

Upvotes: 1

Views: 390

Answers (2)

Idriss Neumann
Idriss Neumann

Reputation: 3838

To complete John1024's answer (which is already complete) : the double bracket syntax of Bash provide the operator =~ which allows us to use regular expressions.

So, when you write bash scripts, it's generally not useful to use external commands like grep or expr in your blocks (if, while, ...).

For example, you could use :

while read -r; do
    if [[ $REPLY =~ ^[0-9] && ! $REPLY =~ OK ]]; then
        # ...
    fi 
done < file

Instead of :

while read -r line; do
    if grep '^[0-9]' <<<"$line" | grep -qv "OK"; then
        # ...
    fi
done < file

On the other hand, the ^[0-9] pattern match only if the line begin whith a numeric character. This never seems to be the case according to your input sample.

Upvotes: 0

John1024
John1024

Reputation: 113934

I have four solutions here, two using awk, one using sed, and one using a slightly modified version of your bash code.

First, an awk solution:

awk '!/^[0-9]/ {next} !/OK/ {print "BAD UNIT DETECTED:",$0}' input

I tried this using sample input:

$ cat input
======
1 OK
2 not
text only
3 OK
4 OK
5 no status

And, the result is:

$ awk '!/^[0-9]/ {next} !/OK/ {print "BAD UNIT DETECTED:",$0}' input
BAD UNIT DETECTED: 2 not
BAD UNIT DETECTED: 5 no status

How it works

There are two awk commands. The first is !/^[0-9]/ {next}. This looks for any line that does not start with a digit (the exclamation point mean "not") and skips it. The second is !/OK/ {print "BAD UNIT DETECTED:",$0}. This looks any of the remaining lines that do not contain "OK" and prints them with an error message.

Alternate solution with awk

In the comments, JS points out that the logic can be reordered:

awk '/^[0-9]/ && !/OK/ {print "BAD UNIT DETECTED:",$0}' input

This looks for all lines which (a) do not begin with a digit, /^[0-9]/, and also (b) do not contain OK, !/OK/. The && is the awk notation for logical-and. Any line passing both those tests is printed with the error message.

Alternate solution using sed

$ sed '/^[^0-9]/d; /OK/d; s/^/BAD UNIT DETECTED: /' input
BAD UNIT DETECTED: 2 not
BAD UNIT DETECTED: 5 no status

There are three sed commands here. The first /^[^0-9]/d skips over any lines not beginning with a digit. The next /OK/d slips over any of the remaining lines that have "OK" in them. For any line that is left, an error message is prepended and it is printed.

Additional notes

A problem with your code is here:

[ grep '^[0-9]' | grep -qv "OK" ]  <<< "$line"

The leading [ signals the start of a test command but what follows is not a valid test. Since grep sets proper exit codes, a test command is not needed anyway. The above probably should be replaced with:

grep '^[0-9]' <<<"$line" | grep -qv "OK"

or,

echo "$line | grep '^[0-9]' | grep -qv "OK"

A minimally-modified working version of your bash code is:

while read -r line
do
    if grep '^[0-9]' <<<"$line" | grep -qv "OK"
    then
        printf "$line\n"
        results="${results}BAD UNIT DETECTED: $line\n\n"
        PASS=false
    fi
done <input

Note that the results variable contains literal characters backslash and n. This may or may not be what you want.

Upvotes: 3

Related Questions