远程执行命令的填坑记录

前言

本文主要记录bash四种模式的细节,以便于遇到问题时查阅。

远程执行出错了

最近使用ansible比较多,在某次使用shell模块远程执行命令的时候老是报‘command not found’。但是手动登录到远端机器执行命令是成功的,于是开始思考里面的细节。特别感谢这篇博文👏

bash的四种模式

遇到问题的时候就觉得应该是环境变量的关系。因为使用的是bash,那下面就来记录一下bash的细节。

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

non-interactive + login shell

第二种模式的shell为non-interactive login shell,即非交互式的登录shell,这种是不太常见的情况。一种创建此shell的方法为:bash -l script.sh,-l参数是将shell作为一个login shell启动,而执行脚本又使它为non-interactive shell。

加载配置文件
对于这种类型的shell,配置文件的加载与第一种完全一样。

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、颜色、提示符等等。
所以引入多种配置文件完全是为了更好的管理配置,每个文件各司其职,只做好自己的事情。

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-login: 读取ENV环境变量对应的文件
  • non-interactive + non-login: 不读取任何文件

所以如果是sh的话,不会加载任何环境变量,结果还是command not found
最后的解决办法就是设置$BASH_ENV/etc/profile,然后将#!/usr/bin/env sh改成#!/usr/bin/env bash即可。

参考

http://feihu.me/blog/2014/env-problem-when-ssh-executing-command-on-remote/

您的支持将鼓励我继续创作!
0%