今日在维护集群环境的时候,遇到了一个小问题,rsync 向集群中的机器传输文件的时候报错:html
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 环境有关。linux
又翻了下 man rsync,发现官方对这个问题有以下的解释:shell
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?".bash
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
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 提示语句。this
注释掉,而后再次测试,便可正常运行了。google
那么,这儿问题就来了(问题固然不是挖掘机哪家强 - _ - 。。。),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的区别