摘自痛恨手册
如果想写个复杂一点的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