28

The following command will resolve google ip

> ip=`dig +short google.com`
> echo $ip
> 216.58.210.238

Sometimes (especially when internet connection is lost) this command fail with this error

> ;; connection timed out; no servers could be reached

When the command fail and I use $# the output is 0 for the assigment

> ip=`dig +short google.com`
> echo $#
> 0
> echo $ip     # Command failed
> ;; connection timed out; no servers could be reached

How can I save the output of command in variable, and also check if the command succeeded?

0

5 Answers 5

39

You can avoid accessing $?, and simply:

if ip=$(dig +short google.com); then
    # Success.
else
    # Failure.
fi

Example:

The following function will print "fail" and return 1.

print_and_fail() { printf '%s' fail; return 1; }

Thus, if we do the following:

if foo=$(print_and_fail); then printf '%s\n' "$foo";fi

We'll get no output, yet store print_and_fail output to $foo - in this case, "fail".

But, take a look at the following function, which will print "success" and return 0.

print_and_succeed() { printf '%s' success; return 0; }

Let's see what happens now:

$ if foo=$(print_and_succeed); then printf '%s\n' "$foo";fi
$ success
Sign up to request clarification or add additional context in comments.

Comments

13

You should use $? instead of $#.

  • $? contains the return value from the last script.
  • $# contains the total number of arguments passed to a script or function.

Do something like below :

ip=$(dig +short google.com)
if [ $? -eq 0 ]; then
  echo "Success" # Do something here
else
  echo "Fail" # Fallback mode
fi

Comments

9

I recommend against the use of $? as much as possible, as it is fragile and easy to overlook when refactoring. If someone adds a line in between the command and $?, the script will break in non-obvious ways.

If you want to both assign the output to a variable and check the exit code at the same time, you can just do both in your conditional block:

if foo="$(echo "foo"; true)"; then
    echo "$foo"
fi

echo "$foo"

3 Comments

Very neat solution I've been looking for.
awesome! just what I was looking for to avoid using $?!! If I'm not mistaken, I can remove the true inside, and also make use of the called command's exit code, right? After all, in this case if the command fails, the true after it would kill the error and return exit 0 all the same, making the if always go for the positive path, correct?
@JoãoCiocca I just put true as a placeholder for a command that exits with true - you definitely don't need it.
1

You can check the return or use command-substitution and check the resulting variable. e.g.

$ ip=$(dig +short google.com)
$ [ -n "$ip" ] && echo "all good, ip = $ip"

(you can do the reverse check for failure with -z

6 Comments

Who is our downvoter who lacks the integrity to leave a comment as to why?
This answer doesn't check the exit status of dig, which the question specifically mentions.
@Slaiyer the +short ("terse") option only returns a value on success. The check on dig exit status is provided by [ -n "$ip" ]. On failure nothing is returned by dig and is caught by the conditional.
While that works in this particular case, exit codes are an out of band reporting mechanism whose usage is a convention for good reasons, the prime being uniformity of implementation (non-zero for failure), specificity (error codes mapped to specific errors), and widespread adoption. A given CLI tool is more likely to have already written something to stdout by the time an error occurs, than it is to return an unexpected code.
I get that point, and if there were a difference in directly checking "$?" here being the subsequent result of the command substitution instead of checking the return by checking whether the variable assigned the result of the command substitution it would make sense.. I note other answers here also took this approach.
|
1

Since you are using Bash, you can use something like the following script which can capture stdout, stderr and the return code https://gist.github.com/jmmitchell/c4369acb8e9ea1f984541f8819c4c87b

For easy reference I have copied the script here:

# #!/bin/bash
# 
# based on posts from stackexchange:
# http://stackoverflow.com/a/26827443/171475
# http://stackoverflow.com/a/18086548/171475
# http://stackoverflow.com/a/28796214/171475

function example_function {
    printf 'example output to stdout %d\n' {1..10}
    echo >&2 'example output to stderr'
    return 42
}



##############################
### using the dot operator ###

if [ "${BASH_VERSINFO}" -lt 4 ]; then
    printf '%s\n' "The source version of this script requires Bash v4 or higher."
else

    # stdout & stderr only
    source <({ cmd_err=$({ mapfile -t cmd_out < <(example_function); } 2>&1; declare -p cmd_out >&2); declare -p cmd_err; } 2>&1)

    printf "\n%s\n" "SOURCE VERSION : STDOUT & STDERR ONLY"
    printf "%s\n" "${cmd_out[@]}"
    printf "%s\n" "${cmd_err}"

    unset cmd_out
    unset cmd_err


    # stdout & stderr only as well as return code:
    source <({ cmd_err=$({ mapfile -t cmd_out< <( \
        example_function \
      ; cmd_rtn=$?; declare -p cmd_rtn >&3); } 3>&2 2>&1; declare -p cmd_out >&2); declare -p cmd_err; } 2>&1)


    printf "\n%s\n" "SOURCE VERSION : STDOUT, STDERR & RETURN CODE"
    printf '%s\n' "${cmd_out[@]}"
    # alternative version
    # declare -p cmd_out 
    printf '%s\n' "${cmd_err}"
    printf '%s\n' "${cmd_rtn}"

    unset cmd_out
    unset cmd_err
    unset cmd_rtn

fi

##############################
######### using exec #########

# stdout & stderr only
eval "$({ cmd_err=$({ cmd_out=$( \
    example_function \
  ); } 2>&1; declare -p cmd_out >&2); declare -p cmd_err; } 2>&1)"

printf "\n%s\n" "EVAL VERSION : STDOUT & STDERR ONLY"
printf '%s\n' "${cmd_out}"
printf '%s\n' "${cmd_err}"
printf '%s\n' "${cmd_rtn}"

unset cmd_out
unset cmd_err

# stdout & stderr only as well as return code:
eval "$({ cmd_err=$({ cmd_out=$( \
    example_function \
  ); cmd_rtn=$?; } 2>&1; declare -p cmd_out cmd_rtn >&2); declare -p cmd_err; } 2>&1)"


printf "\n%s\n" "EVAL VERSION : STDOUT, STDERR & RETURN CODE"
printf '%s\n' "${cmd_out}"
printf '%s\n' "${cmd_err}"
printf '%s\n' "${cmd_rtn}"


unset cmd_out
unset cmd_err
unset cmd_rtn

2 Comments

If someone is going to down vote my answer, I would appreciate understanding how I was wrong or unhelpful in my post.
It does not look easy to use, but I think it is mainly due to formatting the code as only one snippet. (I did not downvote though)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.