How does one extract a command's exit status into a variable?

265

Solution 1

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.

Solution 2

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.

Solution 3

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.

Solution 4

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"

Solution 5

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
Share:
265

Related videos on Youtube

homes
Author by

homes

Updated on September 18, 2022

Comments

  • homes
    homes over 1 year

    I have table A which has ID and proposalID. Table B has a proposalID as well. What I want to do is for each distinct proposalID in table B, I want to create a new row in table A with the same proposalID and a new ID.

    So table A has these distinct proposalIDs:

    94CAEF39-855B-4B5C-9534-9D4AD0A75FC8
    C1D87317-7028-4F69-91D4-FFFBB35E7ACD
    807D7733-5CCF-486F-81E5-FFF153307C22
    521E22E2-511E-46F2-AA46-FFF832367A9E
    

    What i want table B to have now is:

    ID: new uniqueidentifier, proposalID: 94CAEF39-855B-4B5C-9534-9D4AD0A75FC8
    ID: new uniqueidentifier, proposalID: C1D87317-7028-4F69-91D4-FFFBB35E7ACD
    ID: new uniqueidentifier, proposalID: 807D7733-5CCF-486F-81E5-FFF153307C22
    ID: new uniqueidentifier, proposalID: 521E22E2-511E-46F2-AA46-FFF832367A9E
    

    I'm still learning sql and don't know how to approach this. I know how to get all the distinct proposalIDs but after that i don't know.

    • tuzion
      tuzion almost 8 years
      For exit status you can examine $? right after command is finished.
    • Alessio
      Alessio almost 8 years
      and you can examine the PIPESTATUS array if you want the status of one of the commands in a pipeline.
  • Jakub Jindra
    Jakub Jindra over 3 years
    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
  • robrecord
    robrecord over 3 years
    Ah, you're right! Will edit.
  • Kusalananda
    Kusalananda almost 3 years
    The -q option for grep is standard, and always available.
  • renatodamas
    renatodamas almost 3 years
    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.
  • Peeech
    Peeech over 2 years
    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!