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.

6
  • 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, 2017 at 15:21
  • I need the output and return code. $? Might do the job
    – ilansch
    Jun 22, 2017 at 15:34
  • @ilansch I added another answer that should satisfy your requirements.
    – pfnuesel
    Jun 22, 2017 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, 2017 at 18:11

5 Answers 5

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.

4
  • 1
    Why the downvote?
    – pfnuesel
    Jun 27, 2017 at 14:24
  • 1
    I’m wondering how this answer ever got any up votes when it does not do what the question asks for — capture the output of grep in a variable. May 20, 2022 at 1:51
  • @Scott, can you clarify what doesn't work?
    – pfnuesel
    May 21, 2022 at 7:24
  • The question is a bit muddled, but the OP seems to want the OUTPUT variable to contain sockname: AF_INET6 ::ffff:10.10.50.28  port: 22(newline)peername: AF_INET6 ::ffff:10.16.6.150  port: 12295. As your answer says, it only sets the output variable to 2. Jun 10, 2022 at 1:20
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.

0
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
3
  • (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. May 20, 2022 at 1: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, 2022 at 7: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. Jun 10, 2022 at 2: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
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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .