<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>SSH on Ricardo</title>
        <link>https://sona201.github.io/tags/ssh/</link>
        <description>Recent content in SSH on Ricardo</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <lastBuildDate>Sun, 01 Nov 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://sona201.github.io/tags/ssh/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>SSH 远程执行命令时，为什么环境变量不一样</title>
        <link>https://sona201.github.io/posts/ssh-remote-command-environment/</link>
        <pubDate>Sun, 01 Nov 2026 00:00:00 +0800</pubDate>
        
        <guid>https://sona201.github.io/posts/ssh-remote-command-environment/</guid>
        <description>&lt;p&gt;读到 feihu 的&lt;a class=&#34;link&#34; href=&#34;https://feihu.me/blog/2014/env-problem-when-ssh-executing-command-on-remote/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;《ssh连接远程主机执行脚本的环境变量问题》&lt;/a&gt;，里面的问题很具体：先登录远程机器再运行脚本，一切正常；把脚本直接放在 &lt;code&gt;ssh&lt;/code&gt; 命令后面，却报 &lt;code&gt;command not found&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;原文发表于 2014 年，测试环境是 SUSE Linux Enterprise。它从 Bash 启动方式寻找原因，这个方向仍然适用。不过，要把它作为今天排查问题的参考，还需要补上 SSH 的特殊处理，并区分终端、shell 模式和脚本解释器。这篇笔记按 Bash 与 OpenSSH 的文档重新整理；其他 shell 需要查各自的启动规则。&lt;/p&gt;
