20130718

除了管道和重定向,还有命令行参数

除了管道和重定向,还有命令行参数

管道和重定向,在Unix Shell
Script入门教程里都要提到。在稍微深入一点的dos/windows批处理教程里也要提到。管道,一般在用前一个进程产生的输出,作为后一个进程的输入;重定向,一般用在把输出由控制台转到文件,或者用文件替代控制台输入。管道和重定向满足了进程的输入和输出的转向。

输入和输出,似乎本来就是进程跑起来以后唯一与外界的联系。根据与外界的联系判定那是个什么东西,而不是根据它的内心,这据说是萨特的观点,小资们不可不知。

不过,输入和输出,并非进程与外界唯一的联系。除了输入以外,命令行参数,也是设定进程行为的重要依据。在这一点上,如果把进程当成一个对象,命令行参数有点像构造函数的参数,虽然跑起来以后就跟它无关了。而且,unix程序一般地,只有命令行参数才是命令,影响进程的行为,而以后从标准输入读进来的所有东西,都只是数据而已,是被进程处理的东西,对进程本身没有影响。

shell (像python一样),是一种不错的粘合剂,它能组装一系的进程,让它们协同工作。管道和重定向,能让它们完成生产者-消费者这样的组合。

不过,如果我们需要一个进程的输出,不是作为另一个进程的输入,而是作为它的命令行参数呢?

下面的写法,
a | b
完成的,是把a的输出作为b的输入。

而如果我们希望下面这样,如何表达?
b -para <此处是a的输出>

有不止一种方法。

1. backtick

backtick,就是"`"。这不是单引号,而是键盘左上角,波浪线 (~)下面的那个小点,像个反向的单引号。

它的作用是,把括起来的命令执行了,并用执行结果中的每一行去替换命令所在的位置。

ls `echo \-l`

相当于

ls -l

我们把其中的一段拿出来单独执行一下。

1 $ echo \-l
2 -l

可见,"echo \-l"的结果,就是"-l"。其中的"\"中为了避免echo把"-l"当成自己
的参数。

而"-l"替换了"ls `echo \-l`"中反引号括起来的部分,所以"ls `echo \-l`"就
变成了"ls -l"。

再举个例子。

1 $ grep -w main `find . *c`
2 ./samples/hidraw/hid-example.c:int main(int argc, char **argv)
3 ./samples/trace_events/Makefile:# have that tracer file in its main
search path. This is because
4 ./Makefile:# $(vmlinux-main). Most are built-in.o files from
top-level directories
5 ./Makefile:# +--< $(vmlinux-main)
6 ./Makefile:vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
7 ./Makefile:vmlinux-all := $(vmlinux-init) $(vmlinux-main)
8 ...

这条命令的作用是,先执行 "find . *c",找到当前目录下所有子目录中的 c文件,然后对每个符合条件的文件执行 "grep -w main"。

命令"grep -w main `find . *c`"中,`find . *c`部分将被替换为很多个匹配的文件名,然后每个文件名都作为
"grep -w main"的命令行参数,类似于:

grep -w main a.c
grep -w main b.c
grep -w main c.c

这样。

2. $(cmd)

backtick倒是挺方便,不过很多同学反映,"`"这个符号太丑陋了。最丑陋的特性之一就是,它跟单引号"'"长得太过地相似了。像我这样认人脸有困难的,本来就希望大家服饰发型更多样化一些,所以你能够理解我现在看到女演员们长得都越来越像,该是多么大的困惑。我指的不光是韩国的,小锥子脸大眼睛面孔白而无表情。backtick也存在这样的问题,跟别人像,本来就是毛病。

所以有别的写法,比如这样:

grep -w main $(find . *c)

这样的效果跟backtick是一样的。一样一样的。区别呢?多少也有一些。比如 $(cmd) 这样写法是可以嵌套的。

3. xargs

不过上述这些需要"代换"的,似乎有些程序员理解起来有困难?后来出现了一个命令,xargs,专门用来做这件事。

find . *c | xargs grep -w main {}

请注意到find和grep的顺序换了。find的输出结果,由xargs转给grep作为命令行参数。事实上,是xargs调用了grep,管道的末端不是grep,而是xargs。这不同于管道的末端是grep,如果是这样,就成了find的输出作为grep的输入,是管道应用的更一般的情况。args把管道的输出转向了grep的命令行参数,而不是作为grep的输入。

"{}"在这里,将被find的输出替代。如果命令行参数的位置不重要,或者在最末端,那么还可以把"{}"省略掉,效果是一样的。像这样:

find . *c | xargs grep -w main

4. -exec

似乎有了xargs以后,还是有些程序员觉得不够简单?又有些命令自带了"-exec"参数,意谓:如果匹配了,就把那些输出作为"-exec后面的进程的命令行参数"。话说,有些事情由于它的领域模型就那么复杂,是不太可能因为表达方式而变得简单的。

以下命令,效果是一样的:
find . *c -exec grep -w main {} -nH \;

行尾的"\;",是为了标识"-exec"部分结束,其中的"\"是因为";"是个特殊字符。grep需要"-nH"参数,是因为不然输出的匹配行里没有文件名和行号。grep在此前的几个命令里用的是别名,这里是真正的grep本身。

5. 总结

以下几个命令等价:
$ cat `which 20.sh`
$ cat ${which 20.sh}
$ which 20.sh | xargs cat

cat不支持"-exec"参数,那是find这样的复杂命令的特色,因此以上总结中未包括。

----------

本文的命令在 emacs eshell 不好使,因为 eshell 对管道支持不充分,shell-mode则没有问题。

--------------------

博客会手工同步到以下地址:

----
杨贵福
东北师范大学 计算机科学与信息技术学院
--
Sincerely,
YANG Guifu
School of Computer Science and Information Technology
Northeast Normal University
Changchun, P.R.China

重剑无锋,大巧不工。

无不大工。

No comments: