Fabric: 远程服务器上的环境变量和 PATH 问题(不采购 .bashrc)

创建于 2016-10-09  ·  22评论  ·  资料来源: fabric/fabric

你好,

我试图在运行 fab 的 run 命令时加载 .bashrc 文件(即 source /home/ubuntu/.bashrc)以添加一些环境变量并扩展路径变量:

run('source /home/ubuntu/.bashrc && echo $PATH')

这仅显示我:

[[email protected]] 出:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin: /bin:/usr/games:/usr/local/games

而不是我手动登录远程服务器时看到的更长的路径列表。

如何让 fab 在我的远程主目录中正确导入 .bashrc 文件?

谢谢!

最有用的评论

这是一个 bash 的东西,而不是一个 fab 的东西。

bash 决定在启动时获取哪些文件可能很复杂http://blog.flowblok.id.au/2013-02/shell-startup-scripts.html和 debian/ubuntu(和大多数发行版)在/etc/profile有一些自定义~/.bashrc

fab 使用的默认 shell 是/bin/bash -l -c ,而-l使其成为“登录”shell。 如果没有 debian/ubuntu 自定义,bash“登录”shell 有可能获取~/.bash_profile而不是~/.bashrc

但是在 ubuntu 16.04 上,即使对于 login-shell,它似乎也默认使用.bashrc来源。 但是添加在默认.bashrc底部的行不会被处理,因为如果它检测到非交互式运行,它会在顶部附近退出。

这里我在 ubuntu-16.04 默认用户.bashrc添加了两行

# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

export VAR1=val1

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

export VAR2=val2

# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth
...

这是我正在测试的 fabfile:

from fabric.api import run, env, task

<strong i="23">@task</strong>
def get_myvars():
    run("echo VAR1=$VAR1 VAR2=$VAR2")

结果:

$ fab -H testpy05.ec2.st-av.net get_myvars
[testpy05.ec2.st-av.net] Executing task 'get_myvars'
[testpy05.ec2.st-av.net] run: echo VAR1=$VAR1 VAR2=$VAR2
[testpy05.ec2.st-av.net] out: VAR1=val1 VAR2=
[testpy05.ec2.st-av.net] out: 
...

所有22条评论

这是一个 bash 的东西,而不是一个 fab 的东西。

bash 决定在启动时获取哪些文件可能很复杂http://blog.flowblok.id.au/2013-02/shell-startup-scripts.html和 debian/ubuntu(和大多数发行版)在/etc/profile有一些自定义~/.bashrc

fab 使用的默认 shell 是/bin/bash -l -c ,而-l使其成为“登录”shell。 如果没有 debian/ubuntu 自定义,bash“登录”shell 有可能获取~/.bash_profile而不是~/.bashrc

但是在 ubuntu 16.04 上,即使对于 login-shell,它似乎也默认使用.bashrc来源。 但是添加在默认.bashrc底部的行不会被处理,因为如果它检测到非交互式运行,它会在顶部附近退出。

这里我在 ubuntu-16.04 默认用户.bashrc添加了两行

# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

export VAR1=val1

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

export VAR2=val2

# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth
...

这是我正在测试的 fabfile:

from fabric.api import run, env, task

<strong i="23">@task</strong>
def get_myvars():
    run("echo VAR1=$VAR1 VAR2=$VAR2")

结果:

$ fab -H testpy05.ec2.st-av.net get_myvars
[testpy05.ec2.st-av.net] Executing task 'get_myvars'
[testpy05.ec2.st-av.net] run: echo VAR1=$VAR1 VAR2=$VAR2
[testpy05.ec2.st-av.net] out: VAR1=val1 VAR2=
[testpy05.ec2.st-av.net] out: 
...

乍一看听起来合法,谢谢@ploxiln!

谢谢你的详细解答!

这似乎与标准 ssh 行为不同,并且很难调试。 当我手动 ssh 一切正常时,由于未在 OSX 中运行 .bash_profile,结构具有不同的 PATH 值。

