一直以来对shell中字符串的了解就是单引号和双引号内的都是字符串,双引号内字符串可以被替换成变量,单引号就是原字符串输出,今天在开发一个hdfs跨集群拷贝数据脚本时候发现shell echo出来同样的命令手动执行可以执行,在shell内通过${cmd}方式却报错,通过sh -x a.sh才发现问题产生的根源,具体如下:
众所周知hdfs在集群内部拷贝文件命令如下:
hdfs dfs -cp src_path target_path
我这里是跨集群拷贝,集群的配置文件中集群部门的同事由于种种原因不愿意配置别的集群的nameservice,我要实现最简单的方式就是通过-D将这些参数传进去,完整命令如下:
hdfs dfs -Ddfs.nameservices='ns1,ns2' -Ddfs.ha.namenodes.ns2='nn1,nn2' -Ddfs.namenode.rpc-address.ns2.nn1='master01:8888' -Ddfs.namenode.rpc-address.ns2.nn2='master02:8888' -Ddfs.client.failover.proxy.provider.ns2='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider' -cp src_path hdfs://ns2/target_path
上述命令中ns1是我们部门集群,ns2是别的部门的集群,需要将我们部门的数据拷贝到别的集群,上述命令手动执行是不会有任何问题的,在shell中使用时候我是这么使用的,部分片段如下:
hdfs_conf="-Ddfs.nameservices='ns1,ns2' -Ddfs.ha.namenodes.ns2='nn1,nn2' -Ddfs.namenode.rpc-address.ns2.nn1='master01:8888' -Ddfs.namenode.rpc-address.ns2.nn2='master02:8888' -Ddfs.client.failover.proxy.provider.ns2='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'"
cp_cmd="hdfs dfs ${hdfs_conf} -cp src_path hdfs://ns2/target_path"
# 输出一下命令
echo ${cp_cmd}
# 执行命令
${cp_cmd}
输出信息如下:
hdfs dfs -Ddfs.nameservices='ns1,ns2' -Ddfs.ha.namenodes.ns2='nn1,nn2' -Ddfs.namenode.rpc-address.ns2.nn1='ns1:8888' -Ddfs.namenode.rpc-address.ns2.nn2='ns2:8888' -Ddfs.client.failover.proxy.provider.ns2='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider' -cp src_path hdfs://ns2/target_path
cp: Couldn't create proxy provider null
看echo出来的命令是没有任何问题的,但是却提示provider为null,导致执行失败,添加-x参数以后输出如下:
+ hdfs_conf='-Ddfs.nameservices='\''ns1,ns2'\'' -Ddfs.ha.namenodes.ns2='\''nn1,nn2'\'' -Ddfs.namenode.rpc-address.ns2.nn1='\''ns1:8888'\'' -Ddfs.namenode.rpc-address.ns2.nn2='\''ns2:8888'\'' -Ddfs.client.failover.proxy.provider.ns2='\''org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'\'''
+ cp_cmd='hdfs dfs -Ddfs.nameservices='\''ns1,ns2'\'' -Ddfs.ha.namenodes.ns2='\''nn1,nn2'\'' -Ddfs.namenode.rpc-address.ns2.nn1='\''ns1:8888'\'' -Ddfs.namenode.rpc-address.ns2.nn2='\''ns2:8888'\'' -Ddfs.client.failover.proxy.provider.ns2='\''org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'\'' -cp src_path hdfs://ns2/target_path'
+ echo hdfs dfs '-Ddfs.nameservices='\''ns1,ns2'\''' '-Ddfs.ha.namenodes.ns2='\''nn1,nn2'\''' '-Ddfs.namenode.rpc-address.ns2.nn1='\''ns1:8888'\''' '-Ddfs.namenode.rpc-address.ns2.nn2='\''ns2:8888'\''' '-Ddfs.client.failover.proxy.provider.ns2='\''org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'\''' -cp src_path hdfs://ns2/target_path
hdfs dfs -Ddfs.nameservices='ns1,ns2' -Ddfs.ha.namenodes.ns2='nn1,nn2' -Ddfs.namenode.rpc-address.ns2.nn1='ns1:8888' -Ddfs.namenode.rpc-address.ns2.nn2='ns2:8888' -Ddfs.client.failover.proxy.provider.ns2='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider' -cp src_path hdfs://ns2/target_path
+ hdfs dfs '-Ddfs.nameservices='\''ns1,ns2'\''' '-Ddfs.ha.namenodes.ns2='\''nn1,nn2'\''' '-Ddfs.namenode.rpc-address.ns2.nn1='\''ns1:8888'\''' '-Ddfs.namenode.rpc-address.ns2.nn2='\''ns2:8888'\''' '-Ddfs.client.failover.proxy.provider.ns2='\''org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'\''' -cp src_path hdfs://ns2/target_path
cp: Couldn't create proxy provider null
相比到这里各位应该看明白了,虽然echo输出来的命令是正确的,但是在执行过程中,shell会给程序添加单引号,结果单引号就很多了,其中有转义,导致最终真实执行的命令很乱。
此时如果要执行这个命令,可以把最后的${cp_cmd}修改为eval ${cp_cmd},eval会把中间的单引号和双引号全部去掉,此时执行就是我想要的结果,感兴趣的朋友可以试着自己debug试试。