Differences between revisions 9 and 37 (spanning 28 versions)
Revision 9 as of 2007-09-15 23:40:20
Size: 2998
Editor: ppp089210012069
Comment: move swap, add a variant to exec
Revision 37 as of 2014-11-21 23:52:48
Size: 9612
Editor: ormaaj
Comment: POSIX tests are pointless here. I'm strongly considering moving this code dump because it has no comments or explanation. Examples should illustrate a concept and shorter is better.
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
[[Anchor(faq2)]]
== How can I store the return value of a command in a variable? ==
Well, that depends on exactly what you mean by that question. Some people want to store the command's ''output'' (either stdout, or stdout + stderr); and others want to store the command's ''exit status'' (0 to 255, with 0 typically meaning "success").

If you want to capture the output:

{{{
    var=$(command) # stdout only; stderr remains uncaptured
    var=$(command 2>&1) # both stdout and stderr will be captured
}}}

If you want the exit status:
<<Anchor(faq2)>>
== 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 [[CommandSubstitution|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:
Line 16: Line 16:
    var=$?     status=$?
Line 22: Line 22:
    var1=$(command)
    var2=$?
}}}
The assignment to {{{var1}}} has no effect on {{{command}}}'s exit status, which is still in {{{$?}}}.

If you don't ''actually'' want the exit status, but simply want to take an action upon success or failure:

