安装nvm后,一直正常工作,直到我需要使用nvm用python远程连接服务器。切换node版本没有报错也没有成功,所以我在服务器上写了一个脚本进行测试:
测试.py:
import os
res = os.popen("nvm use v14.2.0")
# res = os.popen("~/nvm/nvm.sh use v14.2.0")
print(res)
print(res.read())
输出:
$ python3 test.py
<os._wrap_close object at 0x7f00acdc4ac8>
/bin/sh: nvm: command not found
nvm安装路径为
~/nvm
和~/nvm/nvm.sh
如果我直接在服务器上执行
nvm -v
,输出是0.39.1
。当我直接执行 nvm 时,它工作正常。为什么从Python脚本执行时不起作用?
在nvm安装目录下执行
sh ./nvm.sh
没有任何输出。
-rwxrwxr-x 1 asd asd 139220 Mar 11 15:57 nvm.sh
$ cd ~/nvm
$ sh ./nvm.sh
$
我的
.bashrc
文件:
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=
# User specific aliases and functions
source ~/nvm/nvm.sh
核心问题是
nvm
可以在命令行执行,但是找不到nvm可执行文件所在的路径。或者 nvm.sh
是可执行文件,但当我执行此文件时没有任何反应。
注意:当我输入
which nvm
时,它输出:
/usr/bin/which: no nvm in (/home/pyer/nvm/versions/node/v14.2.0/bin:/usr/local/nodejs/bin:/usr/local/bin :/usr/bin:/usr/local/sbin:/usr/sbin:/usr/lib/golang/bin:/usr/local/apache-maven-3.8.4/bin)
更新
上次测试后,代码执行不再报错或阻塞,但又发现了新的问题。代码好像没有执行成功,我重写了一个测试脚本
测试.py:
import subprocess
def test():
# p = subprocess.Popen(['bash', '-c', '-i', 'nvm use v14.2.0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, executable='/usr/bin/bash')
p = subprocess.Popen(['bash', '-c', '. ~/nvm/nvm.sh; nvm use v14.2.0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, executable='/usr/bin/bash')
out, err = p.communicate()
print(out.decode('utf-8'))
# p = subprocess.Popen(['bash', '-c', '-i', 'nvm list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, executable='/usr/bin/bash')
p = subprocess.Popen(['bash', '-c', '. ~/nvm/nvm.sh; nvm list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, executable='/usr/bin/bash')
out, err = p.communicate()
print(out.decode('utf-8'))
test()
输出:
$ python3 test.py
Now using node v14.2.0 (npm v6.14.4)
v8.9.0
-> v10.24.1
v12.10.0
v14.2.0
v17.5.0
system
default -> v14.2.0
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v17.5.0) (default)
stable -> 17.5 (-> v17.5.0) (default)
lts/* -> lts/gallium (-> N/A)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1
lts/erbium -> v12.22.10 (-> N/A)
lts/fermium -> v14.19.0 (-> N/A)
lts/gallium -> v16.14.1 (-> N/A)
显示正在使用
v14.2.0
,但实际上是默认的v10.24.1
。我发现nvm
与其他命令有点不同。在我的机器上,其他命令都有可执行文件。但是nvm加载了.bashrc
/.zshrc
,所以我有两个猜测。 1是因为python调用的bash没有加载.bashrc
,所以python执行时的nvm和命令行执行的nvm不一样,可能范围不一样? 2是因为python的子进程调用了一些脚本,这些脚本为了‘安全’会在沙箱中执行,比如nvm
?
错误非常明显:系统找不到
nvm
命令。这可能是因为子进程中的搜索路径与 shell 中的搜索路径不同。
文档对此给出了以下建议:
警告:为了获得最大可靠性,请使用可执行文件的完全限定路径。要在
上搜索不合格名称,请使用PATH
。在所有平台上,推荐通过shutil.which()
来再次启动当前的 Python 解释器,并使用sys.executable
命令行格式来启动已安装的模块。-m
解析可执行文件的路径(或参数的第一项)是平台相关的。 (...)
您也可以更改命令以包含完整路径。所以类似:
os.popen("/usr/bin/nvm use v14.2.0")
要找出正确的路径,请在 shell 中输入
which nvm
。这应该打印您的 nvm 可执行文件的完整路径。
虽然上述情况一般适用于所有应用程序,但问题是为什么在您的路径中找不到
nvm
可执行文件。 documentation 解释说应该首先调用 ~/nvm/nvm.sh
脚本来“加载 nvm”。我原本怀疑这只是设置 PATH 变量,以便可以找到 nvm
可执行文件,但是查看 nvm.sh
,似乎 nvm 命令实际上不是可执行文件,而是 shell 函数。这就是为什么当尝试从 Python 脚本执行它时找不到它。
根据这个答案,您应该能够运行以下命令:
subprocess.Popen(['bash', '-c', '. ~/nvm/nvm.sh; nvm'])
请注意,
nvm
命令似乎旨在在命令行上使用。该脚本所做的事情之一是更改环境变量,例如 PATH。例如,请参见此处。如果您输入 nvm use v14.2.0
,它就会选择不同的版本。
如果你按照你的方式从Python调用它,它不会有任何效果。它将更改当前环境中的
PATH
变量,然后将其关闭。第二次调用 subprocess.Popen
将创建一个新环境,在该环境中将再次使用默认版本。不过,以下命令应该按预期工作,因为两个 nvm
命令现在将在同一个 shell 进程中执行:
subprocess.Popen(['bash', '-c', '. ~/nvm/nvm.sh; nvm use v14.2.0; nvm list'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
executable='/usr/bin/bash')
但是,您应该真正考虑一下您想要在这里实现的目标。为什么从 Python 脚本中调用
nvm
?如果您只是执行它来设置正确的版本,您不妨将该逻辑包含在您的 Python 脚本中。如果您只是调用一些 nvm
命令,您可以轻松地在 bash 脚本中执行此操作。但是,如果您确实愿意,可以这样做,但您必须记住每次调用 subprocess.Popen()
都会导致一个新环境,类似于启动到该服务器的新远程会话。