为什么 bashrc 会检查当前 shell 是否是交互式的?

ter*_*don 79 bash bashrc

在我的拱安装,/etc/bash.bashrc以及/etc/skel/.bashrc包含这些行:

# If not running interactively, don't do anything
[[ $- != *i* ]] && return
Run Code Online (Sandbox Code Playgroud)

在 Debian 上,/etc/bash.bashrc有:

# If not running interactively, don't do anything
[ -z "$PS1" ] && return
Run Code Online (Sandbox Code Playgroud)

并且/etc/skel/.bashrc

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac
Run Code Online (Sandbox Code Playgroud)

man bash然而,根据,非交互式 shell 甚至不读取这些文件:

   When  bash  is  started  non-interactively,  to run a shell script, for
   example, it looks for the variable BASH_ENV in the environment, expands
   its  value if it appears there, and uses the expanded value as the name
   of a file to read and execute.  Bash behaves as if the  following  com?
   mand were executed:
          if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
   but  the value of the PATH variable is not used to search for the file?
   name.
Run Code Online (Sandbox Code Playgroud)

如果我理解正确,*.bashrc文件将仅在BASH_ENV设置为指向它们时才会被读取。这是不可能偶然发生的事情,只有在有人相应地明确设置变量时才会发生。

这似乎打破了.bashrc通过设置自动获取用户脚本的可能性BASH_ENV,这可能会派上用场。鉴于 bash 在非交互式运行时永远不会读取这些文件,除非明确告知这样做,为什么默认*bashrc文件不允许它?

Ant*_*gan 84

这是我几周前要在这里发布的问题。像terdon一样,我知道 a.bashrc仅用于交互式 Bash shell,因此不需要.bashrc检查它是否在交互式 shell 中运行。令人困惑的是,我使用的所有发行版(Ubuntu、RHEL 和 Cygwin)都有某种类型的检查(测试$-$PS1)以确保当前的 shell 是交互式的。我不喜欢Cargo cult 编程,所以我开始在我的.bashrc.

Bash 有一个远程 shell 的特殊情况

在研究了这个问题后,我发现远程 shell的处理方式不同。虽然非交互式 Bash shell 通常不会~/.bashrc在启动时运行命令,但当 shell 被远程 shell daemon 调用时会产生一种特殊情况:

Bash 尝试确定它何时在其标准输入连接到网络连接的情况下运行,如由远程 shell 守护程序(通常rshd)或安全 shell 守护程序执行时sshd。如果 Bash 确定它正在以这种方式运行,它会从 ~/.bashrc 读取并执行命令,如果该文件存在且可读。如果作为sh. 该--norc选项可用于禁止此行为,并且该--rcfile选项可用于强制读取另一个文件,但既rshd不会也不sshd通常使用这些选项调用 shell 或允许指定它们。

例子

在 remote 的开头插入以下内容.bashrc。(如果.bashrc来自.profile.bash_profile,请在测试时暂时禁用此功能):

echo bashrc
fun()
{
    echo functions work
}
Run Code Online (Sandbox Code Playgroud)

在本地运行以下命令:

$ ssh remote_host 'echo $- $0'
bashrc
hBc bash
Run Code Online (Sandbox Code Playgroud)
  • No iin$-表示外壳是非交互式的
  • 没有领导-$0表示该外壳不是登录shell

.bashrc也可以运行在远程定义的 Shell 函数:

$ ssh remote_host fun
bashrc
functions work
Run Code Online (Sandbox Code Playgroud)

我注意到,~/.bashrc只有当命令被指定为参数来源ssh。这是有道理的:whenssh用于启动常规登录 shell,.profile.bash_profile运行(并且.bashrc仅在由这些文件之一明确执行时才获取)。

.bashrc在运行(非交互式)远程命令时,我可以看到的主要好处是可以运行 shell 函数。但是,典型命令中的大多数命令.bashrc仅与交互式 shell 相关,例如,除非 shell 是交互式的,否则不会扩展别名。

远程文件传输可能会失败

rshssh用于启动交互式登录 shell 或使用非交互式 shell 运行命令时,这通常不是问题。但是,对于诸如,和使用远程 shell传输数据的程序来说,这可能是一个问题rcpscpsftp

事实证明,在使用该scp命令时,远程用户的默认 shell(如 Bash)是隐式启动的。有没有在手册页没有这方面的记载-仅一提的是scp使用ssh它的数据传输。这导致如果.bashrc包含任何打印到标准输出的命令,文件传输将失败,例如, scp 失败而没有错误

