558

How can I do something like this in bash?

if "`command` returns any error";
then
    echo "Returned an error"
else
    echo "Proceed..."
fi
Michael Mrozek
  • 93,103
  • 40
  • 240
  • 233

10 Answers10

659

How to conditionally do something if a command succeeded or failed

That's exactly what bash's if statement does:

if command ; then
    echo "Command succeeded"
else
    echo "Command failed"
fi

Adding information from comments: you don't need to use the [ ... ] syntax in this case. [ is itself a command, very nearly equivalent to test. It's probably the most common command to use in an if, which can lead to the assumption that it's part of the shell's syntax. But if you want to test whether a command succeeded or not, use the command itself directly with if, as shown above.

  • 31
    Note that the semicolon is important. – Thorbjørn Ravn Andersen Oct 17 '11 at 07:37
  • 48
    Or you can just put then on a separate line. – l0b0 Oct 17 '11 at 09:00
  • 3
    Careful, this presumes the command``returns a sane return value. Today most do, but e.g.vi(1)` (the original one) was (in)famous for its random return values. – vonbrand Dec 27 '15 at 21:02
  • 2
    @vonbrand: Sure, but I don't think it's common to check the return value of vi. – Keith Thompson Dec 27 '15 at 22:03
  • 2
    @KeithThompson, you'd be surprised... ;-) (found out about this misfeature experimentally) – vonbrand Dec 27 '15 at 22:10
  • This will not work when you have a have a | or complex command. e.g. if $(ssh invalid | logger); then echo "hi"; fi. The result will be the error and then "hi" :) – cgseller Jun 08 '17 at 13:16
  • AFAIK, $(command) gives you the stdout of the command.... – Timothy Zorn Jun 28 '17 at 21:53
  • 3
    @cgseller If you want to use multiple commands (like pipelines | or lists ;, &, &&, ||) between if and then you simply put them there like this: if ssh invalid | logger ; then echo "hi"; fi --- If you really want to enclose the command list once more you can use the curly {} or round () brackets. --- The construct $(ssh invalid | logger) is replaced by the stdout output of the command(s) (in this case the output of logger) and if the construct is in a place of a command (as in your example) then the substitued output is executed as a command. – pabouk - Ukraine stay strong Dec 15 '17 at 09:20
  • If logger is successful, then did you get an error, even if the first command was not successful? – cgseller Dec 18 '17 at 19:40
  • 2
    How do you format command? A simple example would help this answer a lot. Does command need to be in backticks? A subprocess $(command)? Quoted single/double? Etc? – David Parks Jun 21 '18 at 20:00
  • 3
    @DavidParks: command is just any arbitrary command, just as if you typed it by itself at the shell prompt. No, it doesn't need to be in backticks or $(...); that would be replaced by the output of the command (unless you want to execute a command given by the output of another command). For example: if cmp -s file1 file2 ; then echo Same ; else echo Different ; fi – Keith Thompson Jun 21 '18 at 20:07
  • It was too simple in the bash world so I didn't think of it, I assumed it needed like [[ $("command-ffs") ]] or something along those lines :D – OZZIE Feb 20 '19 at 08:04
  • What if you command has a pipe? like dmesg | grep – Freedo Aug 06 '21 at 22:19
  • 1
    @Freedo: Quick answer: I'd try an experiment and/or read the documentation if I wanted to try that. I think it just uses the status of the last command in the pipeline, but I haven't checked. – Keith Thompson Aug 06 '21 at 23:47
  • 2
    @Freedo From the bash documentation: "The exit status of a pipeline is the exit status of the last command in the pipeline, unless the 'pipefail' option is enabled (*note The Set Builtin::). If 'pipefail' is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully. If the reserved word '!' precedes the pipeline, the exit status is the logical negation of the exit status as described above. The shell waits for all commands in the pipeline to terminate before returning a value." – Keith Thompson Aug 06 '21 at 23:48
  • Other shells may differ in more or less subtle ways. The point is that it's subtle enough that I wouldn't try it without reading the manual first. – Keith Thompson Aug 06 '21 at 23:49
  • Something else to note: If your command involves setting a variable to a value in a subshell which has a non-zero retun value, the if statement will fail. For example: if foo=$(./return_1); then echo Y; else echo N; fi returns N. (Assuming you have a script that returns a 1). – Dean Householder Aug 16 '22 at 19:32
  • Re: "If 'pipefail' is enabled": what is the pipefail's default state: enabled or disabled? – pmor May 17 '23 at 11:40
  • 1
    @pmor As the bash manual says, pipefail is disabled by default. – Keith Thompson May 17 '23 at 20:38
256

For small things that you want to happen if a shell command works, you can use the && construct:

rm -rf somedir && trace_output "Removed the directory"

Similarly for small things that you want to happen when a shell command fails, you can use ||:

rm -rf somedir || exit_on_error "Failed to remove the directory"

Or both

rm -rf somedir && trace_output "Removed the directory" || exit_on_error "Failed to remove the directory"

It's probably unwise to do very much with these constructs, but they can on occasion make the flow of control a lot clearer.

Jason
  • 101
  • 6
    They are shorter and (at least in some shells) faster. I shudder remembering a monster Ultrix installation script written with just these conditional constructions I once tried to decipher... – vonbrand Dec 27 '15 at 22:15
  • 4
    It should be noted that the order of && and || in the last example is important. If you reverse the order as in rm ... || command1 && command2, you'll end up executing both commands in case rm fails as here the && applies to command1 and assuming that doesn't fail as well, this will cause command2 to run. In the reverse order rm ... && command2 || command1 the double-excecution is not an issue, assuming command2 never fails. – Raven Mar 29 '22 at 07:16
  • 2
    It is also noteworthy to know that if you need multiple commands, you can chain items in a subshell. This is almost always going to result in a spaghetti code situation and regular if statements should be used. But if needed you could run: command && (command1; command 2) || (command3; command 4) – Dean Householder Aug 16 '22 at 19:39
206

Check the value of $?, which contains the result of executing the most recent command/function:

#!/bin/bash

echo "this will work"
RESULT=$?
if [ $RESULT -eq 0 ]; then
  echo success
else
  echo failed
fi

if [ $RESULT == 0 ]; then
  echo success 2
else
  echo failed 2
fi
  • 13
    While technically correct (and thus not warranting a downvote), it's not making use of Bash's if idiom. I prefer Keith Thompson's answer. – janmoesen Oct 17 '11 at 11:30
  • 37
    There are benefits to this idiom -- it preserves the return value. In all, I find this one to be more powerful, though more verbose. it's also easier to read. – taxilian Oct 07 '15 at 21:16
  • 7
    What is "Bash's if idiom"? – Nowaker Jun 27 '16 at 01:37
  • 8
    @Nowaker The fact that the sole purpose of if is to do this. The flow control conditions in Bash all examine $? behind the scenes; that's what they do. Explicitly examining its value should be unnecessary in the vast majority of cases, and is usually a beginner antipattern. – tripleee Nov 04 '16 at 12:34
  • @triplee so what is the correct pattern ? – danfromisrael Apr 17 '18 at 09:02
  • @danfromisrael Most of the other answers on this page; Keith's in paricular. – tripleee Apr 17 '18 at 09:23
  • @triplee: And how do you go about acting on the negated exit status (i.e. on failure) with if? If the success branch is a no-op, so you'd only need if ! cmd; then error..., as I understand, if alone can't do the negation (bash doesn't seem to have a not builtin), so one should resort to some [ ... ] hackery then, right? – Sz. Aug 19 '19 at 15:51
  • 1
    @lunakid If you don't care about the exit code, if ! cmd is fine. Otherwise, a common arrangement is to use an else clause. You can't have an empty then but you sometimes see then : nothing; else where the : no-op is significant. true would work there as well. – tripleee Aug 19 '19 at 17:13
  • @triplee, "if ! cmd is fine" ah thanks! (I did try, but I managed to get something obvious wrong then. I guess I only tried a not, instead of just a !.) – Sz. Aug 19 '19 at 17:34
  • 17
    I prefer this for readability. The main command is typically a really long one. Putting it inside an "if" statement makes for bad readability. – Nikhil VJ Sep 16 '19 at 01:34
  • 1
    This pattern can be useful, when you want to capture the output of a command into a variable, but also fail on error, e.g. result=$(command); if [ $? -eq 0 ]; then .... The gotcha here though is that if you declare, local or readonly the var that you are assigning, then $? will be the return value of the assignment command, not the command that you are interested in! (So declare the var first, then assign it). – spikyjt Mar 03 '20 at 13:47
  • 1
    @spikyjt You can capture the output and fail on error by using if ! output=$(command); then ... syntax. – yurez Jan 08 '21 at 11:05
  • Info on the result values with $? – Timo Jun 18 '21 at 07:10
  • If the command is long, it can be put into an array: cmd=( theCommand "with args" and "other args" "and so on"); if "${cmd[@]}"; then ... -- I expecially like this because the array definition can include arbitrary whitespace so the options can be laid out with newlines for maximum readability. – glenn jackman Jan 07 '22 at 15:41
  • unnecessary complexity – Feline Jan 10 '22 at 16:38
85

This worked for me:

command && echo "OK" || echo "NOK"

if command succeeds, then echo "OK" is executed, and since it's successful, execution stops there. Otherwise, && is skipped, and echo "NOK" is executed.

  • 6
    If you want to do something if it fails, and preserve the exit code (to show in command prompt or test in a script), you can do this:

    command && echo "OK" || c=$?; echo "NOK"; $(exit $c)

    – Sam Hasler Jun 24 '14 at 15:57
  • 3
    @Sam-Hasler: shouldn't that be command && echo "OK" || (c=$?; echo "NOK"; (exit $c))? – jrw32982 Apr 01 '15 at 18:24
  • 17
    Also, if the echo "OK" part could itself fail, then this is better: command && (echo "OK"; exit 0) || (c=$?; echo "NOK"; (exit $c)) – jrw32982 Apr 01 '15 at 18:34
  • @jrw32982, Nice, I've used the former construction, but not the latter. – Sam Hasler Apr 02 '15 at 16:11
  • The real answer is in the comments, thanks all! – Joshua Pinter Oct 24 '17 at 20:13
  • This is fabulous. Since I wanted to continue executing next commands no matter command fails or not, I returned else part with exit 0 as well: command && (echo "OK"; exit 0) || (c=$?; echo "NOK"; (exit 0)) – Qumber Jun 16 '20 at 09:36
12

It should be noted that if...then...fi and &&/|| type of approach deals with exit status returned by command we want to test( 0 on success ); however, some commands don't return a non-zero exit status if command failed or couldn't deal with input. This means that the usual if and &&/|| approaches won't work for those particular commands.

For instance, on Linux GNU file still exits with 0 if it received a non-existing file as argument and find couldn't locate the file user specified.

$ find . -name "not_existing_file"                                          
$ echo $?
0
$ file ./not_existing_file                                                  
./not_existing_file: cannot open `./not_existing_file' (No such file or directory)
$ echo $?
0

