nvm 在 bash 中有效,但在 Python 脚本中执行时无效
nvm works in bash, but not when executed in Python script
安装nvm后一直正常工作,直到我需要用nvm远程连接服务器python。没有报错,也没有切换node版本成功,于是在服务器上写了个脚本测试一下:
test.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)
更新
经过上次测试,代码执行不会再报错或阻塞,但又发现了新的问题。代码好像没有执行successfully.I重写了一个测试脚本
test.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的子进程调用了一些脚本,这些脚本会在'safety'的沙箱中执行,比如nvm
?
错误很明显:系统找不到nvm
命令。这可能是因为子进程中的搜索路径与 shell.
中的搜索路径不同
documentation 对此提出以下建议:
Warning: For maximum reliability, use a fully-qualified path for the executable. To search for an unqualified name on PATH
, use shutil.which()
. On all platforms, passing sys.executable
is the recommended way to launch the current Python interpreter again, and use the -m
command-line format to launch an installed module.
Resolving the path of executable (or the first item of args) is platform dependent. (...)
您也可以只更改命令以包含完整路径。所以像:
os.popen("/usr/bin/nvm use v14.2.0")
要找出正确的路径,请在 shell 中键入 which nvm
。这应该会打印出您的 nvm 可执行文件的完整路径。
更新
虽然上述一般适用于所有应用程序,但问题是为什么在您的路径中找不到 nvm
可执行文件。 documentation explains that the ~/nvm/nvm.sh
script should be called first to "load nvm". I originally suspected that this would just set the PATH variable so the nvm
executable can be found, but looking at nvm.sh
,看起来nvm命令其实不是一个可执行文件而是一个shell函数。这就是为什么在尝试从 Python 脚本执行它时找不到它的原因。
根据this answer,你应该可以运行命令如下:
subprocess.Popen(['bash', '-c', '. ~/nvm/nvm.sh; nvm'])
更新 2
请注意,nvm
命令似乎旨在用于 command-line。该脚本所做的其中一件事是更改环境变量,例如您的 PATH。例如,参见 here。这就是 select 不同版本的方式 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()
都会产生一个新的环境,类似于启动一个新的远程会话到该服务器。
安装nvm后一直正常工作,直到我需要用nvm远程连接服务器python。没有报错,也没有切换node版本成功,于是在服务器上写了个脚本测试一下:
test.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)
更新
经过上次测试,代码执行不会再报错或阻塞,但又发现了新的问题。代码好像没有执行successfully.I重写了一个测试脚本
test.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的子进程调用了一些脚本,这些脚本会在'safety'的沙箱中执行,比如nvm
?
错误很明显:系统找不到nvm
命令。这可能是因为子进程中的搜索路径与 shell.
documentation 对此提出以下建议:
Warning: For maximum reliability, use a fully-qualified path for the executable. To search for an unqualified name on
PATH
, useshutil.which()
. On all platforms, passingsys.executable
is the recommended way to launch the current Python interpreter again, and use the-m
command-line format to launch an installed module.Resolving the path of executable (or the first item of args) is platform dependent. (...)
您也可以只更改命令以包含完整路径。所以像:
os.popen("/usr/bin/nvm use v14.2.0")
要找出正确的路径,请在 shell 中键入 which nvm
。这应该会打印出您的 nvm 可执行文件的完整路径。
更新
虽然上述一般适用于所有应用程序,但问题是为什么在您的路径中找不到 nvm
可执行文件。 documentation explains that the ~/nvm/nvm.sh
script should be called first to "load nvm". I originally suspected that this would just set the PATH variable so the nvm
executable can be found, but looking at nvm.sh
,看起来nvm命令其实不是一个可执行文件而是一个shell函数。这就是为什么在尝试从 Python 脚本执行它时找不到它的原因。
根据this answer,你应该可以运行命令如下:
subprocess.Popen(['bash', '-c', '. ~/nvm/nvm.sh; nvm'])
更新 2
请注意,nvm
命令似乎旨在用于 command-line。该脚本所做的其中一件事是更改环境变量,例如您的 PATH。例如,参见 here。这就是 select 不同版本的方式 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()
都会产生一个新的环境,类似于启动一个新的远程会话到该服务器。