@bitprophet这可能是 OSX 错误??

@code-tree 您使用的是 Fabric 1 还是 Fabric 2? 1.x 使用显式 shell 包装器,这就是它与标准 OpenSSH 客户端不同的原因; 版本 2 不做任何包装,就 sshd 代表您运行的内容而言,它的行为应该更像 OpenSSH 客户端。

版本 1 有一些 env 配置值选项用于更改该 shell 包装器,因此您可能想尝试向其中添加标志,例如-i ,其中 IIRC 是“运行交互模式并获取一些额外文件”的 bash。

这很奇怪,因为我使用的是2.2.1 。 我目前必须执行以下操作才能使我的应用程序正常工作:
c.run('bash -l -c "python3 ./configure.py"')
当我安装 Python 3 时,它通过我的.bash_profile将自己添加到 PATH 中,它不是由 Fabric 运行的。 我也没有对标准的内置 sshd 配置进行任何修改。

那很奇怪,我必须看看我是否可以复制。 只是做了一个快速的理智跟踪来证明我的意思:Fabric 2 没有做任何“特殊”的事情:sshd 驱动的执行:

@code-tree 也是,如果可以的话,请发布一些关于您正在运行的内容和其他环境细节(客户端和服务器操作系统/版本等)的更多详细信息。

当然,我正在运行旧的 OSX 版本作为服务器。 如果您可以确认在较新的 MacOS 上一切正常,那么好吧。 但如果不是,那么可能是 MacOS 作为服务器的一般问题?

  • 客户端:Ubuntu 16.04、Python 3.6.6、OpenSSH 7.2p2、Bash 4.3.48、Fabric 2.2.1
  • 服务器:OSX 10.11、Python 3.6.6、OpenSSH 6.9p1、Bash 3.2.57

旁注,我想知道这是否真的是误诊案例:

由于未在 OSX 中运行 .bash_profile,fabric 具有不同的 PATH 值

这可能是 #1744 的一个例子,其中 Fabric 2 与ssh在将本地环境变量分流到管道方面不同吗? (取决于所讨论的确切 env vars 以及它们在本地和远程是否相似;@code-tree 可以通过仔细检查游戏中的确切值来轻松反驳这一理论:他们的本地和远程 bash_profile ... 😁)


但无论如何我都会直接解决这个问题,只是为了让我知道我不是疯了:我对 sshd 在 shell 和源文件方面所做的事情的理解。

另外我想知道这是否与#1816的场景/智能有关,这也是关于shell和启动文件的。 它_可能_不直接相关,因为这里关于ssh行为一种方式而 Fabric 行为另一种方式的断言(而不是“sshd 正常调用 shell 的方式”),但无论如何都值得链接。

发现试图偶然发现一堆我记得在过去几个月里做过的深度 sshd 代码潜水,与这些方面的东西有关。 我现在找不到它,虽然这很烦人。 编辑:啊,我认为这是一个无关的 Paramiko 问题(不链接,因为没有点混淆)关于在身份验证之前执行命令。 好的。

