Home Explore Blog CI



nushell

4th chunk of `cookbook/foreign_shell_scripts.md`
db487bbc37377116c7b05e85751d8a584799b7384965c2050000000100000c99
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
```

Title: Detailed Explanation of Script Execution and Environment Capture
Summary
The script executes the provided code via a shell specified with the `^` operator, passing arguments and using `-c` to inline and execute the script. The actual script is passed through the `SCRIPT_TO_SOURCE` environment variable in an unescaped form. Alternatives like `-c $script`, string interpolation, or temporary files are avoided for reasons of safety, complexity, or performance. The executed script includes logging environment variables before and after running the passed script, using a unique fence string to delineate the outputs. The script then isolates and parses the changed environment variables into a record for further use.