另请参阅 15 年前的相关 Red Hat 错误报告,当 /etc/bashrc 中有 echo 命令(最终关闭为WONTFIXscp 中断

为什么scpsftp失败

SCP(安全复制)SFTP(安全文件传输协议)有自己的协议,用于本地和远程端交换有关正在传输的文件的信息。来自远程端的任何意外文本都被(错误地)解释为协议的一部分,传输失败。根据蜗牛书常见问题解答

经常会发生什么,不过,是有系统无论是陈述或者每个用户的shell启动文件的服务器上(.bashrc.profile/etc/csh.cshrc.login,等)上的登录输出文字信息,旨在由人来阅读(像fortuneecho "Hi there!",等等。)。

tty附加到标准输入时,此类代码应仅在交互式登录时产生输出 。如果它不进行此测试,它将在它们不属于的地方插入这些文本消息:在这种情况下,会污染scp2/sftp和之间的协议流sftp-server

shell 启动文件之所以重要,是因为它sshd 在代表用户启动任何程序时使用用户的 shell (使用例如 /bin/sh -c "command")。这是 Unix 的传统,并且具有以下优点:

  • 用户的常用设置(命令别名、环境变量、umask 等)在运行远程命令时生效。
  • 将帐户的 shell 设置为 /bin/false 以禁用它的常见做法将阻止所有者运行任何命令,如果身份验证仍然出于某种原因意外成功。

SCP协议细节

对于那些对 SCP 如何工作的细节感兴趣的人,我在 SCP 协议的工作原理中找到了有趣的信息,其中包括在远程端使用健谈的 shell 配置文件运行 scp 的详细信息

例如,如果您将其添加到远程系统上的 shell 配置文件中,就会发生这种情况:

回声“”

为什么它只是挂起?这来自于scp模式下如何等待第一个协议消息的确认。如果它不是二进制 0,它期望它是远程问题的通知,并等待更多字符形成错误消息,直到新行到达。由于您没有在第一行之后打印另一行新行,因此您的本地人scp只是停留在一个循环中,在read(2). 同时,在远程端处理 shell 配置文件后,scp在接收器模式下启动,它也阻塞在 上read(2),等待表示数据传输开始的二进制零。

结论/TLDR

典型.bashrc中的大多数语句仅对交互式 shell 有用 - 在使用rsh或运行远程命令时无效ssh。在大多数情况下,不需要设置 shell 变量、别名和定义函数——如果使用或等程序传输文件,将任何文本打印到标准输出都是有害的。在验证当前 shell 是非交互式之后退出是.scpsftp.bashrc


Mik*_*kel 16

手册页忽略了非交互式远程 shell 的bash来源.bashrc,如

ssh hostname command
Run Code Online (Sandbox Code Playgroud)

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1010

 COMMAND        EXECUTE BASHRC
 --------------------------------
 bash -c foo        NO
 bash foo           NO
 foo                NO
 rsh machine ls     YES (for rsh, which calls 'bash -c')
 rsh machine foo    YES (for shell started by rsh) NO (for foo!)
 echo ls | bash     NO
 login              NO
 bash               YES
Run Code Online (Sandbox Code Playgroud)

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1050

          /* If we were run by sshd or we think we were run by rshd, execute
             ~/.bashrc if we are a top-level shell. */
          if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2)
            {
              maybe_execute_file (SYS_BASHRC, 1);
              maybe_execute_file (bashrc_file, 1);
Run Code Online (Sandbox Code Playgroud)

  • 不。我把你链接到了 `bash` 源,而不是 `rsh` 源。:) 该评论也适用于 `ssh`,它是很久以前写的。查看更多来源的更新答案。 (2认同)

cuo*_*glm 5

按照惯例,.bashrc是用户存储外壳自定义配置的地方。

这些自定义配置可以是环境变量、别名、花哨的提示。对于非交互式 shell,那些缺少的东西毫无意义。此外,可以在许多上下文中调用非交互式 shell,您不确定这些环境变量是否会导致误报情况,甚至安全漏洞。

一个最接近的例子是一个别名,如:

alias cp='cp -i'
Run Code Online (Sandbox Code Playgroud)

然后它永远挂起你的非交互式 shell。

所以检查在顶部执行.bashrc以确保我们不会遇到麻烦。


因为 shell 可以被称为非交互式登录shell,所以明确阻止采购*bashrc是没有意义的。

当炮弹被称为非交互式登录shell,它源/etc/profile,那么其中的第一个发现~/.bash_profile~/.bash_login~/.profile

当 bash 作为交互式登录 shell或作为带有 --login 选项的非交互式 shell调用时,它首先从文件 /etc/profile 读取并执行命令(如果该文件存在)。读取该文件后,它会按顺序查找 ~/.bash_profile、~/.bash_login 和 ~/.profile,然后从第一个存在且可读的命令中读取和执行命令。

没有什么可以阻止这些文件.bashrc自行采购,因此在内部进行检查.bashrc更安全并使事情变得简单。