今日在维护集群环境的时候,遇到了一个小问题,rsync 向集群中的机器传输文件的时候报错:
protocol version mismatch -- is your shell clean?
(see the rsync man page for an explanation)
rsync error: protocol incompatibility (code 2) at compat.c(171) [sender=3.0.6]
即使打开调试选项 -vv,也没能得到更多的有用信息,不过看提示,应该是跟 shell 环境有关。
又翻了下 man rsync,发现官方对这个问题有如下的解释:
DIAGNOSTICS
rsync occasionally produces error messages that may seem a little cryptic. The one that seems to cause the most confusion is "protocol version mismatch -- is your shell clean?".
This message is usually caused by your startup scripts or remote shell facility producing unwanted garbage on the stream that rsync is using for its transport. The way to diagnose this problem is to run your remote shell like this:
ssh remotehost /bin/true > out.dat
then look at out.dat. If everything is working correctly then out.dat should be a zero length file. If you are getting the above error from rsync then you will probably find that out.dat contains some text or data. Look at the contents and try to work out what is producing it. The most common cause is incorrectly configured shell startup scripts (such as .cshrc or .profile) that contain output statements for non-interactive logins.
同时 google 这个问题你会发现答案也都是来源于官方的帮助文档,那么问题初步确定是 shell 环境的问题。
按照提示,ssh remotehost /bin/true > out.dat 执行过后输出的正是远程机器上 .bashrc 设置里的一条 echo 提示语句。
注释掉,然后再次测试,即可正常运行了。
那么,这儿问题就来了(问题当然不是挖掘机哪家强 - _ - 。。。),rsync 和 .bashrc 有半毛钱关系呢?
哈哈,欲听后事如何,且听我慢慢道来~
原因是 rsync 在传输数据之前,会先与远端进行一次 ssh 登录认证,而当 .bashrc文件有输出的时候,rsync 客户端解析返回的数据包会出现混乱,于是乎就会出现文中开头提到的报错:客户端和远端的协议版本不兼容/不匹配了。
需要说明的是:
远端 sshd 进程是通过“bash –c”的方式来执行命令(即"非交互式的非登录shell")
但在执行命令之前,ssh的那一次登录本身是“非交互式的登录shell”,非交互式的登录shell (bash –l xxx.sh)载入的信息列表及顺序如下:
/etc/profile
[~/.bash_profile || ~/.bash_login || ~/.profile]
$BASH_ENV
对于Bash来说,登录shell(包括交互式登录shell和使用“–login”选项的非交互shell),它会首先读取和执行/etc/profile全局配置文件中的命令,然后依次查找~/.bash_profile、~/.bash_login 和 ~/.profile这三个配置文件,读取和执行这三个中的第一个存在且可读的文件中命令。除非被“–noprofile”选项禁止了。
在非登录shell里,只读取 ~/.bashrc (和 /etc/bash.bashrc、/etc/bashrc )文件,不同的发行版里面可能有所不同,如RHEL6.3中非登录shell仅执行了“~/.bashrc”文件(没有执行/etc/bashrc),而KUbuntu10.04中却依次执行了/etc/bash.bashrc 和 ~/.bashrc 文件。
对于这些规则,可以直接在相应的配置文件中加一些echo命令来验证其真实性。
所以 ssh 的时候会载入“~/.bash_profile”,
让我们再来看一下 .bash_profile 的内容:
cat ~/.bash_profile
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc # .bashrc 默认又会加载 /etc/bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH
看到这儿,我想你大概明白了为什么 .bashrc 里有输出流会导致 rsync 传输失败了。
既然都找到原因了,那怎么解决呢?
有同学会问,我本来就是要让用户登录的时候有登录提示的呀?现在为了 rsync 岂不是得禁掉?
那这里就又引入了另一个知识点:交互式shell和飞交互式shell、登录shell和非登录shell 的区别
什么是交互式的呢?就像我们平时通过命令行做事一样,你敲一个命令,终端解析执行完之后给你一个结果,这样一种交互式的形式。那么,平时我们接触的Shell基本上都是交互式的,如gnome-terminal打开一个Shell以及通过Ctrl+alt+1等切换过去的文本终端。交互式Shell下, "echo $-"返回的字符串中包含i,否则不包含。也可以通过在bash后面加-i参数打开一个交互式的Shell,具体可以看man bash。bash后面加-c参数执行命令打开的是非交互式Shell,可以用以下命令验证:
bash -c 'echo $-' # 返回hBc
解释完交互式之后,继续解析本小节后半部分中的登录二字。登录Shell其实很好理解,就是我们平时通过用户名/密码才能登录的Shell,最典型的就是用Ctrl+alt+1切换过去的文本终端。如何区分登录Shell和非登录Shell呢,可以通过查看$0的值,登录Shell返回-bash,而非登录Shell返回的是bash。平时gnome-terminal打开的Shell就是非登录Shell。也可以通过在bash后面加--login参数打开一个登录Shell。
回到本小节开头的问题,咱们可以用如下方式去区分一个命令是处于登录shell还是非登录shell:
[[ $- == *i* ]] && echo 'This is interactive shell.'
其中,$-中包含i意思是指当前的Shell是一个交互式(interactive)的Shell。
针对文中开头的问题,咱们加上如上的判断,就可以做到登录的时候有提示,同时 rsync 也不会报错了,可谓一举两得。
好了,今天的内容就到这儿了,其实 shell 作为一门古老的编程语言以及随着 linux 版本的多样化发展、不断的演变,”坑“很多,却也值得让人细细探索~
[1] 什么是交互式登录 Shell [[ $- != *i* ]] && return
http://kodango.com/what-is-interactive-and-login-shell
[2] linux下的bash与sh 详解以及例子
http://bbs.chinaunix.net/thread-1068678-1-1.html
[3] 登录shell,交互式非登录shell,非交互式shell
http://bbs.chinaunix.net/thread-2018339-1-1.html
[4] Shell 默认选项 himBH 的解释
http://kodango.com/explain-shell-default-options
[5] 交互式SHELL和非交互式SHELL、登录SHELL和非登录SHELL的区别
http://smilejay.com/2012/10/interactive-shell-login-shell/