Differences between revisions 14 and 50 (spanning 36 versions)
Revision 14 as of 2009-12-29 17:13:34
Size: 3165
Editor: MatthiasPopp
Comment:
Revision 50 as of 2023-05-11 13:36:34
Size: 7876
Editor: emanuele6
Comment: head -5 => head -n5
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
== How can I store the return value/output of a command in a variable? ==
Well, that depends 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").
== 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").
Line 7: Line 7:
{{{
    var=$(command) # stdout only; stderr remains uncaptured
    var=$(command 2>&1) # both stdout and stderr will be captured
{{{#!highlight bash
output=$(command) # stdout only; stderr remains uncaptured
output=$(command 2>&1) # both stdout and stderr will be captured
Line 12: Line 12:
If you want the exit status: If you want the exit status, you use the special parameter `$?` after running the command:
Line 14: Line 14:
{{{
    command
    var=$?
{{{#!highlight bash
command
status=$?
Line 21: Line 21:
{{{
    var1=$(command)
    var2=$?
{{{#!highlight bash
output=$(command)
status=$?
Line 25: Line 25:
The assignment to {{{var1}}} has no effect on {{{command}}}'s exit status, which is still in {{{$?}}}. The assignment to {{{output}}} has no effect on {{{command}}}'s exit status, which is still in {{{$?}}}.
Line 27: Line 27:
If you don't ''actually'' want the exit status, but simply want to take an action upon success or failure: 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`:

{{{#!highlight bash
if command; then
    printf "it succeeded\n"
else
    printf "it failed\n"
fi
}}}

Or if you want to capture stdout as well as taking action on success/failure, without explicitly storing or checking `$?`:

{{{#!highlight bash
if output=$(command); then
    printf "it succeeded\n"
    ...
}}}

If you don't understand the difference between ''standard output'' and ''standard error''. here is a brief demonstration. A sane command writes the output that you request to standard output (stdout) and only writes ''errors'' to standard error (stderr). Like so:
Line 30: Line 48:
    if command
    then
        echo "it succeeded"
    else
        echo "it failed"
    fi
$ dig +short A mywiki.wooledge.org
199.231.184.176
$ ip=$(dig +short A mywiki.wooledge.org)
$ echo "{$ip}"
{199.231.184.176}

$ ls no-such-file
ls: cannot access 'no-such-file': No such file or directory
$ output=$(ls no-such-file)
ls: cannot access 'no-such-file': No such file or directory
$ echo "{$output}"
{}
Line 38: Line 62:
Or (shorter): In the example above, `dig` wrote output to stdout, which was captured in the `ip` variable. `ls` encountered an error, so it did ''not'' write anything to stdout. It wrote to stderr, which was ''not'' captured (because we didn't use `2>&1`). The error message appeared directly on the terminal instead.

Some commands are ''not'' well-written, however, and may write information to the wrong place. You must keep an eye out for such commands, and work around them when necessary. For example:
Line 41: Line 67:
    command && echo "it succeeded" || echo "it failed" $ vers=$(python --version)
Python 2.7.13
$ echo "{$vers}"
{}
Line 44: Line 73:
What if you want the exit status of a command in a few that are piped to each other? Use the {{{PIPESTATUS}}} array (BASH only). Say you want the exit status of {{{grep}}} in the following: Even though we specifically ''asked'' for the version number, `python` wrote it to stderr. Thus, it appeared on the terminal, and was not captured in the `vers` variable. You'd need to use `2>&1` here.
Line 46: Line 75:
{{{
    grep foo somelogfile | head -5
    result=${PIPESTATUS[0]}
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. In the case of Zsh, it's lower-cased {{{pipestatus}}}). Say you want the exit status of {{{grep}}} in the following:

{{{#!highlight bash
grep foo somelogfile | head -n5
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`:

{{{#!highlight bash
set -o pipefail
if ! grep foo somelogfile | head -n5; then
    printf "uh oh\n"
fi
Line 53: Line 93:
{{{
    var=$(command 2>&1 >/dev/null) # Save stderr, discard stdout.
    var=$(command 2>&1 >/dev/tty) # Save stderr, send stdout to the terminal.
    var=$(command 3>&2 2>&1 1>&3-) # Save stderr, send stdout to stderr
{{{#!highlight bash
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 58: Line 98:

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.
Line 61: Line 115:
{{{
    exec 3>&1 # Save the place that stdout (1) points to.
    var=$(command 2>&1 1>&3)    # Run command. stderr is captured.
    exec 3>&- # Close FD #3.
{{{#!highlight bash
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 66: Line 120:
    # Or this alternative:
    { var=$(command 2>&1 1>&3-) ;} 3>&1 # Capture stderr, let stdout through.
# Or this alternative, which captures stderr, letting stdout through:
{ output=$(command 2>&1 1>&3-) ;} 3>&1
Line 70: Line 124:
In the last example above, note that {{{1>&3-}}} duplicates FD 3 and stores a copy in FD 1, and then closes FD 3. 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>&-`.
Line 72: Line 126:
What you ''cannot'' do is capture stdout in one variable, and stderr in another, using only FD redirections. You must use a temporary file to achieve that one. 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.
Line 75: Line 129:
{{{
   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*}
{{{#!highlight bash
cmd
() { curl -s -v http://www.google.fr; }

result=$(
   
{ stdout=$(cmd) ; } 2>&1
    printf "this line is the separator\n"
    printf "%s\n" "$stdout"
)
var_out=${result#*this line is the separator$'\n'}
var_err=${result%$'\n'this line is the separator*}
Line 82: Line 142:
''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.'' And if you want the exit code of your cmd (here a modification in the case of if the cmd stdout nothing)
{{{#!highlight bash
cmd() { curl -s -v http://www.google.fr; }

result=$(
    { stdout=$(cmd); returncode=$?; } 2>&1
    printf "this is the separator"
    printf "%s\n" "$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.''

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:

   1 output=$(command)      # stdout only; stderr remains uncaptured
   2 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:

   1 command
   2 status=$?

If you want both:

   1 output=$(command)
   2 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:

   1 if command; then
   2     printf "it succeeded\n"
   3 else
   4     printf "it failed\n"
   5 fi

Or if you want to capture stdout as well as taking action on success/failure, without explicitly storing or checking $?:

   1 if output=$(command); then
   2     printf "it succeeded\n"
   3     ...

If you don't understand the difference between standard output and standard error. here is a brief demonstration. A sane command writes the output that you request to standard output (stdout) and only writes errors to standard error (stderr). Like so:

$ dig +short A mywiki.wooledge.org
199.231.184.176
$ ip=$(dig +short A mywiki.wooledge.org)
$ echo "{$ip}"
{199.231.184.176}

$ ls no-such-file
ls: cannot access 'no-such-file': No such file or directory
$ output=$(ls no-such-file)
ls: cannot access 'no-such-file': No such file or directory
$ echo "{$output}"
{}

In the example above, dig wrote output to stdout, which was captured in the ip variable. ls encountered an error, so it did not write anything to stdout. It wrote to stderr, which was not captured (because we didn't use 2>&1). The error message appeared directly on the terminal instead.

Some commands are not well-written, however, and may write information to the wrong place. You must keep an eye out for such commands, and work around them when necessary. For example:

$ vers=$(python --version)
Python 2.7.13
$ echo "{$vers}"
{}

Even though we specifically asked for the version number, python wrote it to stderr. Thus, it appeared on the terminal, and was not captured in the vers variable. You'd need to use 2>&1 here.

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. In the case of Zsh, it's lower-cased pipestatus). Say you want the exit status of grep in the following:

   1 grep foo somelogfile | head -n5
   2 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:

   1 set -o pipefail
   2 if ! grep foo somelogfile | head -n5; then
   3     printf "uh oh\n"
   4 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:

   1 output=$(command 2>&1 >/dev/null)  # Save stderr, discard stdout.
   2 output=$(command 2>&1 >/dev/tty)   # Save stderr, send stdout to the terminal.
   3 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:

   1 exec 3>&1                    # Save the place that stdout (1) points to.
   2 output=$(command 2>&1 1>&3)  # Run command.  stderr is captured.
   3 exec 3>&-                    # Close FD #3.
   4 
   5 # Or this alternative, which captures stderr, letting stdout through:
   6 { 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:

   1 cmd() { curl -s -v http://www.google.fr; }
   2 
   3 result=$(
   4     { stdout=$(cmd) ; } 2>&1
   5     printf "this line is the separator\n"
   6     printf "%s\n" "$stdout"
   7 )
   8 var_out=${result#*this line is the separator$'\n'}
   9 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)

   1 cmd() { curl -s -v http://www.google.fr; }
   2 
   3 result=$(
   4     { stdout=$(cmd); returncode=$?; } 2>&1
   5     printf "this is the separator"
   6     printf "%s\n" "$stdout"
   7     exit "$returncode"
   8 )
   9 returncode=$?
  10 
  11 var_out=${result#*this is the separator}
  12 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.


CategoryShell

BashFAQ/002 (last edited 2023-05-11 13:36:34 by emanuele6)