好的,那么当我们请求命令 exec 时到底发生了什么?

  • SSH-the-protocol 要求您创建shell通道请求(加载用户定义的登录 shell 程序并且不接受任何command )或exec通道请求(它只是说“执行给定的命令字符串,其中可能包含一个路径”并且没有指定更多)
  • Paramiko 是后者,正如我们在上面发现的那样。
  • 那么 OpenSSH 本身实际上在做什么呢? 我将查看 openssh-portable 的本地结帐(在775f8a23f2353f5869003c57a213d14b28e0736e

好吧,首先我会闲逛一下。 针对任意 Debian 8.10 容器(运行 OpenSSH 6.7!)运行,我在执行ssh localhost whoami时在 DEBUG3 级日志中看到了这一点:

debug1: server_input_channel_req: channel 0 request exec reply 1
debug1: session_by_channel: session 0 channel 0
debug1: session_input_channel_req: session 0 req exec
Starting session: command for root from 172.17.0.1 port 42598

fab -H localhost -- whoami

debug1: server_input_channel_req: channel 0 request exec reply 1
debug1: session_by_channel: session 0 channel 0
debug1: session_input_channel_req: session 0 req exec
Starting session: command for root from 172.17.0.1 port 42610

好的,他们都在做exec ......这是可以预料的。 进程树有什么不同吗? 不应该,而且……没有。 两者看起来像这样:

sshd,1 -D -e
  └─sshd,1535
      └─pstree,1537 -alpU

那么,绝对没有外壳在起作用? 它到底在做什么? 我可以使用像&&这样的外壳吗?

我可以! 例如whoami && id工作正常。 考虑到我对 Unix 进程控制的理解,这有点奇怪; sshd 似乎不太可能只是在执行exec(string) ,但是如果它使用 shell 进行解释,我们不会在pstree看到吗?

我认为是时候真正深入研究 openssh-portable 并看看是什么了。


另外,嗯,我 _did_ 过去做了一部分跟踪(但出于不同的原因/重点),如 2016 年 12 月: https :


我没有在pstree看到 shell 的原因是因为使用了execve ,它替换了父进程而不是生成子进程。 很高兴知道。 (编辑:错误,这只是用外壳替换了 sshd 子进程;外壳本身可能正在执行替换我风格的 exec 调用;请参阅下面的评论。)


无论如何,所以! 这仍然意味着 Fabric 和 OpenSSH 的客户端在 shell 执行方面做着完全相同的事情; 他们中的任何一个都不会修改 OpenSSH 代码库的这些部分。 它应该始终类似于bash -c "python3 ./configure.py" ,实际值bash取决于@code-tree 的 macOS 级别用户及其配置的 shell。

让我想知道我关于 env var 传输作为差异化因素的预感是否准确,因为这是我唯一能想到的另一件事:两个执行环境如何不同。 FWIW 在我以 CLI 为重点的测试中, env在两个系统上报告相同,并且不管其他与环境相关的问题,_default_ 行为(由于 SSH 安全策略)应该始终是没有本地环境“泄漏”到另一边。

是的,我很确定 sshd 总是在用户的 shell 中运行命令字符串(在 /etc/passwd 中配置)。 但是,如果它可以解析命令字符串并确定它是单个命令,则它会在命令字符串前面加上“exec”。

$ ssh testdeploy02.ec2.st-av.net pstree -a
...
  |-sshd -D
  |   `-sshd 
  |       `-sshd  
  |           `-pstree -a
$ ssh testdeploy02.ec2.st-av.net 'pstree -a && sleep 1'
...
  |-sshd -D
  |   `-sshd 
  |       `-sshd  
  |           `-bash -c pstree -a && sleep 1
  |               `-pstree -a
$ ssh testdeploy02.ec2.st-av.net 'VAR=-a sh -c "exec pstree \$VAR"'
...
  |-sshd -D
  |   `-sshd 
  |       `-sshd  
  |           `-pstree -a

至于环境变量,这些主要由 ssh 客户端和 sshd 服务器配置控制:

$ grep Env /etc/ssh/*_config
/etc/ssh/ssh_config:    SendEnv LANG LC_*
/etc/ssh/sshd_config:AcceptEnv LANG LC_*

因此 ssh 客户端确实明确发送了一些变量(在命令字符串本身之外),并且它们由 sshd 过滤。 但显然有一些例外:

$ ssh testdeploy02.ec2.st-av.net env | grep TERM
$ ssh -t testdeploy02.ec2.st-av.net env | grep TERM
TERM=xterm-256color

实际上,我认为“自动执行以单个命令替换 shell”行为是 bash 的一个特性:

$ dash -c "pstree -a"
...
  ├─sshd -D
  │   └─sshd 
  │       └─sshd  
  │           └─bash
  │               └─dash -c pstree -a
  │                   └─pstree -a
$ bash -c "pstree -a"
  ├─sshd -D
  │   └─sshd 
  │       └─sshd  
  │           └─bash
  │               └─pstree -a

编辑:为了完整性:

$ bash -c "pstree -a && sleep 1"
  ├─sshd -D
  │   └─sshd 
  │       └─sshd  
  │           └─bash
  │               └─bash -c pstree -a && sleep 1
  │                   └─pstree -a

啊,是的,我错了, execve只是意味着sshd 子 proc是被替换的,所以之后会发生什么取决于 shell 本身以及它对-c xxx 。 我的测试是在 zsh 中,fwiw。

另外@ploxiln是的,带有 env vars 的东西正在引用上面链接的其他票证,尽管@code-tree 是否真的遇到了与这些票证不同的症状还有待观察。

抱歉,我认为这是误报。 我不知道通过 ssh 内联执行命令和通过 ssh 登录后执行之间有什么区别。 当我执行ssh host 'echo $PATH'它也没有更新 PATH,因为echo $PATH登录后工作正常。

我仍然不明白为什么 ssh 这样做(似乎我确实必须这样做ssh host 'bash -l -c "python3 ./configure.py"' )。 无论如何,可以肯定地说这毕竟不是 Fabric 问题。 对困惑感到抱歉。

@code-tree 它与前面提到的内容有关 - shell 有几种不同的操作模式,通常称为“登录”和“交互”(通常这些模式位于基本模式之上,两者都被认为是)和哪种模式shell 正在运行,更改它加载的 rc 文件集。

这里的重点是bash -c xxx不被视为“交互式”,因此会跳过加载某些文件 - 例如.bash_profile - 而只是运行bash而没有-c通常是交互式的,并且会加载配置文件。

正如您在上面看到的 - 当您要求 sshd 运行单个命令(如 Fabric 或ssh host command那样)时,它总是运行bash -c <command send down the pipe> ,因此,它始终处于非交互模式。 (除非你做 Fabric 1 所做的事情并使用-l运行你自己的、嵌套的 shell ......但是你正在运行bash -c "bash -l -c \"oh god escaping is hard help\""并且我现在要去吃一些🥃。

无论如何,并不是所有的都丢失了,我需要每隔几年刷新我对这部分事情的记忆,所以呃......现在我精神焕发了😆

这是一个 bash 的东西,而不是一个 fab 的东西。

bash 决定在启动时获取哪些文件可能很复杂http://blog.flowblok.id.au/2013-02/shell-startup-scripts.html和 debian/ubuntu(和大多数发行版)在/etc/profile有一些自定义~/.bashrc

fab 使用的默认 shell 是/bin/bash -l -c ,而-l使其成为“登录”shell。 如果没有 debian/ubuntu 自定义,bash“登录”shell 有可能获取~/.bash_profile而不是~/.bashrc

但是在 ubuntu 16.04 上,即使对于 login-shell,它似乎也默认使用.bashrc来源。 但是添加在默认.bashrc底部的行不会被处理,因为如果它检测到非交互式运行,它会在顶部附近退出。

这里我在 ubuntu-16.04 默认用户.bashrc添加了两行

# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

export VAR1=val1

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

export VAR2=val2

# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth
...

这是我正在测试的 fabfile:

from fabric.api import run, env, task

<strong i="24">@task</strong>
def get_myvars():
    run("echo VAR1=$VAR1 VAR2=$VAR2")

结果:

$ fab -H testpy05.ec2.st-av.net get_myvars
[testpy05.ec2.st-av.net] Executing task 'get_myvars'
[testpy05.ec2.st-av.net] run: echo VAR1=$VAR1 VAR2=$VAR2
[testpy05.ec2.st-av.net] out: VAR1=val1 VAR2=
[testpy05.ec2.st-av.net] out: 
...

你救了我的命。 这是我找到的完美答案:) 非常感谢!

此页面是否有帮助?
0 / 5 - 0 等级