如何调用 shell 函数作为子进程?

How to call shell functions as a child process?

我用自制软件 (brew install nvm) 安装了 nvm。我想在我用 Rust 编写的 CLI 中调用它,以在我的 Rust 程序中安装和切换 node.js 版本。

问题是:nvm 是一个 shell 函数,而不是文件入口点。不幸的是,由 Rust tokio::process::Command 生成的子进程仅继承父进程的环境和 PATH,而不是 shell 函数。

Constructs a new Command for launching the program at path program, with the following default configuration:

  • No arguments to the program
  • Inherit the current process's environment
  • Inherit the current process's working directory
  • Inherit stdin/stdout/stderr for spawn or status, but create pipes for output

https://docs.rs/tokio/0.2.0/tokio/process/struct.Command.html

当我尝试调用 tokio::process::Command::new("nvm"); 时,我得到:

thread 'main' panicked at 'Command not fail: Os { code: 13, kind: PermissionDenied, message: "Permission denied" }'

如果我 运行 使用 sudo,则没有任何变化,因为我认为 nvm 对我的子进程不存在。

如何访问 nvm,或者更简单地说,我的 Rust 程序中的 shell 函数?

回答字面上的问题

它不太可能对您有用,但如果您的 shell 是 bash 或具有类似的扩展,您可以通过环境导出 shell 函数。

给定以下名为 example.rs 的 Rust 程序:

use std::process::Command;
use std::process::Stdio;
fn main() {
  Command::new("bash")
    .arg("-c")
    .arg("myfunction")
    .stdout(Stdio::inherit())
    .status();
}

...以下 shell 程序:

myfunction() { echo "This is a function call"; }
export -f myfunction
rustc example.rs && ./example

...将作为输出发出:

This is a function call

采购 In-Process

您还可以让您的 bash 解释器获取定义函数的文件,而不需要将其导出:

use std::process::Command;
use std::process::Stdio;
fn main() {
  Command::new("bash")
    .arg("-c")
    .arg(". \"[=13=]\" && \"$@\"")
    .arg("/path/to/nvm.sh")
    .arg("nvm")
    .arg("install")
    .arg("12")
    .stdout(Stdio::inherit())
    .status();
}

这适用于 nvm install,运行 用于与调用 shell 无关的副作用(下载解释器)。它 不会 nvm use 的上下文中做任何特别有用的事情,因为它在 运行 中的 shell 会立即退出,因此状态会发生变化立即被扔掉。


解释为什么这个答案没用

(用于重新配置用户的交互shell)

如果 nvm 可以这样调用,那么它一开始就不需要是 shell 函数。

nvm 作为一个 shell 函数的全部意义在于让它修改调用它的 shell 的状态(修改 shell 的变量,工作目录,或其他内部状态)。您不能从任何进程修改 shell 的状态,除了 shell 本身、期间、full-stop.

你可以做的是让一个程序将有效的 shell 语法写入它的标准输出,并让 shell 其中定义了函数的 eval 该程序的输出。在不存在其他选项的此类用例中,这是一种相当常见的模式——请参阅人们 运行 eval "$(ssh-agent -s)" 的示例。

请注意,bash 确实允许您将函数导出到环境中——您可以 运行 export -f nvm,然后是一个也是 [=57= 的副本的子进程] 将定义一个 nvm 函数——但这没有什么价值,因为 bash 的新副本有一个单独的环境;更改它不会修改父 shell 的环境,并且 如果 nvm 命令不需要修改父 shell 的环境,则不需要首先是一个函数。