Friday, September 16, 2011

slaves.sh的小细节


Hadoop集群中,批量地对slaves节点进行操作,对于略懂得shell脚本技巧的人其实并不是一个大的问题。简单地说,就是对slaves文件中的每个host进行循环,然后ssh执行命令即可。近来看同事操作,总算发现bin下有一个slaves.sh,已经做了同样的事情,实在是省下不少typing work!

后来因为一个不相干的问题稍微被看一下slaves.sh的代码,下面最关键的一句,却没完全看明白:

ssh $HADOOP_SSH_OPTS $slave $"${@// /\\ }" \
   2>&1 | sed "s/^/$slave: /" &

其中 ${@// /\\ } 难理解。看到这里,你就知道下面文章实际与hadoop没有多大关系,而是跟 shell 脚本有关系了。

先简单说明变量字符串字串替换的语法,譬如有变量v:

v="hello borqs, hello me"
[bing@myvm ~]$ echo ${v/hello/great}
great borqs, hello me
[bing@myvm ~]$ echo ${v//hello/great}
great borqs, great me

当v后面是一个斜杠时,是指替换第一处"hello"为"great";当后面是两个斜杠时,是指替换所有的"hello"为"great"。

其次,是 ${@// /\\ } 的语义。用小脚本 print_args.sh 作为演示:

$ cat print_args.sh
#!/bin/bash

echo "${@// /\\ }"
echo "$@"
echo "$#"

分别执行下面的命令:

$ ./print_args.sh i say "hello hanborqs"     
i say hello\ hanborqs
i say hello hanborqs
3

也就是说,${@// /\\ } 是对$@这个数组中的每一个元素进行了空格转义,而不是对"$@"这一个整个字符串进行空格转义(这是最初困扰我的)。起初,我以为 ${@// /\\ } 会将参数替换为"i\ say\ hello\ hanborqs"。这也是我最开始无法理解 slaves.sh 中这处代码的地方:有与没有是一样的。
其实,$@ 也无非就是一个数组罢了。也就是说,这个字符串替换语法,在应用于一个Array上面时,应当是对先每个元素替换。当然,这样设计才是合理的 :) 。Anyway,让我们再尝试一下:

$ arr=("i" "say" "hello borqs")
$ echo ${arr[@]}
i say hello borqs
$ echo ${#arr[@]}
3
$ echo ${arr[@]// /\\ /}
i say hello\ /borqs

这样,就能确定这个字串替换语法中应用于Array时,的确考虑了计算的优先次序的问题。
回过来看 slaves.sh。slaves.sh这样实现,无非是为了解决传递带空格的参数或命令的问题。譬如,想在slaves.sh 上都创建一个名为“hello world.txt”,则可以这样运用:
slaves.sh touch "hello world.txt"
如果想创建"hello"文件和"world.txt",则应当:
slaves.sh touch hello world.txt
同样地,如果你要执行的命令(第三方的命令吧?)或脚本的名字中有空格,用引号括起来就行了,slaves.sh是可以应付的。everything works just like in a bash shell !

Monday, August 16, 2010

万恶的指针退化

我估计,还是有人像我一样,懒得不想知道一个指针与数组的区别, 反正用起来都是一样的。比如:
char buff[SIZE];
或者
char *pBuff = new char[SIZE];
除了一个是静态内存,一个是动态,恐怕也再也没有多大分别了。毕竟,用法是大致相仿。如:
sprintf(buff, "hello buff!\n"); // or pBuff
printf("%s\n", buff); // or pBuff, it's OK

至此,一切都还不错。有一天,我们为了安全起见,希望将sprintf改成snprintf,于是:
snprintf(buff, sizeof(buff), "hello buff!\n");
printf("%s\n", buff);
好,完成。嗒嗒嗒,编译运行,通过。一切都还风平浪静。于是,我们继续航行,今天,又写了3000行代码。次日,你发现buff可能行不通,要换成pBuff。所以,你轻易地:
snprintf(pBuff, sizeof(pBuff), "hello buff!\n");
printf("%s\n", buff);
可是,当再次运行时,你发现你的“船”出了问题了。结果,只打印了"hel",不知道是"help",还是"hell"。要知道,sizeof(buff)的结果为SIZE,但sizeof(pBuff)的结果恒为4,即指针的大小。幸运的是,所有的一切祸根来自于你把“buff”改成了“pBuff”,所以你意味到了数组与指针有多么不同。但是,并不是每次都是这么幸运的。我的朋友多次问起我指针与数组的区别。我总是随意地进行了答复,用起来都一样,别理它。终于有一天,我发现了自己犯下了一个低级无耻的错误——别忘了,数组是有大小信息的。
于是,我觉得,我应该将pBuff改回buff比较好:
snprintf(buff, sizeof(buff), "hello buff!\n");
大后天,代码越来越多了,适当的重构变成了一个不错的主意。于是,我将打印放到了一个函数中(当然,真的是算是一个好主意):
void printbuff(char buff[]) {
snprintf(buff, sizeof(buff), "hello buff!\n");
printf("%s\n", buff);
}

int main() {
char buff[SIZE];
printbuff(buff);
...
}

老问题又出现了。buff明明还是数组,为何会错呢?莫非是printbuff的形参中,没有指定大小信息。实际上,数组名在传入函数的时候,数组就退化成了指针,这个退化集中表现为数组的大小信息丢失了(还有其它表现么?)。这就是为什么,C语言的老师在教我们写函数时,除了传递数组名,还要传一个数组大小。
void printbuff(char *buff, int buffSize) {
...
关于数组退化,我记得在 Effective C++ 还是哪本小册子上看过。推荐如我这般的菜鸟阅读!