17

I am writing a bash script; I execute a certain command and grep.

pfiles $1 2> /dev/null | grep name # $1 Process Id

The response will be something like:

sockname: AF_INET6 ::ffff:10.10.50.28  port: 22
peername: AF_INET6 ::ffff:10.16.6.150  port: 12295

The response can be no lines, 1 line, or 2 lines.
In case grep returns no lines (grep return code 1), I abort the script; if I get 1 line I invoke A() or B() if more than 1 line. grep's return code is 0 when the output is 1-2 lines.

grep has return value (0 or 1) and output.
How can I catch them both ? If I do something like:

OUTPUT=$(pfiles $1 2> /dev/null | grep peername)

Then variable OUTPUT will have the output (string); I also want the boolean value of grep execution.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
ilansch
  • 415
  • https://unix.stackexchange.com/questions/294371/how-to-obtain-grep-exit-status-into-a-variable – RomanPerekhrest Jun 22 '17 at 15:18
  • I'm not sure if I understood the question. Do you need the output from grep at all? Do you need the return value? Would it be enough to just count the matchs, as per my answer below? – pfnuesel Jun 22 '17 at 15:21
  • I need the output and return code. $? Might do the job – ilansch Jun 22 '17 at 15:34
  • @ilansch I added another answer that should satisfy your requirements. – pfnuesel Jun 22 '17 at 21:21
  • "variable OUTPUT will have the output (string); I also want the boolean value of grep execution." Where this two values should be stored (boolean value and grep output)? In the OUTPUT variable both, each on its own line? Just interesting, what you wanted. – MiniMax Jun 25 '17 at 18:11
  • Yes. In 2 variables. Cant i modify a local variable during the execution? – ilansch Jun 25 '17 at 19:08

5 Answers5

20

You can use

output=$(grep -c 'name' inputfile)

The variable output will then contain the number 0, 1, or 2. Then you can use an if statement to execute different things.

pfnuesel
  • 5,837
5

This is fairly simple:

OUTPUT=$(pfiles "$1" 2> /dev/null | grep peername)
grep_return_code=$?

If a $(…) command substitution is assigned to a variable, $? will get the return code from the last command in the $(…).  And, of course, you don’t need to refer to $? explicitly; you can do things like

if OUTPUT=$(pfiles "$1" 2> /dev/null | grep peername)
then
    # the rest of the script
                ︙
fi

or

if ! OUTPUT=$(pfiles "$1" 2> /dev/null | grep peername)
then
    exit
fi
# the rest of the script

This approach is useful in situations where the output of the command and its return code (a.k.a. exit status) are uncorrelated.  But, for grep, they are highly correlated: If it produced output, it succeeded.  If it didn’t produce output, it failed.  So why not just test the output ?

OUTPUT=$(pfiles "$1" 2> /dev/null | grep peername)
if [ "$OUTPUT" ]
then
    # the rest of the script
                ︙
fi

or

OUTPUT=$(pfiles "$1" 2> /dev/null | grep peername)
if [-z "$OUTPUT" ]
then
    exit
fi
# the rest of the script

P.S. You should always quote your shell variable references (e.g., "$1") unless you have a good reason not to, and you’re sure you know what you’re doing.

3

If you need the result of grep, you can not use the -c flag as outlined in the other answer. What you can do, though, is running twice, once with -c flag to get the number of matches and one without -c flag, to see the matches. However, this can be very inefficient, depending on the size of your input file.

You can do something like this:

Content of input:

The first line is foo
I have a drink at the bar
The third line is foo again

Content of script:

#!/usr/bin/env bash

countMatches(){ echo Searching for "${1}" result=$(grep "${1}" input) if [ "$?" -ne 0 ]; then echo No match found echo exit 1 fi

if [ $(echo "${result}" | wc -l) -eq 1 ]; then
    echo 1 match found:
    echo "${result}"
    echo
else
    echo 2 matches found:
    echo "${result}"
    echo
fi

}

countMatches foo countMatches bar countMatches baz

And here's the output when you invoke the script:

Searching for foo
2 matches found:
The first line is foo
The third line is foo again

Searching for bar 1 match found: I have a drink at the bar

Searching for baz No match found

pfnuesel
  • 5,837
  • (1) You don’t need any of those curly braces in parameter expansions (${1} and ${result}) — quotes alone are enough …  (2) … and you should use quotes for "$?".  (3) grep, like a few other programs, can exit with a status of 2 for system-level errors (like failure to open or read a file).  If you want to test for failure of a program, you should test for "$?" -ne 0. – Scott - Слава Україні May 20 '22 at 01:53
  • 1
    @Scott I fixed point (2) and (3). As for (1), the curly braces are not needed but don't hurt either, it's a matter of style. One could even argue that the quotes around ${1} or $? are not needed either since it's always one word. Thanks for proofreading and feel free to directly edit my posts! – pfnuesel May 21 '22 at 07:28
  • 1
    (4) It’s not valid to say that $1 doesn’t need to be quoted “since it's always one word” just because *you* plan to use arguments that don’t contain spaces or glob characters.  Somebody else might want to call countMatches with an argument like 'Local Address'. (5) As Stéphane Chazelas pointed out in Security implications of forgetting to quote a variable in bash/POSIX shells, you should write scripts that work correctly even if IFS is set to something weird, and even $#, $! and $? can suffer from word splitting if they aren’t quoted. – Scott - Слава Україні Jun 10 '22 at 02:32
2

Try this code, I feel, it doing what you want.

  1. I put grep output to the OUTPUT variable
  2. I think, you don't need grep exit status as boolean value. It is 0 or 1, and it doesn't suit to your task. You need amount of lines - 0, 1, 2, etc. Therefore, count lines in the OUTPUT variable and put this number to the second variable - line_count. We will have two variables in the result. First with grep output, second with amount of lines.
  3. Then, check line_count in the case statement and do needed action.

Usage: ./switching_by_grep_result.sh your_file.txt

#!/bin/bash

# your code
OUTPUT=$(pfiles $1 2> /dev/null | grep name) # $1 Process Id

# count lines in the OUTPUT variable by using 'grep -c'
line_count=$(echo -n "$OUTPUT" | grep -c '^')

# checks lines count in here and invokes needed script or exit.
# if 0 lines - exit
# if 1 lines - invoke A
# if any other number of lines - invoke B
case $line_count in  
    0) echo 'exit'
    ;;  
    1) echo 'A() script invoking here'
    ;;  
    *) echo 'B() script invoking here'
    ;;  
esac
MiniMax
  • 4,123
1

I came across this whilst trying to figure out how to use grep as a test for some piped output, whilst still showing that output regardless. Note that I'm not grepping a file, but stdin.

My particular example is as follows:

./manage.py --dry-run makemigrations | grep "No changes detected"

The return code is 0 (success) when No changes detected. The return code is 1 (fail) when there are changes - but grep doesn't match and so the required changes are not printed!

My solution was as follows

./manage.py --dry-run makemigrations | tee /dev/stderr | grep -q "No changes detected"

This will always print the output, but will still return the correct error code.

Ben XO
  • 226