let env_out = with-env { SCRIPT_TO_SOURCE: $script_contents } {
^$shell ...$arguments -c ` ... `
}
```
Essentially, this calls the specified shell (using `^` to run the value as a
command) with any arguments specified. It also passes `-c` with an inlined
script for the shell, which is the syntax to immediately execute a passed script
and exit in most shells.
The `with-env { SCRIPT_TO_SOURCE: $script_contents }` block defines an
additional environment variable with the actual script we want to run. This is
used to pass the script in an unescaped string form, where the executing shell is entirely responsible for parsing it. The alternatives would have been:
- Passing the script via `-c $script`, but then we couldn't (safely) add our own
commands to log out the environment variables after the script ran.
- Using string interpolation, but then we would be responsible for fully
escaping the script, so that the `eval "($script)"` line doesn't break due to
quotation marks. With the variable expansion in the foreign shell, that shell
does not need the value to be escaped; just as nu is normally able to pass a
string with any contents to a command as a single string argument.
- Using a (temporary or existing) file containing the script - This would also
work, but seems unnecessary and potentially slower.
Then the external shell executes the script we passed:
```bash
env
echo '<ENV_CAPTURE_EVAL_FENCE>'
eval "$SCRIPT_TO_SOURCE"
echo '<ENV_CAPTURE_EVAL_FENCE>'
env -u _ -u _AST_FEATURES -u SHLVL
```
These POSIX-shell compatible commands, available in UNIX-like OSes, do the
following:
1. Log out all environment variables at the start of the script. These may be
different than the ones in nushell, because the shell might have defined
variables on startup and all passed-in variables have been serialized to
strings by nushell.
2. Log `<ENV_CAPTURE_EVAL_FENCE>` to stdout, this is so we later know where the
first `env` output stopped. The content of this is arbitrary, but it is
verbose to reduce the risk of any env var having this string in its contents.
3. Run the actual shell script in the current context, using `eval`. The double
quotes around the variable are necessary to get newlines to be interpreted
correctly.
4. Log the "fence" again to stdout so we know where the "after" list of
variables starts.
5. Log all environment variables after the script run. We are excluding a few
variables here that are commonly changed by a few shells that have nothing to
do with the particular script that was run.
We then take the script output and save all lines from the `env` output before
and after running the passed script, using the `<ENV_CAPTURE_EVAL_FENCE>` logs.
```nu
# <shell invocation>
| split row '<ENV_CAPTURE_EVAL_FENCE>'
| {
before: ($in | first | str trim | lines)
after: ($in | last | str trim | lines)
}
```
Finally, all that is left to do is to take all env-output lines from the "after"
output that were not there before, and parse them into a record:
```nu
$env_out.after
| where { |line| $line not-in $env_out.before } # Only get changed lines
| parse "{key}={value}"
| transpose --header-row --as-record
```