摘自痛恨手册

摘自痛恨手册

如果想写个复杂一点的shell脚本对找到的文件进行处理,结果往往会很奇怪。这是shell传递参数方式所产生的悲惨后果。

日期: Sat, 12 Dec 92 01:15:52 PST
发信人: Jamie Zawinski <jwz@lucid.com>
主题: Q: what's the opposite of 'find?' A: 'lose'
(问题:'find'的反义词是什么? 答案:丢失)
收信人: UNIX-HATERS

我想找出一个目录下的所有的没有对应.elc文件存在的.el文件。这应该不太难,我用的是find.

不过我错了。

我先是这么干的:

% find . -name '*.el' -exec 'test -f {}c'
find: incomplete statement

噢,我记起来了,它需要个分号。

% find . -name '*.el' -exec 'test -f {}c'\;
find: Can't execute test -f {}c:
No such file or directory

真有你的,竟然没去解析这个命令。

% find . -name '*.el' -exec test -f {}c \;

咦,似乎什么也没做...

% find . -name '*.el' -exec echo test -f {}c \;
test -f c
test -f c
test -f c
test -f c
....

明白了。是shell把大括号给展开了。

% find . -name '*.el' -exec test -f '{}'c \;
test -f {}c
test -f {}c
test -f {}c
test -f {}c

嗯?也许我记错了,{}并不是find使用的那个“替换成这个文件名”的符号。真的么?...

% find . -name '*.el' \
-exec test -f '{}' c \;
test -f ./bytecomp/bytecomp-runtime.el c
test -f ./bytecomp/disass.el c
test -f ./bytecomp/bytecomp.el c
test -f ./bytecomp/byte-optimize.el c
....

喔,原来如此。下面该怎么办呢?我想,我似乎可以试试"sed..."

可我忘记了一个深刻的哲理:“当遇到一个Unix问题的时候,有的人会想‘我懂,我可以试试sed.’这下他们有两个问题去对付了。”

试验了五次,阅读了sed手册两遍,我得到了这个:

% echo foo.el | sed 's/$/c/'

于是:

% find . -name '*.el' \
-exec echo test -f `echo '{}' \
| sed 's/$/c'` \;
test -f c
test -f c
test -f c
....

OK, 看来只能去试试所有shell引用的排列组合了,总会有一款和我意吧?

% find . -name '*.el' \
-exec echo test -f "`echo '{}' \
| sed 's/$/c'`" \;
Variable syntax.
% find . -name '*.el' \
-exec echo test -f '`echo "{}" \
| sed "s/$/c"`' \;
test -f `echo "{}" | sed "s/$/c"`
test -f `echo "{}" | sed "s/$/c"`
test -f `echo "{}" | sed "s/$/c"`
....

嗨,最后一个似乎有戏。我只需要这么干一下:

% find . -name '*.el' \
-exec echo test -f '`echo {} \
| sed "s/$/c"`' \;
test -f `echo {} | sed "s/$/c"`
test -f `echo {} | sed "s/$/c"`
test -f `echo {} | sed "s/$/c"`
....

别急,这是我想要的,可是你为什么不把{}替换成文件名呢?你再仔细瞅瞅,{}两边不是有空格么?你究竟想要什么?

哦,等等。那个反单引号间的引用被当成了一个元素。

或许我能用sed把这个反单引号过滤掉。嗯,没戏。

于是我用了半分钟去想如何能运行"-exec sh -c..."之类的东西,终于出现了曙光,写了一段emcas-lisp代码去做这件事。这不困难,挺快的,而且工作了。

我真高兴。我以为一切都过去了。

今天早上我洗澡的时候突然想到了另一种做法。我试了一次又一次,深深坠入了她的情网,意乱情迷,无法自拔。醉了。只有罕诺塔的Scribe实现曾给过我这样的快感。我仅试了12次就找到了解法。对于每个遍历到的文件它只产生两个进程。这才是Unix之道!

% find . -name '*.el' -print \
| sed 's/^/FOO-/'|\
sed 's/$/; if [ ! -f ${FOO}c]; then \
echo \ $FOO; fi/' | sh

BWAAAAAHH HAAAAHH HAAAAHH HAAAAHH HAAAAHH HAAAAHH HAAAAHH HAAAAHH!!!!

―Jamie      
1) 思路太混乱
2) 对 shell 语法理解不深刻
3) 以为 shell 是智能机器人