本文简要介绍通过 管道使用 find,bzip2,grep,案例是把诸多硬盘的目录(和文件)结构压缩存储,用于离线查找硬盘上的文件;还涉及到流在通过管道时压缩和解压缩。
最近拍的照片,哈尔滨探望大毅,跟ZHUMAO出去玩,在这里 [http://www.douban.com/photos/album/106351444/]。
1. 问题
事实上,我没有"很多"移动硬盘,而是只有"一些"。我倒是有很多光盘,装在半米高的柜子里几乎塞满,里面都是文件,有的是软件,有的是我的笔记和资料。光盘全都用记号笔写了名字,每张光盘还都配了一张纸,打印整页的树状目录。当年盗版软件光盘能做的工作我都做了,就是为了什么时候需要某个文件的时候能一下子查出来。
但是,失败了。
文件太多,一页纸经常打不下,如果简略一些,日后就可能找不到;光盘太多,一张张找来也真是费劲。现在,移动硬盘又出现了类似的问题。当我查找某个东西的时候,经常需要把六七个移动硬盘挨个插上计算机,一顿搜索。有时候还可能忘了最佳的关键词,再重插重搜。
当年的光盘是这样解决的。用TotalCommander
(绝对一大利器,向用windows的同学强烈推荐)做每一张光盘的目录树压缩包,把这些压缩包放在机器的硬盘里。需要找某个文件的时候,搜索这个压缩包,而不用插光盘。找到文件在哪张光盘里,再按卷标找光盘。当然,这要求给文件起个有区分度的好名字。目录树压缩包,是TotalCommander的一个插件,就像ZIP或RAR一样压缩目录,只不过它的内容不是文件,只是文件名。
移动硬盘多了,也出现了当年光盘的问题。不过当年的方案不能用了,因为我现在的坐机是Linux,没用TotalCommander可用。不过,好在上述功能用简单的脚本也足够能完成,涉及到的命令也只有三个而已。
2. 索引和压缩
这一步,把移动硬盘插上,把目录树列出来,做成个压缩包。
比如,我的这块移动硬盘 mount 在 /media/Elements/ 之下,形成的目录树压缩
包的名字是Elements.dir.zip,命令如下:
$ find /media/Elements/ | bzip2 -c > Elements.dir.zip &&
timidity~/tools/tomato/unlock.mid
(1) "find /media/Elements/" 的作用是把硬盘的目录树列出来,因为后面是管道,所以没有任何输出。如果我们截获它的输出,应该类似于:
/media/Elements/
/media/Elements/
/media/Elements/files
/media/Elements/files/ted
/media/Elements/files/TOP250部电影
/media/Elements/files/video
/media/Elements/files/[沙丘].Dune_CN_01_11.mp3
/media/Elements/files/[沙丘].Dune_CN_01_12.mp3
/media/Elements/files/[沙丘].Dune_CN_01_13.mp3
/media/Elements/files/[沙丘].Dune_CN_01_14.mp3
/media/Elements/files/[沙丘].Dune_CN_01_15_A.mp3
/media/Elements/files/[沙丘].Dune_CN_01_15_B.mp3
/media/Elements/files/[沙丘].Dune_CN_01_16_A.mp3
/media/Elements/files/[沙丘].Dune_CN_01_16_B.mp3
/media/Elements/files/[沙丘].Dune_CN_01_21_A.mp3
/media/Elements/files/[沙丘].Dune_CN_01_21_B.mp3
这里之所以不用tree的原因是,tree会形成下面的效果,因而无法用grep查找到某个文件在哪个目录下。
| |-- Card
| | `-- Characters and Viewpoint Elements of Fiction Writing (33)
| | |-- Characters and Viewpoint Elements of Fiction Writing - Card.mobi
| | |-- Characters and Viewpoint Elements of Fiction Writing - Card.pdf
| | |-- cover.jpg
| | `-- metadata.opf
如上,用grep的时候会查到:
| | |-- cover.jpg
我们只知道cover.jpg在第三级,却不容易找到它所在的目录是 "(某个目录)/Card/Characters and Viewpoint
Elements of Fiction Writing (33)"。而根据后文我们会知道,用grep查找而不用眼睛一行行去看非常重要。
(2) "bzip2 -c > Elements.dir.zip"
的作用是把标准输入来的东西压缩为zip格式,命名为Elements.dir.zip。"bzip2
-c"的意思是把压缩的结果输出到标准输出,所以我们用">"把它重定向了。
在本命令中,标准输入就是 (1) 里面的 find 的输出,那个目录树结构。
(3) 后面的 "&& timidity~/tools/tomato/unlock.mid" 的用途是压缩完成以后让计算机叫一声,通知我。
压缩目录树的速度非常快,但是从一整块移动硬盘里获取目录树有时花的时间比较长,而一直盯着屏幕等程序退出绝非我辈之所为--尽可能把任务交给计算机,只要你能说明白了。
"&&"符合不同于";"之处是,"&&"要求之前的命令执行结果正确,无错误返回值,然后再叫,";"不管前面结果如何。
输完上述命令以后,我们就可以听歌看碟看书喝咖啡,等计算机通知我们这块硬盘压完了。然后换一块移动硬盘,改mount点,改压缩包的名字,回画,再去享受生活。
生活里令我们非常享受的一点是,我们没有把目录树先写成个文件,然后再压缩这个文件。我们使用了管道,而不是文件作为中介。这让我想起,不止一次,研究生同学在做项目的时候告诉我,不可能把摄像头啊数据啊什么的直接读出来,必须先写到硬盘上,或者这么做比较方便。我们不能那么做的原因,首要的原因是是这样的效率要比直接读低很多,因为硬盘比内存慢好几个数量级,另一个更重要的原因是:我们学计算机的,代码早有一天要被别人看,咱丢不起这个人呐。
如无必要,勿增实体。
3. 解压和查找
当哪天我们要查硬盘里有什么数据的时候,我们就这样:
$ bzip2 -cd *.dir.zip | grep -i justice
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.01.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.02.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.03.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.04.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.05.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.06.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.07.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.08.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.09.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.10.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.11.mp4
/media/Elements/emule/incoming/[公平:怎么做才正确?].Justice.What's.The.Right.Thing.To.Do.Episode.12.mp4
/media/Elements/emule/incoming/公平与正义.第06集.Justice.What's.The.Right.Thing.To.Do.EP06.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第07集.Justice.What's.The.Right.Thing.To.Do.EP07.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第10集.Justice.What's.The.Right.Thing.To.Do.EP10.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第11集.Justice.What's.The.Right.Thing.To.Do.EP11.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第12集.Justice.What's.The.Right.Thing.To.Do.EP12.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第1集.Justice.What.The.Right.Thing.To.Do.EP01.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第2集.Justice.What.The.Right.Thing.To.Do.EP02.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第3集.Justice.What.The.Right.Thing.To.Do.EP03.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第4集.Justice.What.The.Right.Thing.To.Do.EP4.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第5集.Justice.What.The.Right.Thing.To.Do.EP5.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第8集.Justice.What.The.Right.Thing.To.Do.EP08.HDTV.HALFCD-TLF.mkv
/media/Elements/emule/incoming/公平与正义.第9集.Justice.What.The.Right.Thing.To.Do.EP09.HDTV.HALFCD-TLF.mkv
命令中,管道的前半段, "bzip2 -cd
*.dir.zip"是把所有文件都解压了,然后输出到标准输出。所有文件,就是目录树的内容。我们没有真正地把这些内容显示在标准输出上,而是通过管道给了另一个程序。
这接收目录树内容的另一个程序,就是"grep -i justice"。其中的"-i"的含义如下:
$ grep --help | grep -w \-i
Example: /bin/grep -i 'hello world' menu.h main.c
-i, --ignore-case ignore case distinctions
有的同学可能觉得这样不能一行行看,只能去查,有点别扭。不过,事情是这样:
(1)如果你知道自己要找的东西的名字,那么grep的检索条件就是它。或者
(2)如果你不知道自己要找的东西的名字,你就应该仔细想,想完了再来查。大多数事情都可以委托给别人做,唯独找到自己想要什么,必须亲力亲为,不可假手他人。对于需要别人提示才知道这正是自己想要的,我总是想起老师诱导小学生,"你看,啥啥是不是挺好的。"然后,我们就只能相信。
对于这一点,不少人最初都不太容易接受。她们倾向于在讨论的过程中互相理解和支持,并达成一致意见,"啊,原来咱们要找的是这个。"又或者,他们习惯于在工程中提需求的时候说,"你先这么做着,然后看看不行再改。"这路子就跟敬酒的时候说,"我干了,你随意,随我的意。"我们当然可以随客户的意,如果他们为他们不知道自己想要什么的时候你所完成的将要必要抛弃的工作付钱的话。不过,大家通常都愿意只为自己最后见到的东西付钱。
软件工程的著名案例,对比土木工程,从来没人说,"你先把这桥建起来,不合适的话,再转90度试试。"人们往往还没认识到,软件工程跟刮大白一样,人工也是要钱的。又像下棋,你下错了子儿,就要付出代价,而不能撒娇耍赖。
所以,一定要知道--自己想要的是什么。记得当年外教mimi问我们大家,什么是幸福。众说纷芸,吃好吃的啦,能毕业啦,亲人健康啦。我当时正天天抑郁
(现在似乎也未稍减),所以答:知道自己想要什么,这就是最大的幸福。她老人家唯独觉得我说得挺在理的。不过最后我期末成绩也不怎么样,大概她觉得那也不是我所追求的幸福吧。
跟索引和压缩那步一样,我们没有先把zip里的东西解出来
(你列一下zip里的目录就知道了,里面根本没有文件),然后再去grep,而是通过管道把解压的结果传给了grep。
如无必要,勿增实体。
4. 管道
完成以上步骤就能够保存移动硬盘的目录树和检索了,这一小节只是做个游戏。
$ find . | bzip2 -c | bzip2 -cd
.
./250porcelain.dir.zip
./my_passport.dir.zip
./backup001.dir.zip
./Elements.dir.zip
./gold.dir.zip
./lniu.dir.zip
上述命令以管道分隔成了三个部分。第一部分find,列出目录树;第二部分把目录树的文本压缩了;第三部分把传来的压缩的东西解开,然后写到标准输出上。在管道里,目录树内容的文本先压缩了,然后又解压了。
熟悉windows下的C语言的同学可能会想到,这管道先是处理了ascii-text,然后又处理了二进制,到底它是二进制的啊,还是纯文本的呢?这个问题请你自己查查吧,挺有意思的。
5. 如无必要,勿增实体 以外
计算机就像所有的艺术一样,充满了相互矛盾的各种信条,比如这条就是。一方面,我们相信,在程序设计、脚本写作中,应该尽量避免中间的分支出去的东西。类似的思想,在程序设计中,我们就要减少变量,把表达式的值直接传递给函数或者下一个表达式。比如,不写成:
a = 10 + b;
printf ("%d", a);
而是写成:
printf ("%d", 10+b);
但是另一方面,我们有时候又相信这个信条的反面:过早优化是万恶之源。在这个时候,我们相信,应该先对问题建模,然后再归约。建模和归约两个步骤合并成一个,往往是造成问题的原因,归约后的模型通常不那么易读了。越是聪明人,越熟悉的技术路线
(或者只知道这一两种技术路线),越倾向于把归约视为理所当然,而我们又经常没有聪明到可以像控制直接映射的模型那样控制归约后的模型。典型的例子是,当你脱口而出,"这不就是ABCD嘛",尤其是语带不屑的时候。
所以,如无必要,勿增实体 之外,我们还得知道事情的反面--过早优化是万恶之源。
PS.
本文的命令在 emacs eshell 不好使,因为 eshell 对管道支持不充分,shell-mode则没有问题。
--------------------
博客会手工同步到以下地址:
[http://giftdotyoung.blogspot.com]
[http://blog.csdn.net/younggift]
20130712
20130704
补充 保持我们最初的理想,当面对无数歧路
补充 保持我们最初的理想,当面对无数歧路
本文继续精略介绍用sed批量修改,补充grep按词匹配。兼极精略讨论周朴园为什么是虚伪的,结论是没文化真可怕。
1. 修正一个错误,find
上文提到 "find ." 中的"."是当前目录,有误。在这里,"."是从当前目录向下递归遍历,而不仅仅是当前目录自己。
如运行:
$ find . -name "*.[h|c]" -exec grep -nH fexecv {} \;
./include/schily.h:107: /* 6th arg not const, fexecv forces av[ac] = NULL */
./include/schily.h:108:extern int fexecv __PR((const char *, FILE *,
FILE *, FILE *, int,
./include/schily.h:110:extern int fexecve __PR((const char *, FILE *,
FILE *, FILE *,
./calltree/calltree.c:232: fexecve (Argv[0], f, fpp[1], stderr,
./libschily/fexec.c:111: ret = fexecv(name, in, out, err, ac, av);
./libschily/fexec.c:168: ret = fexecve(name, in, out, err, av, env);
./libschily/fexec.c:175:fexecv(name, in, out, err, ac, av)
./libschily/fexec.c:182: return (fexecve(name, in, out, err, av, environ));
./libschily/fexec.c:186:fexecve(name, in, out, err, av, env)
搜索到的匹配的文件,全都不在当前目录下,而是在子目录include,lcalltree,libschily中。
2. 去掉杂音 grep找getline
上文中用grep找包含getline的文件,结果有些杂音。
$ find . -name "*.[h|c]" -exec grep -nH getline {} \;
./include/schily.h:117:extern int fgetline __PR((FILE *, char *, int));
./include/schily.h:186:extern int getline __PR((char *, int));
./calltree/calltree.c:852: while (fgetline(fp, fname, sizeof(fname)) >= 0)
./libschily/stdio/fgetline.c:1:/* @(#)fgetline.c 1.6 03/06/15
Copyright 1986, 1996-2003 J. Schilling */
./libschily/stdio/fgetline.c:29:fgetline(f, buf, len)
./libschily/stdio/fgetline.c:67:getline(buf, len)
./libschily/stdio/fgetline.c:71: return (fgetline(stdin, buf, len));
其中有5行不是我们想要的,是因为 fgetline 误伤到的。fgetline比我们要找的多个f。
这些杂音会干扰我们的视线,让我们迷失方向,忘掉最初的理想。解决的方法就是:忽略它们,想办法不去注意它们,让噪音消失。
如果你的意志不够坚强,我们可以借助工具,像下面这样,用"单词"匹配,就把 fgetline 这样的去掉了,同时保留 "getline(" 这样的:
$ find . -name "*.[h|c]" -exec grep -w -nH getline {} \;
./include/schily.h:186:extern int getline __PR((char *, int));
./libschily/stdio/fgetline.c:67:getline(buf, len)
上面多的"-w"参数,代表:
$ grep --help | grep \-w -w -w, --word-regexp force PATTERN to match
only whole words
3. sed的使用,批量修改
上文还提到,找到包含getline字样的文件,然后依次用文本编辑器打开,一个个修改。这样还是容易错,在这些细节中,我们也容易迷失理想。而保持理想的秘诀正是忽略现在。
我们可以用sed批量修改。
(1) 先验证一下修改策略。做批处理的计划最怕的是中间某个步骤做了,但这不是放弃使用批量的理由,我们可以在此处加观察验证。
$ find . -name "*.[h|c]" -exec sed 's/\<getline\>/getline_calltree/'
{} \; | grep getline
extern int fgetline __PR((FILE *, char *, int));
extern int getline_calltree __PR((char *, int));
while (fgetline(fp, fname, sizeof(fname)) >= 0)
/* @(#)fgetline.c 1.6 03/06/15 Copyright 1986, 1996-2003 J. Schilling */
fgetline(f, buf, len)
getline_calltree(buf, len)
return (fgetline(stdin, buf, len));
还是用find找到*.h和*.c文件,然后调用 sed 批量修改。
sed的意思是 stream editor,可以执行对文本的编辑命令。这些命令在命令行里给出,不仅跟IDE这样的图形界面不一样,甚至跟emacs/vi这样的全屏编辑也不一样。不知道sed和行编辑哪一个更早。
我们来看一下sed的这些参数。"sed 's/\<getline\>/getline_calltree/' {} "
's/\<getline\>/getline_calltree/'就是编辑的命令,它的意思是:查找 (search,
s)符合条件的文字"\<getline\>",改成"getline_calltree"。格式是"s/匹配条件/修改成什么样子"。
匹配条件"\<getline\>"中的"\<"和"\>"表示按词匹配,类似于grep中的-w。"\"是转义符,因为"<"">"是特殊字符。
所以sed这部分的作用是找到 (*.h和*.c中的)所有getline这样的整词,修改为getline_calltree。
上述命令行最后的 grep getline,是为了检验修改策略是正确的。我们观察结果,果然,所有的getline都变成了getline_calltree,而fgetline无一误伤。
(2) 实施
执行
$ find . -name "*.[h|c]" -exec sed -i 's/\<getline\>/getline_calltree/' {} \;
这里的"-i"参数的意思是:
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied)
(3) 验证
保险起见,可以再看看"-i"到底改了没有。
$ find . -name "*.[h|c]" -exec grep -w -nH getline {} \;
$
果然干干净净的了。
(4) 剩下的工作
然后呢?
find . -name "*.[h|c]" -exec sed -i 's/\<fexecve\>/fexecve_calltree/' {} \;
然后 make。
然后看看编译出来的东西 (object) 在哪里。
$ find . -name calltree
./calltree
./calltree/OBJ/i686-linux-cc/calltree
大功告成。
(5) 批量
这样,当你告诉别人如何修改 calltree
才能编译的时候,你不必写上一大篇"先找到什么,在哪几个文件里,再找什么,在哪几个文件里"。你可以提供一个脚本完成这个任务。
如下:
find . -name "*.[h|c]" -exec sed -i 's/\<getline\>/getline_calltree/' {} \;
find . -name "*.[h|c]" -exec sed -i 's/\<fexecve\>/fexecve_calltree/' {} \;
这两行里蕴含了上述所有要求的步骤。
科幻小说作家Ted姜曾经写过一个情节,两个智商非常高的牛人终于碰面,他们只说了一个字,里面就包含了千言万语,进行了猩猩相惜还有战争。牛人们之所以能进行这种对话的原因在于,话语中的每个词都包含了非常的丰富的约定了确定定义的信息。因为那些是已经知道的了,正如康德说的,分析得到的都不是知识,而综合的,把这些已知知识放在一起的微量的未知,才是知识。调用DLL的程序之所以那么短,就是因为很多动作流程的约定已经在DLL中了。
周朴园之所以可以定性为虚伪的原因,也在于他实施了具有精确约定含义的动作--没有娶四凤她妈,剩下的说啥也白扯。他的各种小动作只是用来表现自己具有何处情怀和愿望而已,于四凤她妈没有任何影响。正是周的这种又做坏事,又要表现自己是好人的的做法,可以性定他为虚伪。所以,有确切含义的约定的知识在判断中至关重要。所有某个知识的背后庞大的网络支撑和构成了这个知识本身。
所以说,没文化真可怕。
--------------------
博客会手工同步到以下地址:
[http://giftdotyoung.blogspot.com]
[http://blog.csdn.net/younggift]
。
本文继续精略介绍用sed批量修改,补充grep按词匹配。兼极精略讨论周朴园为什么是虚伪的,结论是没文化真可怕。
1. 修正一个错误,find
上文提到 "find ." 中的"."是当前目录,有误。在这里,"."是从当前目录向下递归遍历,而不仅仅是当前目录自己。
如运行:
$ find . -name "*.[h|c]" -exec grep -nH fexecv {} \;
./include/schily.h:107: /* 6th arg not const, fexecv forces av[ac] = NULL */
./include/schily.h:108:extern int fexecv __PR((const char *, FILE *,
FILE *, FILE *, int,
./include/schily.h:110:extern int fexecve __PR((const char *, FILE *,
FILE *, FILE *,
./calltree/calltree.c:232: fexecve (Argv[0], f, fpp[1], stderr,
./libschily/fexec.c:111: ret = fexecv(name, in, out, err, ac, av);
./libschily/fexec.c:168: ret = fexecve(name, in, out, err, av, env);
./libschily/fexec.c:175:fexecv(name, in, out, err, ac, av)
./libschily/fexec.c:182: return (fexecve(name, in, out, err, av, environ));
./libschily/fexec.c:186:fexecve(name, in, out, err, av, env)
搜索到的匹配的文件,全都不在当前目录下,而是在子目录include,lcalltree,libschily中。
2. 去掉杂音 grep找getline
上文中用grep找包含getline的文件,结果有些杂音。
$ find . -name "*.[h|c]" -exec grep -nH getline {} \;
./include/schily.h:117:extern int fgetline __PR((FILE *, char *, int));
./include/schily.h:186:extern int getline __PR((char *, int));
./calltree/calltree.c:852: while (fgetline(fp, fname, sizeof(fname)) >= 0)
./libschily/stdio/fgetline.c:1:/* @(#)fgetline.c 1.6 03/06/15
Copyright 1986, 1996-2003 J. Schilling */
./libschily/stdio/fgetline.c:29:fgetline(f, buf, len)
./libschily/stdio/fgetline.c:67:getline(buf, len)
./libschily/stdio/fgetline.c:71: return (fgetline(stdin, buf, len));
其中有5行不是我们想要的,是因为 fgetline 误伤到的。fgetline比我们要找的多个f。
这些杂音会干扰我们的视线,让我们迷失方向,忘掉最初的理想。解决的方法就是:忽略它们,想办法不去注意它们,让噪音消失。
如果你的意志不够坚强,我们可以借助工具,像下面这样,用"单词"匹配,就把 fgetline 这样的去掉了,同时保留 "getline(" 这样的:
$ find . -name "*.[h|c]" -exec grep -w -nH getline {} \;
./include/schily.h:186:extern int getline __PR((char *, int));
./libschily/stdio/fgetline.c:67:getline(buf, len)
上面多的"-w"参数,代表:
$ grep --help | grep \-w -w -w, --word-regexp force PATTERN to match
only whole words
3. sed的使用,批量修改
上文还提到,找到包含getline字样的文件,然后依次用文本编辑器打开,一个个修改。这样还是容易错,在这些细节中,我们也容易迷失理想。而保持理想的秘诀正是忽略现在。
我们可以用sed批量修改。
(1) 先验证一下修改策略。做批处理的计划最怕的是中间某个步骤做了,但这不是放弃使用批量的理由,我们可以在此处加观察验证。
$ find . -name "*.[h|c]" -exec sed 's/\<getline\>/getline_calltree/'
{} \; | grep getline
extern int fgetline __PR((FILE *, char *, int));
extern int getline_calltree __PR((char *, int));
while (fgetline(fp, fname, sizeof(fname)) >= 0)
/* @(#)fgetline.c 1.6 03/06/15 Copyright 1986, 1996-2003 J. Schilling */
fgetline(f, buf, len)
getline_calltree(buf, len)
return (fgetline(stdin, buf, len));
还是用find找到*.h和*.c文件,然后调用 sed 批量修改。
sed的意思是 stream editor,可以执行对文本的编辑命令。这些命令在命令行里给出,不仅跟IDE这样的图形界面不一样,甚至跟emacs/vi这样的全屏编辑也不一样。不知道sed和行编辑哪一个更早。
我们来看一下sed的这些参数。"sed 's/\<getline\>/getline_calltree/' {} "
's/\<getline\>/getline_calltree/'就是编辑的命令,它的意思是:查找 (search,
s)符合条件的文字"\<getline\>",改成"getline_calltree"。格式是"s/匹配条件/修改成什么样子"。
匹配条件"\<getline\>"中的"\<"和"\>"表示按词匹配,类似于grep中的-w。"\"是转义符,因为"<"">"是特殊字符。
所以sed这部分的作用是找到 (*.h和*.c中的)所有getline这样的整词,修改为getline_calltree。
上述命令行最后的 grep getline,是为了检验修改策略是正确的。我们观察结果,果然,所有的getline都变成了getline_calltree,而fgetline无一误伤。
(2) 实施
执行
$ find . -name "*.[h|c]" -exec sed -i 's/\<getline\>/getline_calltree/' {} \;
这里的"-i"参数的意思是:
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied)
(3) 验证
保险起见,可以再看看"-i"到底改了没有。
$ find . -name "*.[h|c]" -exec grep -w -nH getline {} \;
$
果然干干净净的了。
(4) 剩下的工作
然后呢?
find . -name "*.[h|c]" -exec sed -i 's/\<fexecve\>/fexecve_calltree/' {} \;
然后 make。
然后看看编译出来的东西 (object) 在哪里。
$ find . -name calltree
./calltree
./calltree/OBJ/i686-linux-cc/calltree
大功告成。
(5) 批量
这样,当你告诉别人如何修改 calltree
才能编译的时候,你不必写上一大篇"先找到什么,在哪几个文件里,再找什么,在哪几个文件里"。你可以提供一个脚本完成这个任务。
如下:
find . -name "*.[h|c]" -exec sed -i 's/\<getline\>/getline_calltree/' {} \;
find . -name "*.[h|c]" -exec sed -i 's/\<fexecve\>/fexecve_calltree/' {} \;
这两行里蕴含了上述所有要求的步骤。
科幻小说作家Ted姜曾经写过一个情节,两个智商非常高的牛人终于碰面,他们只说了一个字,里面就包含了千言万语,进行了猩猩相惜还有战争。牛人们之所以能进行这种对话的原因在于,话语中的每个词都包含了非常的丰富的约定了确定定义的信息。因为那些是已经知道的了,正如康德说的,分析得到的都不是知识,而综合的,把这些已知知识放在一起的微量的未知,才是知识。调用DLL的程序之所以那么短,就是因为很多动作流程的约定已经在DLL中了。
周朴园之所以可以定性为虚伪的原因,也在于他实施了具有精确约定含义的动作--没有娶四凤她妈,剩下的说啥也白扯。他的各种小动作只是用来表现自己具有何处情怀和愿望而已,于四凤她妈没有任何影响。正是周的这种又做坏事,又要表现自己是好人的的做法,可以性定他为虚伪。所以,有确切含义的约定的知识在判断中至关重要。所有某个知识的背后庞大的网络支撑和构成了这个知识本身。
所以说,没文化真可怕。
--------------------
博客会手工同步到以下地址:
[http://giftdotyoung.blogspot.com]
[http://blog.csdn.net/younggift]
。
20130703
保持我们最初的理想,当面对无数歧路
保持我们最初的理想,当面对无数歧路
本文以实例非常粗浅地介绍Linux下的工具 find, grep, tar
的使用,还有正则表达式。侯捷先生用这些工具帮助写了<STL源码剖析>和<深入浅出MFC>,我们将用这些工具帮助编译一个叫做calltree的小工具。
本文还讨论了如何保持最初的理想。
0. calltee
calltree是个静态分析C代码中的函数调用关系的工具,用来画 call
graph。为了找个工具帮助画几个模块间的依赖关系,找到了它。刚刚这句你看不懂也没关系,你知道calltree是个程序就行了。事实上,大部分文章中的大部分话你看不懂,都没关系。直到因为你不知道这段话所提供的事实影响了你的阅读,那才有关系了。
1. 多歧路
在你读某段话时,如果没有完全读懂,那么宇宙会因此而分裂成为无数条分支,每条分支对应这段话的一种含义的可能。但是,这没有什么影响,你大可以把这些分支都忘了。不过,当你继续读,后面某一段可能会用到刚刚那句话提供的信息了,此时,因为你的注视,所有那些分支宇宙就坍缩为唯一的一个。如果你不知道那句话的确切含义,随着一步步深入,分支就会呈爆炸状迅速增长。
初学者在涉足某个领域的时候,所面临的正是这个问题--在每一步中,可能性都太多了,稍有不慎就拐进了一个死胡同。而且越走越远,接下来全是无用功。正是因为这个,我们要在每一步中检查自己当前的状态,而不能等到最后。
经常有同学问到,为什么我这段程序编译通不过呢?你要来他的程序一看,好几百行,编译一下,错误在第十几行。如果前面的你都没有通过,为什么要继续向下走呢,后面那好几百行都是怎么写出来的啊。
这是理工类的一个重要特点,如果当前的一步没有处理好,未来很难弥补。事实上,能在未来弥补现在的错误,是个巨牛的活儿。一般地,如果你幼稚到现在会犯这样的错误,基本可以断定,你不具备在未来弥补的能力。
所以写代码的时候,如果你承认自己不够牛
(通常如此),那么你应该写一会代码就编译一下,让编译器帮助检查错误。有同学说,我还得一阵才能写到下一个"括回}"呢。你应该在写"{"的下一个字符就写"}",然后再写中间的部分。步子要保证足够小,能在足够短的时间内编译,从而得到检验。如果航海技术不够发达,就应该珍惜生命,不远离陆地,在能看到海岸的地方航行。铁子同学,你是不是想起了《文明II》?
为了回避失败感,初学者们经常想要计划得非常周全,按他们的话说,"我想等想好了的再动手"。他们太骄傲了,计划,这是世界上最难的事实,远远比动手再失败要难很多个数量级。尤其是,你计划这件事情的时候,你尚不知道未来会遇到哪些问题--如果你知道,那么你就是这个并非陌生的领域的专家了。所谓初学者,就是两眼一抹黑地往前走的人。你不走,就永远也不会熟悉这个领域。
2. 解压
你说什么,等专家引领?专家哪有那个功夫。
比如安装calltree。你随便搜索一下,就有人告诉你从哪个网址下载。你下载完了,文件名是 "calltree-2.3.tar.bz2"。
你可能认识不少扩展名,认识 tar.gz,认识 zip,认识 rar,甚至还认识7z。可是bz2是什么?
大家一般的选择:1.找个支持bz2解压的工具,2.搜索一下,3.QQ上找个好友,"bz2怎么解压?"
最后这种人后来就被QQ好友拉黑了。有位大侠在网上提到: tar jxvf
calltree-2.3.tar.bz2。这也可能是你的QQ好友告诉你的。在命令行下跑一遍,你得到了 calltree-2.3 目录。
看起来解压完成了,很多人以为这一节就结束了。不过,在这个时刻,你可能没有注意到,宇宙分裂了。 tar jxvf
是什么意思,你没有注意,它存在各种可能,等到你再遇到需要它的时候,世界将坍缩为"你还是不会"。
这段话的意思,你当然可以搜索一下,或者QQ (你永远可以QQ别人,因此以后这条选择略掉),不过Linux/Unix的习惯是手册里有主要的信息。
我们执行:
$ tar --help | grep \-j
-j, --bzip2 filter the archive through bzip2
然后我们就明白了,j的作用是"filter the archive through
bzip2",原来是用bzip2解压缩的。我们的文档刚好是bz2扩展名,合理合理。
"tar --help"会输出tar的所有帮助。有不少人习惯于把 tar --help 全输出来,然后用眼睛找想要的信息。
如果你知道自己想要什么,那么可以直接告诉计算机,而不用自己再亲力亲为。对大量数据的重复工作,计算机比我们擅长,只要我们指令明确。grep就是干这个用的。grep是一个过滤器,它把符合条件的留下来。"-j"前面的那个"\",是因为"-"是个特殊字符,它的后面是grep的参数,"\"的作用是避免grep把"-j"作为参数,而是作为过滤条件。
如果你不知道自己想要什么,就只能在歧路中摸索;如果你知道自己的目标,那么,就可以直接命中,无需犹豫。大毅同学问我,为什么要看康德呢。我说,因为我想要过毫不犹豫的生活。道理是一样的。
初学者和专家最大的区别在于,专家不必尝试,他知道应该如何做,知道这样做的结果如何,甚至还知道误差和出错的概率有多大,甚至还知道如果出错如何改善。这些"如何"的难度,从前向后,越来越大。
2. make失败
解压以后,按Linux下从源代码编译安装,惯例就是进目录,读INSTALL文件;或者不读,直接跑configure或者make。
我make,然后出了一大堆错误。有的同学可能就要一行行找错误了,用眼睛。这很难,因为make的输出信息长达511行。这个数据也不是我一行行查的,而是:
$ make | wc -l
511
你要到哪里找错误呢?先想想错误信息的特征是什么。没错,"error"。所以,我们再用 grep。
$ make | grep -i "error"
../include/schily.h:186: error: conflicting types for 'getline'
make[2]: *** [OBJ/i686-linux-cc/avoffset.o] Error 1
make[1]: *** [all] Error 2
../include/schily.h:110: error: conflicting types for 'fexecve'
../include/schily.h:186: error: conflicting types for 'getline'
make[2]: *** [OBJ/i686-linux-cc/cvmod.o] Error 1
make[1]: *** [all] Error 2
../include/schily.h:110: error: conflicting types for 'fexecve'
../include/schily.h:186: error: conflicting types for 'getline'
make[1]: *** [OBJ/i686-linux-cc/calltree.o] Error 1
~/Downloads/calltree-2.3 $
这错误有点让人感到莫名其妙,因为getline和fexecve是两个著名的函数,你甚至能man出来它们,是posix的一部分。又或者你根本不知道它们是什么也行,你就会觉得,"这源代码不是应该一编译就通过吗,搞什么..."
3. 修改源代码
搜索一下,我找到了EnuLL大侠的博客[http://www.3null.org/?p=439],他说,"在比较新的内核(glibc库)上编译,会出现编译不过的问题。具体现象大概像下面这个样子..."
这个样子,就是你上面看到的样子。
然后EnuLL大侠说,"具体解决办法有两种,一种就是给glibc打patch,另一种就麻烦点,把冲突的命名手动都改了。"
关于解决方案,这句话到这里就完了,初学者就也完了。打patch,怎么打啊,手动改命名,怎么改啊。此处说来话长,我略过吧。总之,我们就知道,应该这样手动命名:把calltree源代码里所有声明、定义、调用这两个函数的地方,都把这两个函数改个名字。只要新的名字不再与posix的重名,那就没冲突了,编译就能通过了。
好了,目标明确了,你打算怎么动手?
有同学可能又想到了,一个个打开文件,然后有可能一行行看,用眼睛找到它们。或者,如果你有个够好的IDE,能全工程全文搜索--但是有时候你正用的项目恰恰不能在这个IDE上打开。再好的IDE和恒久远的钻石都是浮云,只有纯文本才是永恒的。
我们这样:
$ find . -name "*.[c|h]" -exec grep getline -nH {} \;
./include/schily.h:117:extern int fgetline __PR((FILE *, char *, int));
./include/schily.h:186:extern int getline __PR((char *, int));
./calltree/calltree.c:852: while (fgetline(fp, fname, sizeof(fname)) >= 0)
./libschily/stdio/fgetline.c:1:/* @(#)fgetline.c 1.6 03/06/15
Copyright 1986, 1996-2003 J. Schilling */
./libschily/stdio/fgetline.c:29:fgetline(f, buf, len)
./libschily/stdio/fgetline.c:67:getline(buf, len)
./libschily/stdio/fgetline.c:71: return (fgetline(stdin, buf, len));
从输出结果中能够看到:
(1) 在 include/schily.h的186行,是getline的声明;
(2) libschily/stdio/fgetline.c第67行,也提到了getline,查看代码以后发现,这是getline的定义;
(3) 奇怪,我没有找到调用getline的地方,不调用为什么要声明和定义。
(4) 那些fgetline是误伤的。
再来看产生这个结果的命令,"find . -name "*.[c|h]" -exec grep getline -nH {} \;"。有点乱,我们一段一段看。
find是个命令,找符合条件的文件。
(1) "find . -name"的意思是在当前目录"."下,找文件名符合条件的。
(2) "*.[c|h]"就是文件名要符合的条件。这是个正则表达式,意思是文件名任意("*"),扩展名(从"."开始)是"c"或者是"h"。我对正则表达式的运用还不是那么本能,所以实践操作中,没有先想到用正则表达式,而是先找了文件名为"*.c"的,然后找了文件名为"*.h"的。这也是典型的幼推的初学者的表现--不能一次性全部地了解自己的想法。
(3) "-exec ... {}... \;"。"-exec"的作用是,对find到的符合条件的文件,都施加一个动作 (命令)
,在本例中,这个动作就是grep。"{}"就代表找到的文件在这个命令中的位置。";"的作用是标识"-exec"的命令部分结束了,"\"是避免shell把";"当作find这个命令和下一个命令分隔符。wikipedia说得更明白一些,"The
semicolon (backslashed to avoid the shell interpreting it as a command
separator) indicates the end of the command."
如果找到的文件是 gold.c,那么"-exec"以后的部分相当于:grep getline -nH gold.c。注意,gold.c刚好取代了"{}"。
(4)"grep getline -nH {}"。grep是按正则表达式
(或退化为字符串)在文本中找到符合条件的行。"getline"是要匹配的正则表达式,在本例中,是我们要找到的那个函数。"{}"的意思上面说过了,代表find找到的文件,"-nH"的意思是打印出文件名和行号,你可以这样知道:
$ grep --help | grep \-H
-H, --with-filename print the filename for each match
$ grep --help | grep \-n,
-n, --line-number print line number with output lines
(5) 以上连起来,就是 "find . -name "*.[c|h]" -exec grep getline -nH {} \;"。
含义是:在当前目录下,找到所有的*.c和*.h;然后在这些文本文件的正文中,找到存在"getline"字样的地方。
然后呢?然后我们用文本编辑器打开那几个文件,找到对应的地方。我把getline都改为 getline_calltree。
然后呢?然后把 fexecve 也都改了,我改成 fexecve_calltree。
4. 可以更自动化
本例中,要改的地方不过五六处而已。如果要改的地方特别多呢?有个工具,叫做sed。
5. 再make
再make,通过了。为什么知道通过了呢?
我们执行
$ make | grep -i "error"
没有输出。没有消息就是好消息。
在".../OBJ/"下,我们得到了 calltree 文件,是可执行的。
6. 回顾歧路
每一个步骤,如果我们不选用自动化的 (批量的)
工具,我们都可能更倾向于陷入细节当中,迷失方向。尤其当我们是初学者的时候,我们尚不知道眼睛该往哪里看。这些细节具有多种可能性,很多步骤的多种可能性叠加起来,我们的面前摆着的就是一张非常复杂的网,每个节点都通向好几个地方。
我们总觉得眼前的就是最重要的,"活在当下"。就这样,我们被眼前的细节引导向完全不同的没有预期的方向;就这样,我们忘掉了最初的理想。
--------------------
博客会手工同步到以下地址:
[http://giftdotyoung.blogspot.com]
[http://blog.csdn.net/younggift]
。
本文以实例非常粗浅地介绍Linux下的工具 find, grep, tar
的使用,还有正则表达式。侯捷先生用这些工具帮助写了<STL源码剖析>和<深入浅出MFC>,我们将用这些工具帮助编译一个叫做calltree的小工具。
本文还讨论了如何保持最初的理想。
0. calltee
calltree是个静态分析C代码中的函数调用关系的工具,用来画 call
graph。为了找个工具帮助画几个模块间的依赖关系,找到了它。刚刚这句你看不懂也没关系,你知道calltree是个程序就行了。事实上,大部分文章中的大部分话你看不懂,都没关系。直到因为你不知道这段话所提供的事实影响了你的阅读,那才有关系了。
1. 多歧路
在你读某段话时,如果没有完全读懂,那么宇宙会因此而分裂成为无数条分支,每条分支对应这段话的一种含义的可能。但是,这没有什么影响,你大可以把这些分支都忘了。不过,当你继续读,后面某一段可能会用到刚刚那句话提供的信息了,此时,因为你的注视,所有那些分支宇宙就坍缩为唯一的一个。如果你不知道那句话的确切含义,随着一步步深入,分支就会呈爆炸状迅速增长。
初学者在涉足某个领域的时候,所面临的正是这个问题--在每一步中,可能性都太多了,稍有不慎就拐进了一个死胡同。而且越走越远,接下来全是无用功。正是因为这个,我们要在每一步中检查自己当前的状态,而不能等到最后。
经常有同学问到,为什么我这段程序编译通不过呢?你要来他的程序一看,好几百行,编译一下,错误在第十几行。如果前面的你都没有通过,为什么要继续向下走呢,后面那好几百行都是怎么写出来的啊。
这是理工类的一个重要特点,如果当前的一步没有处理好,未来很难弥补。事实上,能在未来弥补现在的错误,是个巨牛的活儿。一般地,如果你幼稚到现在会犯这样的错误,基本可以断定,你不具备在未来弥补的能力。
所以写代码的时候,如果你承认自己不够牛
(通常如此),那么你应该写一会代码就编译一下,让编译器帮助检查错误。有同学说,我还得一阵才能写到下一个"括回}"呢。你应该在写"{"的下一个字符就写"}",然后再写中间的部分。步子要保证足够小,能在足够短的时间内编译,从而得到检验。如果航海技术不够发达,就应该珍惜生命,不远离陆地,在能看到海岸的地方航行。铁子同学,你是不是想起了《文明II》?
为了回避失败感,初学者们经常想要计划得非常周全,按他们的话说,"我想等想好了的再动手"。他们太骄傲了,计划,这是世界上最难的事实,远远比动手再失败要难很多个数量级。尤其是,你计划这件事情的时候,你尚不知道未来会遇到哪些问题--如果你知道,那么你就是这个并非陌生的领域的专家了。所谓初学者,就是两眼一抹黑地往前走的人。你不走,就永远也不会熟悉这个领域。
2. 解压
你说什么,等专家引领?专家哪有那个功夫。
比如安装calltree。你随便搜索一下,就有人告诉你从哪个网址下载。你下载完了,文件名是 "calltree-2.3.tar.bz2"。
你可能认识不少扩展名,认识 tar.gz,认识 zip,认识 rar,甚至还认识7z。可是bz2是什么?
大家一般的选择:1.找个支持bz2解压的工具,2.搜索一下,3.QQ上找个好友,"bz2怎么解压?"
最后这种人后来就被QQ好友拉黑了。有位大侠在网上提到: tar jxvf
calltree-2.3.tar.bz2。这也可能是你的QQ好友告诉你的。在命令行下跑一遍,你得到了 calltree-2.3 目录。
看起来解压完成了,很多人以为这一节就结束了。不过,在这个时刻,你可能没有注意到,宇宙分裂了。 tar jxvf
是什么意思,你没有注意,它存在各种可能,等到你再遇到需要它的时候,世界将坍缩为"你还是不会"。
这段话的意思,你当然可以搜索一下,或者QQ (你永远可以QQ别人,因此以后这条选择略掉),不过Linux/Unix的习惯是手册里有主要的信息。
我们执行:
$ tar --help | grep \-j
-j, --bzip2 filter the archive through bzip2
然后我们就明白了,j的作用是"filter the archive through
bzip2",原来是用bzip2解压缩的。我们的文档刚好是bz2扩展名,合理合理。
"tar --help"会输出tar的所有帮助。有不少人习惯于把 tar --help 全输出来,然后用眼睛找想要的信息。
如果你知道自己想要什么,那么可以直接告诉计算机,而不用自己再亲力亲为。对大量数据的重复工作,计算机比我们擅长,只要我们指令明确。grep就是干这个用的。grep是一个过滤器,它把符合条件的留下来。"-j"前面的那个"\",是因为"-"是个特殊字符,它的后面是grep的参数,"\"的作用是避免grep把"-j"作为参数,而是作为过滤条件。
如果你不知道自己想要什么,就只能在歧路中摸索;如果你知道自己的目标,那么,就可以直接命中,无需犹豫。大毅同学问我,为什么要看康德呢。我说,因为我想要过毫不犹豫的生活。道理是一样的。
初学者和专家最大的区别在于,专家不必尝试,他知道应该如何做,知道这样做的结果如何,甚至还知道误差和出错的概率有多大,甚至还知道如果出错如何改善。这些"如何"的难度,从前向后,越来越大。
2. make失败
解压以后,按Linux下从源代码编译安装,惯例就是进目录,读INSTALL文件;或者不读,直接跑configure或者make。
我make,然后出了一大堆错误。有的同学可能就要一行行找错误了,用眼睛。这很难,因为make的输出信息长达511行。这个数据也不是我一行行查的,而是:
$ make | wc -l
511
你要到哪里找错误呢?先想想错误信息的特征是什么。没错,"error"。所以,我们再用 grep。
$ make | grep -i "error"
../include/schily.h:186: error: conflicting types for 'getline'
make[2]: *** [OBJ/i686-linux-cc/avoffset.o] Error 1
make[1]: *** [all] Error 2
../include/schily.h:110: error: conflicting types for 'fexecve'
../include/schily.h:186: error: conflicting types for 'getline'
make[2]: *** [OBJ/i686-linux-cc/cvmod.o] Error 1
make[1]: *** [all] Error 2
../include/schily.h:110: error: conflicting types for 'fexecve'
../include/schily.h:186: error: conflicting types for 'getline'
make[1]: *** [OBJ/i686-linux-cc/calltree.o] Error 1
~/Downloads/calltree-2.3 $
这错误有点让人感到莫名其妙,因为getline和fexecve是两个著名的函数,你甚至能man出来它们,是posix的一部分。又或者你根本不知道它们是什么也行,你就会觉得,"这源代码不是应该一编译就通过吗,搞什么..."
3. 修改源代码
搜索一下,我找到了EnuLL大侠的博客[http://www.3null.org/?p=439],他说,"在比较新的内核(glibc库)上编译,会出现编译不过的问题。具体现象大概像下面这个样子..."
这个样子,就是你上面看到的样子。
然后EnuLL大侠说,"具体解决办法有两种,一种就是给glibc打patch,另一种就麻烦点,把冲突的命名手动都改了。"
关于解决方案,这句话到这里就完了,初学者就也完了。打patch,怎么打啊,手动改命名,怎么改啊。此处说来话长,我略过吧。总之,我们就知道,应该这样手动命名:把calltree源代码里所有声明、定义、调用这两个函数的地方,都把这两个函数改个名字。只要新的名字不再与posix的重名,那就没冲突了,编译就能通过了。
好了,目标明确了,你打算怎么动手?
有同学可能又想到了,一个个打开文件,然后有可能一行行看,用眼睛找到它们。或者,如果你有个够好的IDE,能全工程全文搜索--但是有时候你正用的项目恰恰不能在这个IDE上打开。再好的IDE和恒久远的钻石都是浮云,只有纯文本才是永恒的。
我们这样:
$ find . -name "*.[c|h]" -exec grep getline -nH {} \;
./include/schily.h:117:extern int fgetline __PR((FILE *, char *, int));
./include/schily.h:186:extern int getline __PR((char *, int));
./calltree/calltree.c:852: while (fgetline(fp, fname, sizeof(fname)) >= 0)
./libschily/stdio/fgetline.c:1:/* @(#)fgetline.c 1.6 03/06/15
Copyright 1986, 1996-2003 J. Schilling */
./libschily/stdio/fgetline.c:29:fgetline(f, buf, len)
./libschily/stdio/fgetline.c:67:getline(buf, len)
./libschily/stdio/fgetline.c:71: return (fgetline(stdin, buf, len));
从输出结果中能够看到:
(1) 在 include/schily.h的186行,是getline的声明;
(2) libschily/stdio/fgetline.c第67行,也提到了getline,查看代码以后发现,这是getline的定义;
(3) 奇怪,我没有找到调用getline的地方,不调用为什么要声明和定义。
(4) 那些fgetline是误伤的。
再来看产生这个结果的命令,"find . -name "*.[c|h]" -exec grep getline -nH {} \;"。有点乱,我们一段一段看。
find是个命令,找符合条件的文件。
(1) "find . -name"的意思是在当前目录"."下,找文件名符合条件的。
(2) "*.[c|h]"就是文件名要符合的条件。这是个正则表达式,意思是文件名任意("*"),扩展名(从"."开始)是"c"或者是"h"。我对正则表达式的运用还不是那么本能,所以实践操作中,没有先想到用正则表达式,而是先找了文件名为"*.c"的,然后找了文件名为"*.h"的。这也是典型的幼推的初学者的表现--不能一次性全部地了解自己的想法。
(3) "-exec ... {}... \;"。"-exec"的作用是,对find到的符合条件的文件,都施加一个动作 (命令)
,在本例中,这个动作就是grep。"{}"就代表找到的文件在这个命令中的位置。";"的作用是标识"-exec"的命令部分结束了,"\"是避免shell把";"当作find这个命令和下一个命令分隔符。wikipedia说得更明白一些,"The
semicolon (backslashed to avoid the shell interpreting it as a command
separator) indicates the end of the command."
如果找到的文件是 gold.c,那么"-exec"以后的部分相当于:grep getline -nH gold.c。注意,gold.c刚好取代了"{}"。
(4)"grep getline -nH {}"。grep是按正则表达式
(或退化为字符串)在文本中找到符合条件的行。"getline"是要匹配的正则表达式,在本例中,是我们要找到的那个函数。"{}"的意思上面说过了,代表find找到的文件,"-nH"的意思是打印出文件名和行号,你可以这样知道:
$ grep --help | grep \-H
-H, --with-filename print the filename for each match
$ grep --help | grep \-n,
-n, --line-number print line number with output lines
(5) 以上连起来,就是 "find . -name "*.[c|h]" -exec grep getline -nH {} \;"。
含义是:在当前目录下,找到所有的*.c和*.h;然后在这些文本文件的正文中,找到存在"getline"字样的地方。
然后呢?然后我们用文本编辑器打开那几个文件,找到对应的地方。我把getline都改为 getline_calltree。
然后呢?然后把 fexecve 也都改了,我改成 fexecve_calltree。
4. 可以更自动化
本例中,要改的地方不过五六处而已。如果要改的地方特别多呢?有个工具,叫做sed。
5. 再make
再make,通过了。为什么知道通过了呢?
我们执行
$ make | grep -i "error"
没有输出。没有消息就是好消息。
在".../OBJ/"下,我们得到了 calltree 文件,是可执行的。
6. 回顾歧路
每一个步骤,如果我们不选用自动化的 (批量的)
工具,我们都可能更倾向于陷入细节当中,迷失方向。尤其当我们是初学者的时候,我们尚不知道眼睛该往哪里看。这些细节具有多种可能性,很多步骤的多种可能性叠加起来,我们的面前摆着的就是一张非常复杂的网,每个节点都通向好几个地方。
我们总觉得眼前的就是最重要的,"活在当下"。就这样,我们被眼前的细节引导向完全不同的没有预期的方向;就这样,我们忘掉了最初的理想。
--------------------
博客会手工同步到以下地址:
[http://giftdotyoung.blogspot.com]
[http://blog.csdn.net/younggift]
。
Subscribe to:
Posts (Atom)