Capturing `stderr` during Fish's Command Substitution

I've been working on extending Spack's fish-shell support, and I came across an unusual problem in fish: when I use a nested version of command substitution (ie. the braces-operator (cmd)), the outer command Substitution refused to capture stderr. This can be demonstrated using the (rather dumb) following example:

function cause_error
    echo "this is a message"
    echo "this is another message"
    echo "this is an error" 1>&2
    return 1
end


function spam
    if set output (cause_error)
        echo "no error: $status"
    else
        echo "error: $status"
    end
end

To my frustration, I found that calling:

set all_output (spam 2>&1)

does not re-route this is an error into all_output as one might expect. Instead what happens is that, since the command Substitution in spam (if set output (cause_error)) does not redirect stderr, the stderr output from cause_error cannot be captured by set all_output (spam 2>&1). Currently this seems to be a bug in fish, so I had to be creative by writing my one capture_all version of eval (see below).

Quick and Dirty Solution

While we're waiting for the fish folks to fix this bug, the following function runs a command using eval and captures status, stdout, and stderr.

function capture_all
    begin;
        begin;
            eval $argv[1]
            set $argv[2] $status  # read sets the `status` flag => capture here
        end 2>| read -z __err
    end 1>| read -z __out

    # output arrays
    set $argv[3] (echo $__out | string split \n)
    set $argv[4] (echo $__err | string split \n)

    return 0
end

It's a bit cumbersome (because everything is managed manually), but it should do the job. Everything is passed using global variables (that the user supplies). The command needs to be passed a single string, and the user-defined target variables need to be set using set -g beforehand. For example:

set -g stat
set -g out
set -g err
capture_all "cause_error" stat out err

should set

  1. stat to 1
  2. out[1] to this is a message, and out[2] to this is another message
  3. err[1] to this is an error.

where the purpose of stat is to monitor the execution of eval.