24

I started learning Bash a couple of days ago.

I'm trying to obtain an exit status of grep expression into a variable like this:

check=grep -ci 'text' file.sh

and the output that I got is

No command '-ic' found

Should I do it with a pipe command?

2
  • 6
    For exit status you can examine $? right after command is finished.
    – Vombat
    Jul 7, 2016 at 8:16
  • and you can examine the PIPESTATUS array if you want the status of one of the commands in a pipeline.
    – cas
    Jul 7, 2016 at 11:29

7 Answers 7

39

Your command,

check=grep -ci 'text' file.sh

will be interpreted by the shell as "run the command -ci with the arguments text and file.sh, and set the variable check to the value grep in its environment".


The shell stores the exit value of most recently executed command in the variable ?. You can assign its value to one of your own variables like this:

grep -i 'PATTERN' file
check=$?

If you want to act on this value, you may either use your check variable:

if [ "$check" -eq 0 ]; then
    # do things for success
else
    # do other things for failure
fi

or you could skip using a separate variable and having to inspect $? all together:

if grep -q -i 'pattern' file; then
  # do things (pattern was found)
else
  # do other things (pattern was not found)
fi

(note the -q, it instructs grep to not output anything and to exit as soon as something matches; we aren't really interested in what matches here)

Or, if you just want to "do things" when the pattern is not found:

if ! grep -q -i 'pattern' file; then
  # do things (pattern was not found)
fi

Saving $? into another variable is only ever needed if you need to use it later, when the value in $? has been overwritten, as in

mkdir "$dir"
err=$?

if [ "$err" -ne 0 ] && [ ! -d "$dir" ]; then
    printf 'Error creating %s (error code %d)\n' "$dir" "$err" >&2
    exit "$err"
fi

In the above code snippet, $? will be overwritten by the result of the [ "$err" -ne 0 ] && [ ! -d "$dir" ] test. Saving it here is really only necessary if we need to display it and use it with exit.

0
9

You've told bash to set the variable check=grep in the environment it passes to the command

-ci 'text' file.sh

but ci does not exist.

I believe you meant to enclose that command in back-ticks, or in parentheses preceded by a dollar sign, either of which would assign the count of how many lines 'text' was found on (case insensitively) in the file:

check=`grep -ci 'text' file.sh`

check=$(grep -ci 'text' file.sh)

Now $check should be 0 if no matches, or positive if there were any matches.

4

Your question is unclear but based on the code you’ve submitted, it looks like you want the variable check to store the exit status of the grep command. The way to do this is to run

grep -ci 'text' file.sh
check=$?

When running a command from a shell, its exit status is made available through the special shell parameter, $?.

This is documented by POSIX (the standard for Unix-like operating systems) in its specification for the shell and the Bash implementation is documented under Special Parameters.

Since you’re a new learner, I’d strongly recommend that you start with a good book and/or online tutorial to get the basics. Recommendations of external resources are discouraged on Stack Exchange sites but I’d suggest Lhunath and GreyCat’s Bash Guide.

2

Confused why using -c when checking the output? It's used to check how many times something is matched - not if it's successful or not.

   -c, --count
          Suppress normal output; instead print a count of matching  lines
          for  each  input  file.  With the -v, --invert-match option (see
          below), count non-matching lines.  (-c is specified by POSIX.)

but in this example

check="$(grep --silent -i 'text' file.sh; echo $?)"

It doesn't output anything except a exit code, which is then echoed. This is the output that the variable check uses. I also prefer it because it's a single line.

You can replace --silent with -q. I use it since you're not interested in the grep output, just whether it worked or not.

   -q, --quiet, --silent
          Quiet;  do  not  write  anything  to  standard   output.    Exit
          immediately  with  zero status if any match is found, even if an
          error was detected.  Also see the -s  or  --no-messages  option.
          (-q is specified by POSIX.)

$ check=$(echo test | grep -qi test; echo $?) # check variable is now set
$ echo $check
0
$ check=$(echo null | grep -qi test; echo $?)
$ echo $check
1
2

Other answers clarify why your command is not working, and some great suggestions as to how to test its result. I'd like to add one more.

$(command)$? appends the exit code

You can do this using a combination of command substitution and exit code checking, all in one line. It will append the exit code to the output as a number.

check=$( grep -ci 'text' file.sh )$?

This is actually $(some command) (get the string output of a command) immediately followed by $? (get the exit code of the last command). To work, they have to be used together with no space.

Source: https://www.assertnotmagic.com/2018/06/20/bash-brackets-quick-reference/#-dollar-single-parentheses-dollar-q-

If there is any possibility of output, which is the case with most commands, you will have to suppress it using > /dev/null 2>&1.

This may not be necessary if you're using a test command such as test -d <directory>.

check=$( grep -ci 'text' file.sh > /dev/null 2>&1)$?

You could use a function to abstract this away:

e() {
    $@ > /dev/null 2>&1
}

# Usage:
check=$(e grep -ci 'text' file.sh)$?

Testing the exit code

To use this later, test against zero (success):

[ $check -eq 0 ] && echo "passed" || echo "didn't pass"

[ $check -ne 0 ] && echo "didn't pass" || echo "passed"

Using an if/then/else construct, testing against zero (success):

if [ $check -eq 0 ]; then
    echo "passed"
else
    echo "didn't pass"
fi

if [ $check -ne 0 ]; then
    echo "didn't pass"
else
    echo "passed"
fi

Testing exit code directly

Using exit within a (subshell) will produce a proper exit code from the number which you can use directly:

(exit $check) && echo "check passed" || echo "check didn't pass"

! (exit $check) && echo "check didn't pass" || echo "check passed"

Using an if/then/else construct, using exit code directly:

if (exit $check); then
    echo "passed"
else
    echo "didn't pass"
fi

if ! (exit $check); then
    echo "didn't pass"
else
    echo "passed"
fi

Using return instead of exit will also work in some shells - zsh included (but not bash).

Getting fancy

If you want to simplify even more, use this function:

t() { return $1; }

and now you can test like this:

t $check && echo "passed" || echo "didn't pass"

! t $check && echo "didn't pass" || echo "passed"
3
  • 1
    1) check=(grep -ci 'text' file.sh) creates array containing elements "grep", "-ci" "text" "file.sh". echo $check will print only first element, to get all you need echo ${check[*]}. 2) check=$(command)$? will store output of command and append exit code. try check=$(echo "a b c")$?; echo $check Aug 28, 2020 at 7:43
  • Ah, you're right! Will edit.
    – robrecord
    Aug 28, 2020 at 8:48
  • 1
    This answer helps a lot in my case, where I use set -e in my bash script. With this setting checking exit status in a new line results in terminating the script in case of non-zero exit status of a command. $(command)$? syntax works as expected. Thanks!
    – Peeech
    Feb 7, 2022 at 12:42
