5269
Comment:
|
6129
Removing some unnecessary whitespace.
|
Deletions are marked like this. | Additions are marked like this. |
Line 8: | Line 8: |
output=$(command) # stdout only; stderr remains uncaptured output=$(command 2>&1) # both stdout and stderr will be captured |
output=$(command) # stdout only; stderr remains uncaptured output=$(command 2>&1) # both stdout and stderr will be captured |
Line 15: | Line 15: |
command status=$? |
command status=$? |
Line 22: | Line 22: |
output=$(command) status=$? |
output=$(command) status=$? |
Line 30: | Line 30: |
if command; then echo "it succeeded" else echo "it failed" fi |
if command; then echo "it succeeded" else echo "it failed" fi |
Line 40: | Line 40: |
if output=$(command); then echo "it succeeded" |
if output=$(command); then echo "it succeeded" |
Line 48: | Line 48: |
grep foo somelogfile | head -5 status=${PIPESTATUS[0]} |
grep foo somelogfile | head -5 status=${PIPESTATUS[0]} |
Line 55: | Line 55: |
set -o pipefail if ! grep foo somelogfile | head -5; then echo "uh oh" fi |
set -o pipefail if ! grep foo somelogfile | head -5; then echo "uh oh" fi |
Line 64: | Line 64: |
output=$(command 2>&1 >/dev/null) # Save stderr, discard stdout. output=$(command 2>&1 >/dev/tty) # Save stderr, send stdout to the terminal. output=$(command 3>&2 2>&1 1>&3-) # Save stderr, send stdout to script's stderr. |
output=$(command 2>&1 >/dev/null) # Save stderr, discard stdout. output=$(command 2>&1 >/dev/tty) # Save stderr, send stdout to the terminal. output=$(command 3>&2 2>&1 1>&3-) # Save stderr, send stdout to script's stderr. |
Line 69: | Line 69: |
Since the last example may seem a bit confusing, here is the further explanation: first, keep in mind that 1>&3- is equivalent to 1>&3 3>&-. So it will be easier to analyse the following sequence: |
Since the last example may seem a bit confusing, here is the explanation. First, keep in mind that `1>&3-` is equivalent to `1>&3 3>&-`. So it will be easier to analyse the following sequence: `$(... 3>&2 2>&1 1>&3 3>&-)` |
Line 72: | Line 71: |
3>&2 2>&1 1>&3 3>&- 1. 3>&2 - we state that FD 3 should point to what FD 2 points to at this very moment, meaning that FD 3 will point to stderr. 1. 2>&1 - we state that FD 2 should point to what FD 1 points to at this very moment, meaning that FD 2 will point to stdout. 1. 1>&3 - we state that FD 1 should point to what FD 3 points to at this very moment, meaning that FD 1 will point to stderr. 1. 3>&- - we close FD 3 as it is no longer necessary. |
|| Redirection || fd 0 (stdin) || fd 1 (stdout) || fd 2 (stderr) || fd 3 || Description || || initial || /dev/tty || /dev/tty || /dev/tty || || Let's assume this is run in a terminal, so stdin, stdout and stderr are all initially connected to the terminal (tty). || || `$(...)` || /dev/tty || '''pipe''' || /dev/tty || || First, the command substitution is set up. Command's stdout ([[FileDescriptor]] 1) gets captured (by using a pipe internally). Command's stderr (FD 2) still points to its regular place (the script's stderr). || || `3>&2` || /dev/tty || pipe || /dev/tty || '''/dev/tty'''|| Next, FD 3 should point to what FD 2 points to at this very moment, meaning FD 3 will point to the script's stderr ("save stderr in FD 3"). || || `2>&1` || /dev/tty || pipe || '''pipe''' || /dev/tty || Next, FD 2 should point to what FD 1 currently points to, meaning FD 2 will point to stdout. Right now, both FD 2 and FD 1 would be captured. || || `1>&3` || /dev/tty || '''/dev/tty'''|| pipe || /dev/tty || Next, FD 1 should point to what FD 3 currently points to, meaning FD 1 will point to the script's stderr. FD 1 is no longer captured. We have "swapped" FD 1 and FD 2. || || `3>&-` || /dev/tty || /dev/tty || pipe || || Finally, we close FD 3 as it is no longer necessary. || |
Line 86: | Line 86: |
exec 3>&1 # Save the place that stdout (1) points to. output=$(command 2>&1 1>&3) # Run command. stderr is captured. exec 3>&- # Close FD #3. |
exec 3>&1 # Save the place that stdout (1) points to. output=$(command 2>&1 1>&3) # Run command. stderr is captured. exec 3>&- # Close FD #3. |
Line 90: | Line 90: |
# Or this alternative, which captures stderr, letting stdout through: { output=$(command 2>&1 1>&3-) ;} 3>&1 |
# Or this alternative, which captures stderr, letting stdout through: { output=$(command 2>&1 1>&3-) ;} 3>&1 |
Line 100: | Line 100: |
result=$( { stdout=$(cmd) ; } 2>&1; echo "this line is the separator"; echo "$stdout") var_out=${result#*this line is the separator$'\n'} var_err=${result%$'\n'this line is the separator*} |
result=$( { stdout=$(cmd) ; } 2>&1; echo "this line is the separator"; echo "$stdout") var_out=${result#*this line is the separator$'\n'} var_err=${result%$'\n'this line is the separator*} |
Line 108: | Line 108: |
cmd() { curl -s -v http://www.google.fr; } | cmd() { curl -s -v http://www.google.fr; } |
Line 110: | Line 110: |
result=$( { stdout=$(cmd); returncode=$?; } 2>&1; echo -n "this is the separator"; echo "$stdout"; exit $returncode) returncode=$? |
result=$( { stdout=$(cmd); returncode=$?; } 2>&1; echo -n "this is the separator"; echo "$stdout"; exit $returncode) returncode=$? |
Line 113: | Line 113: |
var_out=${result#*this is the separator} var_err=${result%this is the separator*} |
var_out=${result#*this is the separator} var_err=${result%this is the separator*} |
How can I store the return value and/or output of a command in a variable?
Well, that depends on whether you want to store the command's output (either stdout, or stdout + stderr) or its exit status (0 to 255, with 0 typically meaning "success").
If you want to capture the output, you use command substitution:
output=$(command) # stdout only; stderr remains uncaptured output=$(command 2>&1) # both stdout and stderr will be captured
If you want the exit status, you use the special parameter $? after running the command:
command status=$?
If you want both:
output=$(command) status=$?
The assignment to output has no effect on command's exit status, which is still in $?.
If you don't actually want to store the exit status, but simply want to take an action upon success or failure, just use if:
if command; then echo "it succeeded" else echo "it failed" fi
Or if you want to capture stdout as well as taking action on success/failure, without explicitly storing or checking $?:
if output=$(command); then echo "it succeeded" ...
What if you want the exit status of one command from a pipeline? If you want the last command's status, no problem -- it's in $? just like before. If you want some other command's status, use the PIPESTATUS array (BASH only). Say you want the exit status of grep in the following:
grep foo somelogfile | head -5 status=${PIPESTATUS[0]}
Bash 3.0 added a pipefail option as well, which can be used if you simply want to take action upon failure of the grep:
set -o pipefail if ! grep foo somelogfile | head -5; then echo "uh oh" fi
Now, some trickier stuff. Let's say you want only the stderr, but not stdout. Well, then first you have to decide where you do want stdout to go:
output=$(command 2>&1 >/dev/null) # Save stderr, discard stdout. output=$(command 2>&1 >/dev/tty) # Save stderr, send stdout to the terminal. output=$(command 3>&2 2>&1 1>&3-) # Save stderr, send stdout to script's stderr.
Since the last example may seem a bit confusing, here is the explanation. First, keep in mind that 1>&3- is equivalent to 1>&3 3>&-. So it will be easier to analyse the following sequence: $(... 3>&2 2>&1 1>&3 3>&-)
Redirection |
fd 0 (stdin) |
fd 1 (stdout) |
fd 2 (stderr) |
fd 3 |
Description |
initial |
/dev/tty |
/dev/tty |
/dev/tty |
|
Let's assume this is run in a terminal, so stdin, stdout and stderr are all initially connected to the terminal (tty). |
$(...) |
/dev/tty |
pipe |
/dev/tty |
|
First, the command substitution is set up. Command's stdout (FileDescriptor 1) gets captured (by using a pipe internally). Command's stderr (FD 2) still points to its regular place (the script's stderr). |
3>&2 |
/dev/tty |
pipe |
/dev/tty |
/dev/tty |
Next, FD 3 should point to what FD 2 points to at this very moment, meaning FD 3 will point to the script's stderr ("save stderr in FD 3"). |
2>&1 |
/dev/tty |
pipe |
pipe |
/dev/tty |
Next, FD 2 should point to what FD 1 currently points to, meaning FD 2 will point to stdout. Right now, both FD 2 and FD 1 would be captured. |
1>&3 |
/dev/tty |
/dev/tty |
pipe |
/dev/tty |
Next, FD 1 should point to what FD 3 currently points to, meaning FD 1 will point to the script's stderr. FD 1 is no longer captured. We have "swapped" FD 1 and FD 2. |
3>&- |
/dev/tty |
/dev/tty |
pipe |
|
Finally, we close FD 3 as it is no longer necessary. |
A little note: operation n>&m- is sometimes called moving FD m to FD n.
This way what the script writes to FD 2 (normally stderr) will be written to stdout because of the second redirection. What the script writes to FD 1 (normally stdout) will be written to stderr because of the first and third redirections. Stdout and stderr got replaced. Done.
It's possible, although considerably harder, to let stdout "fall through" to wherever it would've gone if there hadn't been any redirection. This involves "saving" the current value of stdout, so that it can be used inside the command substitution:
exec 3>&1 # Save the place that stdout (1) points to. output=$(command 2>&1 1>&3) # Run command. stderr is captured. exec 3>&- # Close FD #3. # Or this alternative, which captures stderr, letting stdout through: { output=$(command 2>&1 1>&3-) ;} 3>&1
In the last example above, note that 1>&3- duplicates FD 3 and stores a copy in FD 1, and then closes FD 3. It could also be written 1>&3 3>&-.
What you cannot do is capture stdout in one variable, and stderr in another, using only FD redirections. You must use a temporary file (or a named pipe) to achieve that one.
Well, you can use a horrible hack like:
result=$( { stdout=$(cmd) ; } 2>&1; echo "this line is the separator"; echo "$stdout") var_out=${result#*this line is the separator$'\n'} var_err=${result%$'\n'this line is the separator*}
Obviously, this is not robust, because either the standard output or the standard error of the command could contain whatever separator string you employ.
And if you want the exit code of your cmd (here a modification in the case of if the cmd stdout nothing)
cmd() { curl -s -v http://www.google.fr; } result=$( { stdout=$(cmd); returncode=$?; } 2>&1; echo -n "this is the separator"; echo "$stdout"; exit $returncode) returncode=$? var_out=${result#*this is the separator} var_err=${result%this is the separator*}
Note: the original question read, "How can I store the return value of a command in a variable?" This was, verbatim, an actual question asked in #bash, ambiguity and all.