我对 shell 脚本编写比较陌生,所以如果这看起来是一个简单的问题,我深表歉意。我有一个 Linux VM ( debian, version 11 (bullseye)
),我可以通过 ssh 进入 ( ssh <ip>
),安装了一些依赖项(homebrew、pyenv 等),并且能够成功使用它们。但是,当我尝试在脚本中或使用 Mac 终端从 VM ( ) 外部运行命令时ssh <user>@<ip> pyenv versions
,会收到bash: line 1: pyenv: command not found
相关错误。
我认为这可能与所解释的内容有关这里,但我不完全确定如何规避这个问题。
在下面的评论中添加@terdon 询问的其他详细信息:
$ which pyenv
/home/linuxbrew/.linuxbrew/bin/pyenv
$ grep /home/linuxbrew/.linuxbrew/bin/ ~/.bashrc ~/.bash_profile ~/.profile /etc/bash.bashrc /etc/profile
grep: /home/f0p021s/.bash_profile: No such file or directory
/home/f0p021s/.profile:eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
我还意识到,如果我从虚拟机中查看路径,它看起来像这样:
$ echo $PATH
/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
当我尝试从本地计算机运行类似的命令时,它看起来有所不同:
$ ssh <user>@<ip> 'echo $PATH'
/usr/local/bin:/usr/bin:/bin:/usr/games
答案1
当您 ssh 进入计算机时,您将启动交互式登录 shell。当您运行 时ssh ip command
,您将启动一个非交互式、非登录 shell:
$ ssh localhost 'echo $- $0; shopt login_shell'
hBc bash
login_shell off
$ ssh localhost
[terdon@tpad ~]$ echo $- $0; shopt login_shell
himBHs -bash
login_shell on
看这个答案了解这实际上向您展示的内容的详细信息。
每种类型的 shell 在启动时读取的文件都不同。来自man bash
(强调我的):
当 bash 作为交互式登录 shell,或者作为带有 --login 选项的非交互式 shell,它首先从文件读取并执行命令/etc/配置文件,如果该文件存在。读取该文件后,它会查找~/.bash_profile、~/.bash_login 和 ~/.profile,按此顺序,并从第一个存在且可读的命令中读取并执行命令。启动 shell 时可以使用 --noprofile 选项来禁止此行为。
例如,当 bash 以非交互方式启动时,要运行 shell 脚本,它会在环境中查找变量 BASH_ENV,如果出现则扩展其值,并使用扩展后的值作为要读取的文件名并执行。 Bash 的行为就像执行了以下命令:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
但 PATH 变量的值不用于搜索文件名。
现在,您已经向我们展示了该pyenv
命令已添加到您的$PATH
in 中/home/f0p021s/.profile
。正如您在上面所看到的,该文件 ( ~/.profile
) 由交互式登录 shell 读取,但不会由非交互式或非登录 shell 读取,因为这些文件仅读取 所指向的内容,$BASH_ENV
并且默认情况下为空。
所以,你的选择是:
只需使用命令的完整路径:
ssh ip /home/linuxbrew/.linuxbrew/bin/pyenv
来源
~/.profile
:ssh ip '. ~/.profile; pyenv'
答案2
您的诊断可能是正确的。
为了避免这个问题,你需要验证哪个rcfile是不是在启动时执行(它可能是设置 PATH 的脚本),在您引用的答案中的脚本中。
然后,您可以重写~/.bashrc
以避免该问题。
或者您可以尝试更改命令:
ssh <user>@<ip> '. /etc/profile; pyenv versions'.
这将导入/etc/profile
到当前执行的 shell 中。您可能需要导入/etc/bashrc
or~/.profile
代替/etc/profile
, 或点它们(注意点和文件名之间有一个空格;此外,整个命令都被引用)。
测试(在 Ubuntu 上)
登录方便的虚拟机:
leonardo@aladdin:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/opt/android-sdk-linux/platform-tools:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin
leonardo@aladdin:~$ logout
现在尝试直接使用 SSH:
$ ssh leonardo@aladdin "echo \$PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
好的。所以,不同的路径。例如,java-8-oracle 从哪里来?您将进行一些类似的检查,在 PATH 中使用一些长字符串,该字符串仅在登录 shell 中时才存在。
作为虚拟机的 root,我在 PATH 分配之后的各处查找该字符串/etc
(如果它不起作用,我将通过指定 来重试主点文件/home/.*
)。
# grep -r PATH.*java-8-oracle /etc
/etc/profile.d/jdk.csh:setenv PATH ${PATH}:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin
/etc/profile.d/jdk.sh:export PATH=$PATH:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin
PATH 分配就在那里,显而易见。
它来自“/etc/profile.d”中的一个文件。现在需要一些琐事 - 知道“whatever.d”文件夹是通常从任何二进制文件,并且它们的内容按顺序进行通配。
因此,如果该文件夹是/etc/profile.d
,我希望这个技巧能够被实现/etc/profile
。无论如何,必须有人干预“profile.d”,所以我可以寻找那:
# grep -r "profile\.d" /etc
事实上,我在几行输出中发现:
/etc/bash_completion.d/libreoffice.sh:# It is based on /etc/profile.d/complete.bash from SUSE Linux 10.1
/etc/profile:if [ -d /etc/profile.d ]; then
/etc/profile: for i in /etc/profile.d/*.sh; do
/etc/apparmor.d/snap/abstractions/bash: /etc/profile.dos r,
...
第二行和第三行是我感兴趣的。他们说,“如果有一个名为 /etc/profile.d 的文件夹,那么;对于该文件夹中以“.sh”结尾的每个文件,执行...” 。这是确切地这是我所期望的。
所以,我可以相信跑步/etc/profile
会起到作用。
确实有效:
ssh leonardo@aladdin ". /etc/profile; echo \$PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/opt/android-sdk-linux/platform-tools:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin