Chris O'Kelly
Chris O'Kelly

Reputation: 1893

How to get output of a bash command in a variable

I have been following through some of the similar questions (such as How to set a variable to the output from a command in Bash?), however the accepted answers seem to be non-working for me. I wasn't sure whether I ought to derail someone else's question or post my own duplicate, so apologies if I chose wrong here.

I wish to get the output and exit status of a number of commands in a script I am putting together. Here is an example of what I have been using:

cmd_output=$(rm $file)
exit_status=$?
if [ "${exit_status}" -eq 0 ]
then
log "Successfully removed the original" ${TAB_LEVEL}
else
fail "Failed to remove the original, the output was: \n ${cmd_output}"
fi

The log and fail functions are:

# Usage: fail "Failure message"
function fail {
echo "FATAL ERROR: $1" >> "${LOG_DIR}/${LOG_FILE}"
exit 1
}

# Usage: log "Log message" 3    Where the tab-level is 3.
function log {
if (("${2}" > 0))
then
eval "printf '    %.0s' {1..$2}" >> "${LOG_DIR}/${LOG_FILE}"
fi
echo "$1" >> "${LOG_DIR}/${LOG_FILE}"
return 0
}

In the example above I use the $(cmd) format, but I have also tried using backticks.

In my log file, all I see when there is a failure is:

FATAL ERROR: Failed to remove the original, the output was: \n

Also, the output of the failed commands ends up on screen as per usual. Is there a common reason that my cmd_output variables would be remaining empty?

Upvotes: 17

Views: 35225

Answers (3)

William Pursell
William Pursell

Reputation: 212188

Since your fail function is just exiting, it would be a lot easier to simply do:

set -e  # Abort on failure
exec 2>> "${LOG_DIR}/${LOG_FILE}"  # Append all errors to LOG_FILE
if cmd_output=$(rm $file)
  log "Successfully removed the original" ${TAB_LEVEL}
fi

The only difference between this and the original code is that it does not print the text FATAL ERROR:. Since brevity is a virtue, it would probably be better to skip the log function entirely. Report errors loudly; succeed silently.

Upvotes: 0

antak
antak

Reputation: 20739

*nix command generally have two forms of output: standard output (stdout) and standard error (stderr).

FOO=$(...) only captures stdout, and leaves stderr unhindered.

If you want the contents of stderr with this syntax, you need to postfix your command with 2>&1 so that stderr is merged into stdout. (e.g. like so: rm $file 2>&1)

Upvotes: 6

You have to include the output of the special standard error output stream:

cmd_output=$(rm "$file" 2>&1)

There are three default streams on every program (that are numbered file descriptors):

0. Standard input (where the program normally reads from)
1. Standard output (where the program normally writes to)
2. Standard error (where the program normally writes error messages)

So to capture the error messages, we must redirect standard error output (stderr) into normal standard output (stdout) which will then be captured by the $(...) expression.

The syntax for redirection is through the > "operator". Immediately before it you tell which file descriptor to redirect (the default is 1, which is stdout). And you can specify it to redirect to a file. If you write an ampersand (&) after it, you force it to redirect into another file descriptor. Therefore, in this example, we redirect file descriptor 2 (stderr) into file descriptor 1 (stdout).

Also, you can also redirect input with < "operator", but in this case the default file descriptor is 0 (stdin).

Another observation is that it is probably good practice to place your $file variable between double quotes, in case it has white space characters.

Hope this helps a little =)

Upvotes: 38

Related Questions