1

The correct way to assign the output of grep command to a variable is as @monty-harder mentioned:

check=`grep -ci 'text' file.sh`
check=$(grep -ci 'text' file.sh)

Though to assign the exit status of this command to a variable you have to use the shell parameter $? right after executing the command as shown below:

grep -ci 'text' file.sh
check=$?
echo $check
-1

For my case I ended up doing something like:

code=$(grep -ci 'text' file.sh 2>&1 > /dev/null; echo $?)
echo $code
0

Works if you are only interested in the return code and you are willing to discard. stdout and stderr

4
  • Why do you store the output of the commands in code if you don't end up using $code? This would be equivalent to grep -ci 'text' file.sh 2>&1 followed by echo 0 (what you are outputting is actually the exit status of echo in the command substitution, which will be zero). It would also be better to use grep -q -i than grep -c -i as you're throwing away the output anyway: grep -q -i 'text' file.sh; echo "$?" (your code corrected). But this is just re-iterating what I wrote in my answer.
    – Kusalananda
    Aug 5, 2021 at 20:32
  • You're right, my mistake. I fixed it. The reason why I did not mention -q is because not all commands have a silent mode, so for this particular example may not be the best approach, but for other solutions it's a valid one. And my answer provides something different comparing to the other answers so it's not a duplicate. Aug 5, 2021 at 21:43
  • The -q option for grep is standard, and always available.
    – Kusalananda
    Aug 5, 2021 at 21:59
  • 1
    Yes I just meant that a silent mode is not available for other commands other than grep since what lead me here was a different command. Aug 5, 2021 at 22:13

You must log in to answer this question.

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