In such cases, one potential way we could handle the situation is by reading stderr/stdin messages, e.g. those that returned by file command, or parse output of the command like in find. For that purposes, case statement could be used.

$ file ./doesntexist  | while IFS= read -r output; do                                                                                                                  
> case "$output" in 
> *"No such file or directory"*) printf "%s\n" "This will show up if failed";;
> *) printf "%s\n" "This will show up if succeeded" ;;
> esac
> done
This will show up if failed

$ find . -name "doesn'texist" | if ! read IFS= out; then echo "File not found"; fi                                                                                     
File not found
4

The most error prone I could come up with was:

  • First, get the value. Suppose you do something like:

RR=$?

Now, for not only this situation, but others you may face, consider:

defined variable:

$ AA=1 ; if (( "10#0${AA}" == 1 )) ; then echo yes ; else echo no ; fi

Answer: yes

$ AA=1 ; if (( "10#0${AA}" != 1 )) ; then echo yes ; else echo no ; fi

Answer: no

undefined variable:

$ AA=1 ; if (( "10#0${BB}" == 1 )) ; then echo yes ; else echo no ; fi

Answer: no

$ AA=1 ; if (( "10#0${BB}" != 1 )) ; then echo yes ; else echo no ; fi

Answer: yes

$ AA=1 ; if (( "10#0${BB}" == 0 )) ; then echo yes ; else echo no ; fi

