前言
本文主要记录 bash 四种模式的细节,以便于遇到问题时查阅。
远程执行出错了
最近使用 ansible 比较多,在某次使用 shell 模块远程执行命令的时候老是报 ‘command not found’。
但是手动登录到远端机器执行命令是成功的,于是开始思考里面的细节。
特别感谢这篇博文
bash 的四种模式
遇到问题的时候就觉得应该是环境变量的关系。
因为使用的是 bash,那下面就来记录一下 bash 的细节。
1. interactive + login shell
第一种是交互式的登录 shell。
这里需要理解两个概念:interactive , login。
login shell 指用户以非图形界面或者以 ssh 登录到机器上时获得的第一个 shell 。
- 简单些说就是需要输入用户名和密码的 shell
- 通常不管以何种方式登录机器后用户获得的第一个 shell 就是 login shell。
interactive shell 会有一个输入提示符,并且它的标准输入、输出和错误输出都会显示在控制台上。
- 一般来说只要是需要用户交互的,即一个命令一个命令输入的 shell 都是 interactive shell。
- 如果无需用户交互,它便是 non-interactive shell。
- 通常来说如
bash script.sh
此类执行脚本的命令就会启动一个 non-interactive shell,它不需要与用户进行交互,执行完后它便会退出创建的 shell。
那么这个模式下最简单的两个例子就是:
- 用户直接登录到机器获得的第一个 shell
- 用户使用
ssh user@remote
获得的 shell
这种模式下,shell 首先加载/etc/profile
,然后再尝试依次去加载下列三个配置文件之一,一旦找到其中一个便不再接着寻找:
- ~/.bash_profile
- ~/.bash_login
- ~/.profile
2. non-interactive + login shell
第二种模式的 shell 为 non-interactive login shell,即非交互式的登录 shell,这种是不太常见的情况。
一种创建此 shell 的方法为:bash -l script.sh
,-l 参数是将 shell 作为一个 login shell 启动,而执行脚本又使它为 non-interactive shell。
对于这种类型的 shell,配置文件的加载与第一种完全一样。
3. interactive + non-login shell
第三种模式为交互式的非登录 shell。
这种模式最常见的情况是在一个已有 shell 中运行 bash,此时会打开一个交互式的 shell,而因为不再需要登录,因此不是 login shell。
对于此种情况,启动 shell 时会去查找并加载 /etc/bash.bashrc
和 ~/.bashrc
文件。
bashrc vs profile
- profile 类型文件,它是某个用户唯一的用来设置全局环境变量的地方 。
因为用户可以有多个 shell 比如 bash, sh, zsh 等, 但像环境变量这种其实只需要在统一的一个地方初始化就可以了, 而这个地方就是 profile。
所以启动一个 login shell 会加载此文件,后面由此 shell 中启动的新 shell 进程如 bash,sh,zsh 等都可以由 login shell 中 继承环境变量等配置。 - bashrc,其后缀 rc 的意思为 Run Commands,由名字可以推断出,此处存放 bash 需要运行的命令 。
但注意,这些命令一般只用于交互式的 shell,通常在这里会设置交互所需要的所有信息,比如 bash 的补全、alias、颜色、提示符等等。
所以引入多种配置文件完全是为了更好的管理配置,每个文件各司其职,只做好自己的事情。
4. non-interactive + non-login shell
最后一种模式为非交互非登录的 shell,创建这种 shell 典型有两种方式:
- bash script.sh
- ssh user@remote command
这两种都是创建一个 shell,执行完脚本之后便退出,不再需要与用户交互。
对于这种模式而言,它会去寻找环境变量BASH_ENV
,将变量的值作为文件名进行查找,如果找到便加载它。
典型模式总结
下面举一些例子:
- 登录机器后的第一个 shell:login + interactive
- 新启动一个 shell 进程,如运行 bash:non-login + interactive
- 执行脚本,如
bash script.sh
:non-login + non-interactive - 运行头部有如
#!/usr/bin/env bash
的可执行文件,如./executable
:non-login + non-interactive - 通过 ssh 登录到远程主机:login + interactive
- 远程执行脚本,如
ssh user@remote script.sh
:non-login + non-interactive - 远程执行脚本,同时请求控制台,如
ssh user@remote -t 'echo $PWD'
:non-login + interactive - 在图形化界面中打开 terminal:Linux 上 : non-login + interactive;Mac OS X 上 : login + interactive
有出路了
通过上面的总结,ansible 的 shell 模块远程执行命令应该就是属于 non-login + non-interactive。
对于这种模式,bash 会选择加载$BASH_ENV
的值对应的文件。
但是,注意到命令里面的那个脚本的第一行#!/usr/bin/env sh
,并不是 bash,而是 sh。
那么 bash 和 sh 有啥区别呢?
通过执行whereis
命令查看发现,sh 只是 bash 的一个软链接。
再通过查看文档知道,当 bash 以 sh 命令启动时,bash 会尽可能的模仿 sh。
所以配置文件的加载变成了下面这样:
- interactive + login : 读取 /etc/profile 和 ~/.profile
- non-interactive + login : 同上
- interactive + non-l gin : 读取 ENV 环境变量对应的文件
- non-interactive + non-login : 不读取任何文件
所以如果是 sh 的话,不会加载任何环境变量,结果还是 command not found。
最后的解决办法就是:
- 第一步 设置
$BASH_ENV
为/etc/profile
- 第二步 将
#!/usr/bin/env sh
改成#!/usr/bin/env bash