{{{
    if command
    then
    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
Line 38: Line 37:
Or (shorter):

{{{
    command && echo "it succeeded" || echo "it failed"
}}}

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:
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:
Line 48: Line 49:
    result=${PIPESTATUS[0]}     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
Line 54: Line 64:
    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
}}}
    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>&-`

 1. Let's assume this is run in a terminal, so stdin, stdout and stderr are all initially connected to the terminal (tty).
 || fd 0 (stdin) || fd 1 (stdout) || fd 2 (stderr) ||
 || /dev/tty || /dev/tty || /dev/tty ||
 1. `$(...)`: 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).
 || fd 0 (stdin) || fd 1 (stdout) || fd 2 (stderr) ||
 || /dev/tty || pipe || /dev/tty ||
 1. `3>&2`: 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").
 || fd 0 (stdin) || fd 1 (stdout) || fd 2 (stderr) || fd 3 ||
 || /dev/tty || pipe || /dev/tty || /dev/tty ||
 1. `2>&1`: 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.
 || fd 0 (stdin) || fd 1 (stdout) || fd 2 (stderr) || fd 3 ||
 || /dev/tty || pipe || pipe || /dev/tty ||
 1. `1>&3`: 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.
 || fd 0 (stdin) || fd 1 (stdout) || fd 2 (stderr) || fd 3 ||
 || /dev/tty || /dev/tty || pipe || /dev/tty ||
 1. `3>&-`: Finally, we close FD 3 as it is no longer necessary.
 || fd 0 (stdin) || fd 1 (stdout) || fd 2 (stderr) ||
 || /dev/tty || /dev/tty || pipe ||

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 63: Line 100:
    var=$(command 2>&1 1>&3)    # Run command. stderr is captured.     output=$(command 2>&1 1>&3) # Run command. stderr is captured.
Line 66: Line 103:
    # Or this alternative:
    { var=$(command 2>&1 1>&3-) ;} 3>&1 # Capture stderr, let stdout through.
}}}

In the last example above, note that {{{1>&3-}}} duplicates FD 3 and stores a copy in FD 1, and then closes FD 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 to achieve that one.
    # 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.
Line 81: Line 118:

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.''

----

Please find below my contribution to capture stdout and stderr in two separate variables using only FD redirections.<<BR>>
All my apologizes if I place it at the wrong place.<<BR>>
Code tested with GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)<<BR>>
You can send me feedback at this address: <<MailTo(jpm4bashfaq AT gmx DOT fr)>>
{{{
# A simple function to send a message on stdout and another one on stderr
foo ()
{
    echo "message on stdout";
    echo "message on stderr" >&2
    return 42
}
}}}
{{{
# Function capturing stdout and stderr in two separate variables using only FD redirections.
foo2 ()
{
  {
    # Code block purpose is to unset these variables and to display unset diagnostic on screen
    unset stdout stderr return_code
    declare -p stdout stderr return_code
  }
  source <(
    {
      {
        {
          stdout="$(foo)" # Call your command here: I put foo as an example
        } 2>&1
        return_code="$?"
        declare -p stdout >&2
        declare -p return_code >&2
      } |
      {
        stderr="$(cat)"
        declare -p stderr >&2
      }
    } 2>&1
  )
  declare -p stdout stderr return_code
}
}}}
Sample session:
{{{
user@domain:~$ foo2
bash: declare: stdout: not found
bash: declare: stderr: not found
bash: declare: return_code: not found
declare -- stdout="message on stdout"
declare -- stderr="message on stderr"
declare -- return_code="42"
user@domain:~$
}}}
Better function based on foo2:
{{{
# Execute a command and store stdout and stderr in two separate variables using only FD redirections
#
# $1: variable name to store stdout command output
# $2: variable name to store stderr command output
# $3: variable name to store command return code
# $4: command to execute
# $5: first command argument
# ...
# $n: last command argument
execute_and_store_std_out_err() {
    local p_stdout=$1
    local p_stderr=$2
    local p_return_code=$3
    command shift 3 2>/dev/null || return

    [[
        $p_stdout != stdout &&
        -n $p_stdout &&
        $p_stderr != stderr &&
        -n $p_stderr &&
        $p_return_code != return_code &&
        -n $p_return_code
    ]] || return

    source <(
        {
            {
                {
                    stdout=$("$@")
                } 2>&1
                return_code=$?
                declare -p stdout return_code >&2
            } |
            {
                stderr=$(</dev/fd/0)
                declare -p stderr >&2
            }
        } 2>&1
    )

    eval "${p_stdout}"'="${stdout}"' &&
    eval "${p_stderr}"'="${stderr}"' &&
    eval "${p_return_code}"'="${return_code}"'
}
}}}
Sample session:
{{{
user@domain:~$ unset out err ret ; declare -p out err ret ; execute_and_store_std_out_err out err ret foo ; declare -p out err ret
bash: declare: out: not found
bash: declare: err: not found
bash: declare: ret: not found
declare -- out="message on stdout"
declare -- err="message on stderr"
declare -- ret="42"
user@domain:~$
}}}
End of <<MailTo(jpm4bashfaq AT gmx DOT fr)>> contribution.
----
CategoryShell

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>&-

  1. Let's assume this is run in a terminal, so stdin, stdout and stderr are all initially connected to the terminal (tty).

    fd 0 (stdin)

    fd 1 (stdout)

    fd 2 (stderr)

    /dev/tty

    /dev/tty

    /dev/tty

  2. $(...): 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).

    fd 0 (stdin)

    fd 1 (stdout)

    fd 2 (stderr)

    /dev/tty

    pipe

    /dev/tty

  3. 3>&2: 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").

    fd 0 (stdin)

    fd 1 (stdout)

    fd 2 (stderr)

    fd 3

    /dev/tty

    pipe

    /dev/tty

    /dev/tty

  4. 2>&1: 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.

    fd 0 (stdin)

    fd 1 (stdout)

    fd 2 (stderr)

    fd 3

    /dev/tty

    pipe

    pipe

    /dev/tty

  5. 1>&3: 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.

    fd 0 (stdin)

    fd 1 (stdout)

    fd 2 (stderr)

    fd 3

    /dev/tty

    /dev/tty

    pipe

    /dev/tty

  6. 3>&-: Finally, we close FD 3 as it is no longer necessary.

    fd 0 (stdin)

    fd 1 (stdout)

    fd 2 (stderr)

    /dev/tty

    /dev/tty

    pipe

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.


Please find below my contribution to capture stdout and stderr in two separate variables using only FD redirections.
All my apologizes if I place it at the wrong place.
Code tested with GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
You can send me feedback at this address: <jpm4bashfaq AT gmx DOT fr>

# A simple function to send a message on stdout and another one on stderr
foo () 
{ 
    echo "message on stdout";
    echo "message on stderr" >&2
    return 42
}

# Function capturing stdout and stderr in two separate variables using only FD redirections.
foo2 ()
{
  {
    # Code block purpose is to unset these variables and to display unset diagnostic on screen
    unset stdout stderr return_code
    declare -p stdout stderr return_code
  }
  source <(
    {
      {
        {
          stdout="$(foo)" # Call your command here: I put foo as an example
        } 2>&1
        return_code="$?"
        declare -p stdout >&2
        declare -p return_code >&2
      } |
      {
        stderr="$(cat)"
        declare -p stderr >&2
      }
    } 2>&1
  )
  declare -p stdout stderr return_code
}

Sample session:

user@domain:~$ foo2
bash: declare: stdout: not found
bash: declare: stderr: not found
bash: declare: return_code: not found
declare -- stdout="message on stdout"
declare -- stderr="message on stderr"
declare -- return_code="42"
user@domain:~$ 

Better function based on foo2:

# Execute a command and store stdout and stderr in two separate variables using only FD redirections
#
# $1: variable name to store stdout command output
# $2: variable name to store stderr command output
# $3: variable name to store command return code
# $4: command to execute
# $5: first command argument
# ...
# $n: last command argument
execute_and_store_std_out_err() {
    local p_stdout=$1
    local p_stderr=$2
    local p_return_code=$3
    command shift 3 2>/dev/null || return

    [[
        $p_stdout != stdout &&
        -n $p_stdout  &&
        $p_stderr != stderr &&
        -n $p_stderr &&
        $p_return_code != return_code &&
        -n $p_return_code
    ]] || return

    source <(
        {
            {
                {
                    stdout=$("$@")
                } 2>&1
                return_code=$?
                declare -p stdout return_code >&2
            } |
            {
                stderr=$(</dev/fd/0)
                declare -p stderr >&2
            }
        } 2>&1
    )

    eval "${p_stdout}"'="${stdout}"' &&
    eval "${p_stderr}"'="${stderr}"' &&
    eval "${p_return_code}"'="${return_code}"'
}

Sample session:

user@domain:~$ unset out err ret ; declare -p out err ret ; execute_and_store_std_out_err out err ret foo ; declare -p out err ret
bash: declare: out: not found
bash: declare: err: not found
bash: declare: ret: not found
declare -- out="message on stdout"
declare -- err="message on stderr"
declare -- ret="42"
user@domain:~$ 

End of <jpm4bashfaq AT gmx DOT fr> contribution.


CategoryShell

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