Answer: yes

This prevents all kinds off errors.

You are probably aware of all the syntax, but here some tips:

  • Use quotes. Avoid a "blank" to be nothing.
  • The new modern notation for variables is ${variable}.
  • Adding a zero concatenated before your number also avoid "no number at all".
  • But wait, adding a zero makes the number become base-8. You will get an error like:
    • value too great for base (error token is "08") for numbers above 7. That is when 10# comes into play:
    • 10# forces the number to be base-10.
DrBeco
  • 774
2

You can do this:

if ($( ping 4.4.4.4 -c1 > /dev/null )) ; then
  echo "ping response succsess!!!"
fi
Michael Mrozek
  • 93,103
  • 40
  • 240
  • 233
Daniel
  • 39
  • 12
    That works but is convoluted. You're running ping in a subshell of a subshell, the output of ping is captured in view of running it as a command. But because the output is redirected to /dev/null that will always be the empty string. So you're running nothing in a subshell, which means the previous exit status (of the command substitution subshell, that is of ping) will be retained. Obviously, the correct way is if ping ...; then here. – Stéphane Chazelas Apr 01 '15 at 15:40
2
#!/bin/bash

if command-1 ; then
   echo "command-1 succeeded and now running from this block command-2"
   command-2
else
   echo "command-1 failed and now running from this block command-3"
   command-3
fi
1

This could be done simply in this way as $? gives you the status of last command executed.

So it could be

#!/bin/sh

... some command ...

if [ $? == 0 ] ; then
  echo '<the output message you want to display>'
else 
  echo '<failure message>'
fi
Ouki
  • 5,962
  • 1
    Downvote: This simply paraphrases an earlier answer which has rightfully received criticism for being unidiomatic. – tripleee Nov 04 '16 at 12:35
  • Actually this was exactly what I was looking for because I'm trying to make the "command" fail the script as part of "set -e" which it doesn't if it's part of the if. This shouldn't be downvoted. – McTrafik Jan 12 '21 at 01:15
1

As noted elsewhere in this thread, the original question basically answers itself. Here is an illustration showing that if conditions may also be nested.

This example uses if to check if a file exists and if it is a regular file. If those conditions are true, then check whether or not it has a size greater than 0.

#!/bin/bash

echo "Which error log are you checking today? "
read answer

if [ -f /opt/logs/$answer*.errors ]
    then
        if [ -s /opt/logs/$answer*.errors ]
            then
                echo "Content is present in the $answer error log file."
            else
                echo "No errors are present in the $answer error log file."
        fi
    else
        echo "$answer does not have an error log at this time."
fi