&lt;h2 id=&#34;同一个用户不代表同一套环境&#34;&gt;同一个用户，不代表同一套环境&lt;/h2&gt;
&lt;p&gt;先看登录后再运行脚本的方式：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 在本地终端登录，再在远程执行脚本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh user@host
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;~/bin/job.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;另一种方式是在本地直接提交远程命令：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh user@host &lt;span class=&#34;s1&#34;&gt;&amp;#39;~/bin/job.sh&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;在常见的 Bash 账户配置下，前一种方式会进入交互式登录 shell；后一种方式由 &lt;code&gt;sshd&lt;/code&gt; 使用账户配置的 shell，通过 &lt;code&gt;-c&lt;/code&gt; 执行命令字符串。这里至少涉及本地 shell、远程命令 shell，以及脚本可能另行启动的解释器三个环节。&lt;a class=&#34;link&#34; href=&#34;https://man.openbsd.org/sshd#LOGIN_PROCESS&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;OpenSSH 的 sshd 手册&lt;/a&gt;说明了远程 shell 的选择和执行过程。&lt;/p&gt;
&lt;p&gt;因此，登录时添加到 &lt;code&gt;PATH&lt;/code&gt; 的目录，不一定会出现在一次新的远程命令会话里。另一个已经登录的终端中执行过 &lt;code&gt;export&lt;/code&gt;，也不会修改以后由 &lt;code&gt;sshd&lt;/code&gt; 创建的会话环境。&lt;/p&gt;
&lt;p&gt;上面的单引号还有一个作用：让 &lt;code&gt;~&lt;/code&gt; 留到远程解析。类似地，查看远程路径应写成：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh user@host &lt;span class=&#34;s1&#34;&gt;&amp;#39;printf &amp;#34;%s\n&amp;#34; &amp;#34;$PATH&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;如果外层改成双引号，&lt;code&gt;$PATH&lt;/code&gt; 会先在本地展开，看到的就可能是本机路径。下面的远程命令示例假定账户 shell 能解析这些 Bourne 风格语法。&lt;/p&gt;
&lt;h2 id=&#34;登录与交互是两个独立的维度&#34;&gt;登录与交互是两个独立的维度&lt;/h2&gt;
&lt;p&gt;对 Bash 来说，登录模式由启动方式决定，例如 &lt;code&gt;argv[0]&lt;/code&gt; 以 &lt;code&gt;-&lt;/code&gt; 开头，或显式传入 &lt;code&gt;--login&lt;/code&gt; / &lt;code&gt;-l&lt;/code&gt;；并不是“输入过密码”就算登录 shell。交互模式可以通过 &lt;code&gt;-i&lt;/code&gt; 显式开启；通常自动判定还要求没有 &lt;code&gt;-c&lt;/code&gt; 或脚本文件参数，并且标准输入和标准错误连接终端。&lt;a class=&#34;link&#34; href=&#34;https://www.gnu.org/software/bash/manual/html_node/Invoking-Bash.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Bash 启动参数&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;常用的启动规则可以整理成下面这样。表中假定以 &lt;code&gt;bash&lt;/code&gt; 名称启动，没有使用 &lt;code&gt;--noprofile&lt;/code&gt;、&lt;code&gt;--norc&lt;/code&gt; 等抑制选项，也不讨论特权执行模式。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;调用场景&lt;/th&gt;
&lt;th&gt;登录&lt;/th&gt;
&lt;th&gt;交互&lt;/th&gt;
&lt;th&gt;主要启动文件&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;在终端中运行 &lt;code&gt;bash -l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/etc/profile&lt;/code&gt;，再读用户登录文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bash -lc &#39;command&#39;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;登录文件；若设置了 &lt;code&gt;BASH_ENV&lt;/code&gt;，还会处理它&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;在终端中直接运行 &lt;code&gt;bash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.bashrc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;普通的 &lt;code&gt;bash script.sh&lt;/code&gt; 或 &lt;code&gt;bash -c &#39;command&#39;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BASH_ENV&lt;/code&gt; 指定的文件（若设置）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;“用户登录文件”是按 &lt;code&gt;~/.bash_profile&lt;/code&gt;、&lt;code&gt;~/.bash_login&lt;/code&gt;、&lt;code&gt;~/.profile&lt;/code&gt; 的顺序，读取第一个存在且可读的文件，不是依次读取全部文件。Bash 本身也不会因为是登录 shell 就自动读取 &lt;code&gt;~/.bashrc&lt;/code&gt;；常见做法是在登录文件中显式加载它。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/bash.bashrc&lt;/code&gt;、&lt;code&gt;/etc/bashrc&lt;/code&gt; 等系统级 rc 文件则与发行版、编译配置或其他启动文件的显式调用有关，不能把某台机器上的顺序当作所有 Bash 的固定规则。&lt;code&gt;.bash_login&lt;/code&gt; 也是有效的 Bash 登录文件，没有必须禁用它的规则。&lt;a class=&#34;link&#34; href=&#34;https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Bash 启动文件&lt;/a&gt;、&lt;a class=&#34;link&#34; href=&#34;https://sources.debian.org/patches/bash/5.2.37-2/deb-bash-config.diff/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Debian 的 Bash 编译配置补丁&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;ssh-的两个容易误判的地方&#34;&gt;SSH 的两个容易误判的地方&lt;/h2&gt;
&lt;h3 id=&#34;ssh--t-不会自动让-bash-进入交互模式&#34;&gt;&lt;code&gt;ssh -t&lt;/code&gt; 不会自动让 Bash 进入交互模式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;-t&lt;/code&gt; 的作用是申请伪终端（PTY）。如果仍然给 SSH 提供了命令，远程 Bash 通常仍然通过 &lt;code&gt;-c&lt;/code&gt; 执行它，没有因此得到 &lt;code&gt;-i&lt;/code&gt;。所以，“分配了终端”和“这是交互式 Bash”要分开检查。&lt;a class=&#34;link&#34; href=&#34;https://man.openbsd.org/ssh#t&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;OpenSSH 的 &lt;code&gt;-t&lt;/code&gt; 参数&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;确认远程账户使用 Bash 后，可以比较：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh -T user@host &lt;span class=&#34;s1&#34;&gt;&amp;#39;printf &amp;#34;flags=%s\n&amp;#34; &amp;#34;$-&amp;#34;; if [ -t 0 ]; then echo tty=yes; else echo tty=no; fi&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh -tt user@host &lt;span class=&#34;s1&#34;&gt;&amp;#39;printf &amp;#34;flags=%s\n&amp;#34; &amp;#34;$-&amp;#34;; if [ -t 0 ]; then echo tty=yes; else echo tty=no; fi&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;第二条用 &lt;code&gt;-tt&lt;/code&gt;，是为了在本地没有终端时也强制请求 PTY；仍取决于服务器是否允许。在通常配置下，&lt;code&gt;tty&lt;/code&gt; 会从 &lt;code&gt;no&lt;/code&gt; 变成 &lt;code&gt;yes&lt;/code&gt;，但两次 &lt;code&gt;$-&lt;/code&gt; 都没有 &lt;code&gt;i&lt;/code&gt;。判断当前 Bash 是否交互，检查 &lt;code&gt;$-&lt;/code&gt; 中有没有 &lt;code&gt;i&lt;/code&gt;；判断是否登录，使用 &lt;code&gt;shopt -q login_shell&lt;/code&gt;。&lt;a class=&#34;link&#34; href=&#34;https://www.gnu.org/software/bash/manual/html_node/Is-this-Shell-Interactive_003f.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Bash 交互模式检测&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;非交互的-ssh-bash-也可能读取-bashrc&#34;&gt;非交互的 SSH Bash 也可能读取 &lt;code&gt;.bashrc&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Bash 对远程 shell 守护进程有特殊处理：当它识别出自己由 &lt;code&gt;sshd&lt;/code&gt; 等启动、处于相应的非交互场景时，会尝试读取 &lt;code&gt;~/.bashrc&lt;/code&gt;。以 &lt;code&gt;sh&lt;/code&gt; 名称启动时不适用这条规则。因此，不能把 &lt;code&gt;ssh host command&lt;/code&gt; 简单归为“只读 &lt;code&gt;BASH_ENV&lt;/code&gt;”的普通脚本场景；具体行为还受调用方式和构建配置影响。&lt;a class=&#34;link&#34; href=&#34;https://tiswww.case.edu/php/chet/bash/bashref.html#Bash-Startup-Files&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Bash 维护者提供的手册：启动文件&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;即使读到了 &lt;code&gt;.bashrc&lt;/code&gt;，文件里的配置也不一定全部执行。比如这样的提前返回：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Bash&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$-&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    *i*&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    *&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 写在这里的 PATH 设置，非交互 shell 执行不到&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.local/bin:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PATH&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;遇到这种情况，需要检查配置的位置和执行条件。把终端配色、提示符、补全等整套交互配置搬进脚本，并不能稳定地解决环境问题。&lt;/p&gt;
&lt;h2 id=&#34;先确认是哪一层找不到命令&#34;&gt;先确认是哪一层找不到命令&lt;/h2&gt;
&lt;p&gt;如果远程账户 shell 是 Bash，可以在出错的同类会话中查看：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh user@host &lt;span class=&#34;s1&#34;&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    printf &amp;#34;bash=%s\nflags=%s\n&amp;#34; &amp;#34;${BASH_VERSION-}&amp;#34; &amp;#34;$-&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    if [ -n &amp;#34;${BASH_VERSION-}&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;        if shopt -q login_shell; then echo login=yes; else echo login=no; fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    printf &amp;#34;PATH=%s\n&amp;#34; &amp;#34;$PATH&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;    command -v app
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;把 &lt;code&gt;app&lt;/code&gt; 换成实际的程序名。若错误发生在 &lt;code&gt;job.sh&lt;/code&gt; 内部，还应在脚本报错位置之前查看 &lt;code&gt;PATH&lt;/code&gt; 和 &lt;code&gt;command -v app&lt;/code&gt;，因为脚本可能使用另一个解释器，或者修改过环境。&lt;code&gt;$SHELL&lt;/code&gt; 通常反映账户配置的 shell，不能单独证明当前脚本由谁解释。&lt;/p&gt;
&lt;p&gt;脚本的 shebang 也要一起看：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;和：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Bash&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;不是同一份启动约定。&lt;code&gt;/bin/sh&lt;/code&gt; 不保证指向 Bash；即使指向 Bash，以 &lt;code&gt;sh&lt;/code&gt; 名称运行时，也不会按普通非交互 Bash 的方式读取 &lt;code&gt;BASH_ENV&lt;/code&gt;。显式执行 &lt;code&gt;sh job.sh&lt;/code&gt; 或 &lt;code&gt;bash job.sh&lt;/code&gt; 时，则由命令里指定的解释器读取脚本，不能指望 shebang 改变这一选择。&lt;a class=&#34;link&#34; href=&#34;https://www.gnu.org/software/bash/manual/html_node/Shell-Scripts.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Bash 的 shell 脚本说明&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#!/usr/bin/env bash&lt;/code&gt; 又多了一步：&lt;code&gt;env&lt;/code&gt; 需要从当时的 &lt;code&gt;PATH&lt;/code&gt; 查找 Bash。在讨论路径问题时，这一步也不能忽略。&lt;/p&gt;
&lt;h2 id=&#34;把任务需要的环境写明确&#34;&gt;把任务需要的环境写明确&lt;/h2&gt;
&lt;p&gt;如果只缺少一个程序的搜索路径，最直接的处理是在任务入口设定它，或使用程序的绝对路径。例如，下面的路径只是示例，需要换成远程机器的实际安装目录：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh user@host &lt;span class=&#34;s1&#34;&gt;&amp;#39;PATH=/opt/myapp/bin:/usr/local/bin:/usr/bin:/bin; export PATH; exec &amp;#34;$HOME/bin/job.sh&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;如果任务依赖多个变量，可以放在专用文件中，例如 &lt;code&gt;~/.config/myjob/env.sh&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/opt/myapp/bin:/usr/local/bin:/usr/bin:/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;APP_ENV&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;production
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;然后显式加载，再执行脚本：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh user@host &lt;span class=&#34;s1&#34;&gt;&amp;#39;. &amp;#34;$HOME/.config/myjob/env.sh&amp;#34; &amp;amp;&amp;amp; exec &amp;#34;$HOME/bin/job.sh&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;这样，脚本得到的是为这个任务准备的环境。配置文件应当是自己管理、可信且兼容当前 shell 的代码。&lt;/p&gt;
&lt;p&gt;对于确定由 Bash 执行的任务，也可以让 &lt;code&gt;BASH_ENV&lt;/code&gt; 指向这份专用文件：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh user@host &lt;span class=&#34;s1&#34;&gt;&amp;#39;BASH_ENV=&amp;#34;$HOME/.config/myjob/env.sh&amp;#34; /bin/bash &amp;#34;$HOME/bin/job.sh&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;这条命令是在远程为接下来启动的 Bash 设置变量。仅在本地 &lt;code&gt;export BASH_ENV=...&lt;/code&gt;，不会自动让远程收到它；SSH 环境变量转发需要客户端与服务端配置共同允许，而且传过去的只是路径字符串，不是文件内容。&lt;a class=&#34;link&#34; href=&#34;https://man.openbsd.org/ssh_config#SendEnv&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;SendEnv&lt;/a&gt;、&lt;a class=&#34;link&#34; href=&#34;https://man.openbsd.org/sshd_config#AcceptEnv&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;AcceptEnv&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;还有一种明确的需求是：这个任务就是要使用远程账户的登录配置。这时可以显式启动登录 Bash：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;div class=&#34;code-block__header&#34;&gt;&lt;span class=&#34;code-block__language&#34;&gt;Shell&lt;/span&gt;&lt;/div&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh user@host &lt;span class=&#34;s2&#34;&gt;&amp;#34;exec /bin/bash -lc &amp;#39;exec ~/bin/job.sh&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;这里的 &lt;code&gt;-l&lt;/code&gt; 负责读取登录配置，&lt;code&gt;-c&lt;/code&gt; 负责执行后面的命令。它仍然是非交互 Bash，也不保证执行 &lt;code&gt;.bashrc&lt;/code&gt; 中被交互检查挡住的配置。如果登录文件会输出文字、启动其他程序或覆盖变量，这些行为也会跟着发生。因此，自动化任务使用哪一种入口，要根据它实际需要的环境来选。&lt;/p&gt;
&lt;h2 id=&#34;复核范围&#34;&gt;复核范围&lt;/h2&gt;
&lt;p&gt;2026-11-01 复核了上述文档规则，并在本机 Bash 3.2.57 中做了隔离实验：有 PTY 的 &lt;code&gt;bash -c&lt;/code&gt; 仍不带交互标志 &lt;code&gt;i&lt;/code&gt;；&lt;code&gt;bash --noprofile -lc&lt;/code&gt; 仍会读取指定的 &lt;code&gt;BASH_ENV&lt;/code&gt;；同一 Bash 以 &lt;code&gt;sh&lt;/code&gt; 名称启动时不读取它。这些实验没有改动用户的启动文件。&lt;/p&gt;
&lt;p&gt;这里没有连接实际远程服务器验证 SSH 启动文件。某台服务器是否读取 &lt;code&gt;.bashrc&lt;/code&gt;、发行版追加了哪些系统配置，仍需在那台机器上按实际账户和命令检查。&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
