﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-weidagang2046的专栏-文章分类-Linux</title><link>http://www.blogjava.net/weidagang2046/category/1069.html</link><description>物格而后知致</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 08:21:02 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 08:21:02 GMT</pubDate><ttl>60</ttl><item><title>wget——Linux下Web文件提取工具</title><link>http://www.blogjava.net/weidagang2046/articles/81685.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 17 Nov 2006 01:56:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/81685.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/81685.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/81685.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/81685.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/81685.html</trackback:ping><description><![CDATA[
		<font color="#cccccc">
				<font color="#000000">wget是一个Linux环境下用于从World Wide Web上提取文件的工具，这是一个GPL许可 <br />证下的自由软件，其作者为Hrvoje Niksic &lt;hniksic@srce.hr&gt;。wget支持HTTP和FTP <br />协议，支持代理服务器和断点续传功能，能够自动递归远程主机的目录，找到合乎条 <br />件的文件并将其下载到本地硬盘上；如果必要，wget将恰当地转换页面中的超级连接 <br />以在本地生成可浏览的镜像。由于没有交互式界面，wget可在后台运行，截获并忽略 <br />HANGUP信号，因此在用户推出登录以后，仍可继续运行。通常，wget用于成批量地下 <br />载Internet网站上的文件，或制作远程网站的镜像。 <br /><br />语法: <br /><br />    wget [options] [URL-list] <br />URL地址格式说明:可以使用如下格式的URL: <br />    http://host[:port]/path <br />例如: <br />    http://fly.cc.fer.hr/ <br />    ftp://ftp.xemacs.org/pub/xemacs/xemacs-19.14.tar.gz <br />    ftp://username:password@host/dir/file  <br />在最后一种形式中，以URL编码形式为FTP主机提供了用户名和密码（当然，也可以使 <br />用参数提供该信息，见后）。 <br /><br />参数说明： <br /><br />wget的参数较多，但大部分应用只需要如下几个常用的参数： <br />    -r    递归；对于HTTP主机，wget首先下载URL指定的文件，然后（如果该文件是 <br />    一个HTML文档的话）递归下载该文件所引用（超级连接）的所有文件（递归深度 <br />    由参数-l指定）。对FTP主机，该参数意味着要下载URL指定的目录中的所有文件， <br />    递归方法与HTTP主机类似。 <br /><br />    -N    时间戳：该参数指定wget只下载更新的文件，也就是说，与本地目录中的对 <br />    应文件的长度和最后修改日期一样的文件将不被下载。 <br /><br />    -m    镜像：相当于同时使用-r和-N参数。 <br /><br />    -l    设置递归级数；默认为5。-l1相当于不递归；-l0为无穷递归；注意，当递 <br />    归深度增加时，文件数量将呈指数级增长。 <br /><br />    -t     设置重试次数。当连接中断（或超时）时，wget将试图重新连接。如果指 <br />    定-t0，则重试次数设为无穷多。 <br /><br />    -c    指定断点续传功能。实际上，wget默认具有断点续传功能，只有当你使用别 <br />    的ftp工具下载了某一文件的一部分，并希望wget接着完成此工作的时候，才需要 <br />    指定此参数。 <br /><br />使用举例： <br />    wget -m -l4 -t0 http://oneweb.com.cn/ <br />将在本地硬盘建立http://oneweb.com.cn/的镜像，镜像文件存入当前目录下一个名为 <br />oneweb.com.cn的子目录中（你也可以使用-nH参数指定不建立该子目录，而直接在当前 <br />目录下建立镜像的目录结构），递归深度为4，重试次数为无穷（若连接出现问题， <br />wget将坚韧不拔地永远重试下去，知道任务完成！） <br /><br />另外一些使用频率稍低的参数如下： <br />    -A acclist / -R rejlist： <br />这两个参数用于指定wget接受或排除的文件扩展名，多个名称之间用逗号隔开。例如， <br />假设我们不想下载MPEG视频影像文件和.AU声音文件，可使用如下参数： <br />    -R mpg,mpeg,au <br /><br />其它参数还有： <br />    -L     只扩展相对连接，该参数对于抓取指定站点很有用，可以避免向宿主主机 <br />    的其他目录扩散。例如，某个人网站地址为：http://www.xys.org/~ppfl/，使用 <br />    如下命令行： <br />    wget -L http://www.xys.org/~ppfl/ <br />    则只提取该个人网站，而不涉及主机www.xys.org上的其他目录。 <br /><br />    -k    转换连接：HTML文件存盘时，将其中的非相对连接转换成为相对连接。 <br /><br />    -X    在下载FTP主机上的文件时，排除若干指定的目录 <br /><br />另外，下面参数用于设置wget的工作界面： <br />    -v    设置wget输出详细的工作信息。 <br />    -q    设置wget不输出任何信息。 <br /><br />如果我们已经在一个HTML文档（或普通文本文档）中存储了所要提取的文件的连接， <br />可以让wget直接从该文件中提取信息，而不用在命令行中提供URL地址，参数格式为： <br />    -i filename <br />地址文件也可以不是HTML文档，例如，一个普通的文本文件，其中有需要下载的URL列 <br />表即可。 <br />我们可以用以下技巧提高下载速度：由于Linux是一个多任务系统，我们可以同时运行 <br />多个wget进程以提高下载速度，例如，先下载某主页文件（index.html），然后将该 <br />文件所列出的所有地址分别用一个独立的wget进程进行下载。 <br /><br />至于其他的参数，可参考wget的man手册页，命令为： <br />    man wget</font>
				<br />
				<br />from: <a href="http://fanqiang.chinaunix.net/a6/b9/20020514/060201225.html">http://fanqiang.chinaunix.net/a6/b9/20020514/060201225.html</a></font>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/81685.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-17 09:56 <a href="http://www.blogjava.net/weidagang2046/articles/81685.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>EMACS在windows下的配置 </title><link>http://www.blogjava.net/weidagang2046/articles/61078.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 31 Jul 2006 12:17:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/61078.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/61078.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/61078.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/61078.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/61078.html</trackback:ping><description><![CDATA[
		<div class="storycontent" id="article_main" style="CLEAR: both">EMACS在windows环境下关于HOME目录设置和中文环境设置的问题和方法 <br />Emacs 的配置文件是一个叫 <font color="#ff0000">.emacs</font> 的文件，不<br />知道 M$ 哪两个部门的沟通出了点问题，导致 windows explorer 无<br />法直接创建 ^.* 这样的 文件（而这样的文件名在windows文件系统中<br />却又是合法的，啧啧）， 所以 ，在windows下也可以 用<br /><font color="#ff0000">_emacs</font> 来代替。<br /><br />下一步，让emacs找到你的 .emacs 配置文件，如果你什么都懒得做，<br />就直接仍到 <font color="#ff0000">C:/ </font>下面好了。<font color="#0000ff"><br />但是我最强烈 的推荐大家，稍微多做一点点工作，以便以后工作的更好</font>。设置一个“主目录”，这个目录通常就是<br /><br />c:/Documentsand Settings/Administrator<br /><br />你也可以设置能其它目录，以方便重装系统的时候备份。<br /><br />设置主目录有两种方法<br /><br />1. 新建一个名字叫 <font color="#008000">HOME</font> 的环境变量。变量<br />值就是你的想要的目录路径名称。<br /><br />2. 如果你觉得第一种方法不过瘾，想让 Windowz 最强大的注册表发<br />挥一下功能，也可以。新建一个数据项<br /><br /><font color="#008000">HKEY_CURRENT_USERSoftwareGNUEmacs</font><br /><br />在里面添加一个 REG_SZ 类型的字符串，名字就叫<br /><font color="#008000">HOME</font>, 值就是你想要的目录路径名。<br /><br />设置好主目录后，把 .emacs(_emacs) 扔进去。最基本的设置就算完<br />成了。<br /><br />3. 中文支持<br /><br />Emacs 是一个真正的支持多语言编辑器。试试看<br /><br />M-x view-hello-file<br /><br />会列出Emacs支持的所有语言。如果有些大方块在里面，那不是<br />Emacs 的错，因为你的系统里没有那种字体。<br /><br />如果你下载的是那个 CVS Emacs,据说默认就支持中文了。如果是21.3<br />，可能需要一点配置。<br /><br />打开 .emacs(_emacs) 文件，你现在还不会用 Emacs, 所以还是先用<br />notepad, ue， editplus 等你熟悉的编辑器吧。<br /><br />注意：<font color="#008000">绿色</font>部分为代码，直接copy过去就可以，'';''开头的行，是<br />elisp的注释行，但愿你的编辑器已经支持它了。<br /><br />加入以下几行。<br /><font color="#008000"><br />(set-terminal-coding-system ''chinese-iso-8bit)<br />(set-keyboard-coding-system ''chinese-iso-8bit)<br />(set-language-environment ''chinese-gb)<br />(set-clipboard-coding-system ''chinese-iso-8bit)<br />(set-selection-coding-system ''chinese-iso-8bit)<br />(setq locale-coding-system ''chinese-iso-8bit)<br />(setq current-language-environment "Chinese-GB")</font><br />启动emacs, 试着敲几个中文，应该不会有问题了。 <br style="CLEAR: both" /><br />from: <a href="http://deanxx.bloghome.cn/posts/940">http://deanxx.bloghome.cn/posts/940</a></div>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/61078.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-07-31 20:17 <a href="http://www.blogjava.net/weidagang2046/articles/61078.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Emacs buffer和window相关问题</title><link>http://www.blogjava.net/weidagang2046/articles/61000.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 31 Jul 2006 05:22:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/61000.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/61000.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/61000.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/61000.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/61000.html</trackback:ping><description><![CDATA[怎么才能知道Emacs中活动buffer的数量？有三种方法：buffer列表（键入C-x C-b时显示在一窗口中），Buffers菜单（它列出了活动的buffer和浏览这些buffer的命令），还有Buffer弹出菜单（按住Ctrl，单击鼠标左键访问，它根据模式列出buffer）。<br /><br />Emacs会创建它自己的专用buffer。这些内部buffer的名字一般来说格式为 *buffer name* 。*Help*、*scratch*和*Buffer List*就是Emacs创建的其中一些buffer。<br /><br />当你启动Emacs时，它会建立两个buffer：<br /><br />*Messages*<br />*scratch*<br /><br />*Messages* buffer存储了Emacs收集自它启动时从minibuffer里显示的消息；*scratch*是个临时的scratchpad（涂鸦板），以便你有地方输入东西。除非你使用C-x C-w明确的把它写到文件中，否则它不会被保存下来。<br /><br />当然，一般来说你用Emacs编辑文件时，这些文件就会被拷贝到同名的buffer中。如果你需要获得帮助，则会进入 *Help* buffer。<br /><br />能在Emacs里打开的buffer数量实际上没有限制。大多数情况下，只显示一个或两个buffer；不过即使你无法看到全部，你在某个Emacs会话里创建的buffer仍处于活动状态。你可以把这些buffer想象成一叠纸，只有放在最上面的那页才显示在你眼前。不过任何时候，你都可以翻到另一页（另一个buffer），或者也可以创建一个新页。<br /><br />每个buffer都关联一个主模式（major mode），它决定了Emacs在这个buffer里的行为。例如，设计用来书写文本的文本模式（text mode），和Lisp模式的表现就不一样，后者被设计用来编写Lisp程序。<br /><br /><span style="FONT-WEIGHT: bold">如何操作多个buffer</span><br /><br />如果要新建包含一个文件的buffer，只需键入 C-x C-f 找到该文件。Emacs就会自动新建一个buffer并定位到该buffer。如果该文件已打开，C-x C-f 只是定位到已有buffer中，这一点很有意义，可以避免同一文件存在多个buffer导致混乱。如果键入 C-x C-f 后输入的文件名不存在，Emacs认为你想新建一个文件，并定位到一个空白buffer中。<br /><br />C-x b：在多个buffer之间进行切换；键入命令后输入buffer名，回车。如果已存在该buffer，则切换到该buffer中；否则以输入的buffer名新建一个buffer，但是注意这个buffer并没有和文件相关，因此关闭Emacs，它不会给你任何提示。<br /><br />C-mouse 1：按住Ctrl并单击鼠标左键，会弹出一个Buffer Menu，它会按主模式类型列出活动的buffer供你选择。<br /><br />C-x -&gt;(&lt;-)：按下C-x后（放开）再按向右（左）方向键可以定位到下（上）一个buffer中。<br /><br /><span style="FONT-WEIGHT: bold">如何删除buffer</span><br /><br />注意：如果你修改了一个buffer（且该buffer和一个文件相关），则Emacs会在删除buffer前询问你是否保存所做修改；如果这个buffer和文件不相关，则你在该buffer里所做任何修改都会丢失，Emacs认为你不在乎这些buffer因此不作任何提示。因此编辑重要的buffer之前最好先 C-x C-w 写到文件中，或者用 C-x C-f 新建buffer。<br /><br />C-x k：kill-buffer命令，删除一个buffer；<br /><br />M-x kill-some-buffers：删除一些buffer；<br /><br />如果删除了当前会话里的所有buffer，Emacs会新建一个 *scratch* buffer，总得有个东西显示在屏幕上不是？:P<br /><br /><span style="FONT-WEIGHT: bold">如何保存buffer</span><br /><br />C-x C-s：保存当前buffer；<br /><br />C-x s：即save-some-buffers命令，一次保存所有buffer；<br /><br /><span style="FONT-WEIGHT: bold">如何重命名buffer</span><br /><br />M-x rename-buffer：重命名buffer；<br /><br /><span style="FONT-WEIGHT: bold">如何使buffer只读</span><br /><br />C-x C-q：切换buffer的read-only和read-write状态；<br /><br /><span style="FONT-WEIGHT: bold">buffer和window的关系</span><br /><br />buffer和window并不是一一对应的，同一个buffer可以有多个window，比如你可以同时在多个window里查看同一buffer的不同部分。Mark是和buffer关联的；而point是和window关联的。<br /><br />C-x 2：即split-window-vertically命令，水平切分窗口；<br /><br />C-x 3：垂直切分窗口；<br /><br />C-x 4 b(f)：在另一个窗口选择另一个buffer（查找另一个文件），这样你可以不用切换到另一个window，就改变其buffer或打开文件；<br /><br />C-M-v：滚动另一个window（C-v是滚动当前window）；<br /><br />C-x o：此处 o 表示other（其它），移动光标到另一个window；<br /><br />C-x 0：删除当前所在的window；<br /><br />C-x 1：删除当前所在window之外的所有window；<br /><br />C-x 5 2：新建一个frame；<br /><br />from: <a href="http://blog.donews.com/leal/archive/2005/10/18/592725.aspx">http://blog.donews.com/leal/archive/2005/10/18/592725.aspx</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/61000.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-07-31 13:22 <a href="http://www.blogjava.net/weidagang2046/articles/61000.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Debugging with GDB （入门篇）</title><link>http://www.blogjava.net/weidagang2046/articles/60913.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 30 Jul 2006 15:45:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/60913.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/60913.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/60913.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/60913.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/60913.html</trackback:ping><description><![CDATA[
		<dd>Debug 是大家常常用到的东西.不管是自己写程式也好,还是想改改别人写好的东西, 又或者帮人家捉捉虫.总之呢,绝对是个常常用的到的东西.Dos, windows 底下,通常大家都在用 softice. 这里我就不介绍了,因为在前面的 "学习程式"中的"Assembly"里面已经有了很详细的介绍了.这里我来说说 linux 底下的 GDB 吧. 
<p></p></dd>
		<dd>GDB 的全称是 GNU Debuger. 是 linux 底下的一种免费的 debug 程式.随然介面不像 SoftIce 那麽好,但是功能也绝对强大.要使用 gdb 那麽首先,在你 compile 程式的时候, 要加上 -g 的选项. （可以用-g, -g2, -g3具体请看 man gcc）通常如果程式不会很大,在 compile 的时候我都是用 -g3 的,因为如果你用到了 inline 的 function, 用 -g 去 compile 就无法去 debug inline function了.这时候就用到 -g2, -g3了,g後面的数字越大,也就是说可以 debug 的级别越高.最高级别就是 -g3. 
<p></p></dd>
		<dd>既然是入门篇,就从最简单的来做啦.先写个小程式,我们用来学习 gdb. 用你喜爱的 editor 编辑一个叫做 test.c 的文件,内容如下∶ 
<p></p><pre>int main()
{
	int a, b, c;
	a=5;
	b=10;
	b+=a;
	c=b+a;
	return 0;
}

然後用下面的指令去编辑这个程式∶

gcc -Wall -g -o test test.c

这样 gcc 就会 compile 一个叫做 test 的小程式.现在我们来用 gdb 看看这个小程式∶

gdb -q test

(gdb) l			(这里用 l 指令,是 list 的简写）

1       int main()
2       {
3               int a, b, c;
4               a=5;
5               b=10;
6               b+=a;
7               c=b+a;
8               return 0;
9       }

（这时候你就可以看到程式的 source 了）


我们现在下一个 breakpoint,这个 breakpoint 将会在第二行,也就是 2 { 这里.
这样程式运行完 int main()以後,就会停下来.

(gdb) b 2	(b 就是 breakpoint 的简写啦)
Breakpoint 1 at 0x80483a0: file test.c, line 2. 

现在来运行这个程式

(gdb) r		(r是 run 的简写)
Starting program: /home/goldencat/study-area/goldencat/gdb/test 

Breakpoint 1, main () at test.c:2
2       {
程式运行到这里,就停下来了.因为我们在这里设下了 breakpoint.
(gdb)n		(n = next)
main () at test.c:4
4               a=5; (这里就跑到了第四行了)
(gdb) n
5               b=10;
(gdb) n
6               b+=a;
(gdb) n
7               c=b+a;
这时候我们来看看 b 的 value 是多少∶

(gdb) p b	(p是print的简写,这里实际写成print b)
$1 = 15		(这里显示的就是 b 的 value, 以後要看 b, 直接用 p $1 也是一样的)
		(这里的 $1 就是指向 b 的)

(gdb) n
8               return 0;
(gdb) p c
$2 = 20		(这里看到 c 的 value 是 20)
(gdb) c		(c 是 continue 的意思,也就是说执行到程式的结束)
Continuing.

Program exited normally.
(gdb) q		(这就结束 gdb 了 )
</pre></dd>
		<dd>跟 n (next) 不同的,还有一个用法就是 step. step 很有用的就是,当你追到一个call 的时候,（如 my_function(value1)）如果用 next 会只接跑过这个 call,而不会跑到这个 call里面, step 就不同了. step 会跑到这个 call 的里面去,让你能追到 call 里面. 
<p></p></dd>
		<dd>这里在顺便说说如何改变一个 value. 当你下指令 p 的时候,例如你用 p b, 这时候你会看到 b 的 value, 也就是上面的 $1 = 15. 你也同样可以用 p 来改变一个 value, 例如下指令 p b = 100 试试看,这时候你会发现, b 的 value 就变成 100 了∶$1 = 100. 
<p></p></dd>
		<dd>利用display这个命令,你可以在每一次的 next 时,都显示其中一个的 value,看看下面的范例也许容易明白些∶ 
<p></p><pre>[goldencat@goldencat gdb]$ gdb -q test
(gdb) l 
1       int main()
2       {
3               int a, b, c;
4               a=5;
5               b=10;
6               b+=a;
7               c=b+a;
8               return 0;
9       }
(gdb) b 2
Breakpoint 1 at 0x80483a0: file test.c, line 2.
(gdb) r
Starting program: /home/goldencat/study-area/goldencat/gdb/test 

Breakpoint 1, main () at test.c:2
2       {
(gdb) n
main () at test.c:4
4               a=5;
(gdb) display a		(set display on)
1: a = 134517840
(gdb) n
5               b=10;
1: a = 5		(display a)
(gdb) n
6               b+=a;
1: a = 5		(display a)
(gdb) n
7               c=b+a;
1: a = 5		(display a)
(gdb) n
8               return 0;
1: a = 5		(display a)
(gdb) c
Continuing.

Program exited normally.
(gdb) q
</pre></dd>
		<dd>当然你要 display 多少个 value 并没有甚麽限制.你完全可以把 a, b, c全部都display出来. 利用 info 这个指令,你可以看到目前的状况.如∶ info display 就能看到目前的display 的状况∶ 
<p></p><pre>(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  b	（这里的 y 就是说, display b 是 enable 的)

用 info break 就可以看到 breakpoint 的状况∶

(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x080483a0 in main at test.c:2
        breakpoint already hit 1 time
(gdb) 

	利用 disable 和 enable 命令,可以赞时开启和这关闭一些命令.例如∶

(gdb) disable display 1
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   n  b		(这里看到个 n, 也就是说, display b 已经被关闭了)
(gdb) 			


(gdb) disable break 1
(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep n   0x080483a0 in main at test.c:2
        breakpoint already hit 1 time
(gdb) 		(这里看到,breakpoint也被用 disable break 1 给关闭了)
</pre></dd>
		<dd>如果你问我为甚麽要用 1 (disable break/display 1),看看上面的 Num 那几个字就知道了. 这里的 1 就是说关闭第一个 value. 因为当你真正 debug 的是侯,可能有很多的 break,你只要关闭你想要关闭的就好了,看看下面∶ 
<p></p><pre>(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
3:   y  c
2:   y  b
1:   y  a
</pre></dd>
		<dd>这里 display 中有三个 value, 现在我想赞时关闭对 b 的 display,可以从 Num 看出, b 的 Num 是 2,所以我们要用 disable display 2 
<p></p><pre>(gdb) disable display 2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
3:   y  c
2:   n  b		(这里看到, b 已经关闭了)
1:   y  a
</pre></dd>
		<dd>如果你用 disable display 而後面没有任何的 number 的话,那麽就是 disable all 的意思∶ 
<p></p><pre>(gdb) disable display  
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
3:   n  c
2:   n  b
1:   n  a
(gdb) 
</pre></dd>
		<dd>接下来说说 enable 吧, 知道了 disable, enable 就简单多了. enable 就是跟 disable 相反的意思.也就是说重新开启被关闭的东西.用法跟 disable 一样. 
<p></p><pre>(gdb) enable display 2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
3:   n  c
2:   y  b
1:   n  a
(gdb) enable display
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
3:   y  c
2:   y  b
1:   y  a
(gdb) 
</pre></dd>
		<dd>再来讲讲 delete 的用法啦. delete 跟 disable 不太一样,一旦被 delete, 那麽是没有办法用 enable 之类的东西找回来的.假设你 disable 一个 breakpoint,那麽就是说,你赞时不需要用到这个 break point,当你要用到的时候,只要 enable 就好.可是如果你去 delete 一个 breakpoint.就是说你将用远不需要这个 breakpoint 了.如果你下次还需要, 那麽你就给重新用 break 指令去下 breakpoint 了. 
<p></p><pre>(gdb) delete display 1
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
3:   y  c
2:   y  b		(1 消失了)
(gdb) delete display	(全部 delete )
Delete all auto-display expressions? (y or n) y		(要求确定一下)
(gdb) info display
There are no auto-display expressions now.	(全部的 display 都被 delete 了)
(gdb) 
</pre></dd>
		<dd>顺便说说如何去 debug 一个已经在 run 的程式∶<br />利用 attach process-id 和 detach 就可以去 debug 一个已经在 run 的程式了. 
<p></p><pre>先用 ps aux 找出你要 debug 的程式的 process it.

[goldencat@goldencat gdb]$ ps aux | grep ssh
root       600  0.0  0.0  2248    0 ?        SW   11:13   0:00 [sshd]
goldenca  1182  0.0  0.7  2448  188 tty2     S    11:40   0:00 ssh 127.0.0.1
goldenca  2802  0.0  1.9  1904  528 pts/1    S    13:45   0:00 grep ssh

这里我们去 debug ssh 127.0.0.1 这个程式,这这程式的 process id 是 1182

[root@goldencat /root]# gdb -q		进入gdb

(gdb) attach 1182			截入 process 1182 到 gdb 里面
Attaching to Pid 1182
0x401b615e in ?? ()
......
......
......					进行 debug
......
......

(gdb) detach				debug 完毕以後,记得要用 detach 这个命令
Detaching from program: , Pid 1182	这个命令就把刚刚 debug 的那个程式 release
(gdb) q					掉了.
</pre><p></p></dd>
		<dd>好啦,入门篇嘛,就写这麽多了.我写的慢,这些就写了我一个早上啦.不敢说能教了大家甚麽东西,但也算是给没有玩过的人一个入门的概念啦.简单的,常用到的break,print, display,disable,enable,delete,run,next,step,continue好像也都说到了. 如果你有心想学,可以看看 man gdb 和进入 gdb 後,用 help 指令. GDB 里面的 help 是很好用的. 
<p></p></dd>
		<dd>如果你是个 debug 的高手,那麽希望你也能抽点时间,跟大家分享一下你的心得.独乐乐不如众乐乐嘛. ∶） 
<p></p><pre>下面是个如何使用 gdb 中的 help 的范例∶


[goldencat@goldencat gdb]$ gdb -q
(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb) help breakpoints
Making program stop at certain points.

List of commands:

awatch -- Set a watchpoint for an expression
break -- Set breakpoint at specified line or function
catch -- Set catchpoints to catch events
clear -- Clear breakpoint at specified line or function
commands -- Set commands to be executed when a breakpoint is hit
condition -- Specify breakpoint number N to break only if COND is true
delete -- Delete some breakpoints or auto-display expressions
disable -- Disable some breakpoints
enable -- Enable some breakpoints
hbreak -- Set a hardware assisted  breakpoint
ignore -- Set ignore-count of breakpoint number N to COUNT
rbreak -- Set a breakpoint for all functions matching REGEXP
rwatch -- Set a read watchpoint for an expression
tbreak -- Set a temporary breakpoint
tcatch -- Set temporary catchpoints to catch events
thbreak -- Set a temporary hardware assisted breakpoint
txbreak -- Set temporary breakpoint at procedure exit
watch -- Set a watchpoint for an expression
xbreak -- Set breakpoint at procedure exit

Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb) help clear
Clear breakpoint at specified line or function.
Argument may be line number, function name, or "*" and an address.
If line number is specified, all breakpoints in that line are cleared.
If function is specified, breakpoints at beginning of function are cleared.
If an address is specified, breakpoints at that address are cleared.

With no argument, clears all breakpoints in the line that the selected frame
is executing in.

See also the "delete" command which clears breakpoints by number.
(gdb) q<br /><br />from: <a href="http://blog.chinaunix.net/u/9483/showart.php?id=57476">http://blog.chinaunix.net/u/9483/showart.php?id=57476</a><br /></pre></dd>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/60913.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-07-30 23:45 <a href="http://www.blogjava.net/weidagang2046/articles/60913.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下的开发工具Emacs介绍 </title><link>http://www.blogjava.net/weidagang2046/articles/60865.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 30 Jul 2006 07:56:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/60865.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/60865.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/60865.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/60865.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/60865.html</trackback:ping><description><![CDATA[
		<div class="content">
				<p>使用Linux已经有一段时间了，现在Linux已经成为我办公、学习、娱乐不可缺少的平台。Office系列软件，网络工具，开发软件，这些在Windows下标价昂贵的东西，在Linux下不仅可以免费获取，而且还可以浏览源码观其内幕，附加价值是相当可观的。</p>
				<p>作为一名开发人员，选择一个好的IDE是必要的。在Linux环境下，可考虑选择Eclipse。它的界面友好，编辑、调试、代码管理功能兼备。不过本文介绍的是另一款功能卓绝的开发工具：Emacs。</p>
				<p>Eclispe比较适合初级开发人员，而Emacs则比较适合高级开发人员。功能方面两者相当，或者Emacs略胜一筹。相比Eclipse华贵的界面，Emacs更显得简洁高效，性能方面表现更佳。正如一名资深的开发专家所言，当你所写的代码超过3万行的时候，一个清醒的大脑比什么IDE都强。这时候也许随心所欲比美好的外观更可取，这也是国外很多人士钟情于Emacs的理由。</p>
				<p>下面是对Emacs快捷键的总结，希望能帮助大家更快适应Emacs使用。</p>
				<p>
						<strong>文件操作</strong>
				</p>
				<p>C-x C-f 打开文件,出现提示时输入/username@host:filepath可编辑FTP文件<br />C-x C-v 打开一个文件，取代当前缓冲区<br />C-x C-s 保存文件<br />C-x C-w 存为新文件<br />C-x i 插入文件<br />C-x C-q 切换为只读或者读写模式<br />C-x C-c 退出Emacs</p>
				<p>
						<strong>编辑操作</strong>
				</p>
				<p>C-f 前进一个字符<br />C-b 后退一个字符<br />M-f 前进一个字<br />M-b 后退一个字<br />C-a 移到行首<br />C-e 移到行尾<br />M-a 移到句首<br />M-e 移到句尾<br />C-p 后退一行<br />C-n 前进一行<br />M-x goto-line 跳到指定行<br />C-v 向下翻页<br />M-v 向上翻页<br />M-&lt; 缓冲区头部<br />M-&gt; 缓冲区尾部</p>
				<p>C-M-f 向前匹配括号<br />C-M-b 向后匹配括号</p>
				<p>C-l 当前行居中</p>
				<p>M-n or C-u n 重复操作随后的命令n次<br />C-u 重复操作随后的命令4次<br />C-u C-u 重复操作随后的命令8次<br />C-x ESC ESC 执行历史命令记录，M-p选择上一条命令，M-n选择下一条命令</p>
				<p>C-d 删除一个字符<br />M-d 删除一个字<br />C-k 删除一行<br />M-k 删除一句<br />C-w 删除标记区域</p>
				<p>C-y 粘贴删除的内容</p>
				<p>注意：C-y可以粘贴连续C-k删除的内容；先按C-y，然后按M-y可以选择粘贴被删除的内容</p>
				<p>C-@ 标记开始区域<br />C-x h 标记所有文字<br />C-x C-x 交换光标位置和区域标记区开头<br />M-w 复制标记区域</p>
				<p>C-_ or C-x u 撤消操作</p>
				<p>
						<strong>执行SHELL命令</strong>
				</p>
				<p>M-x shell 打开SHELL<br />M-! 执行SHELL命令 (shell-command)<br />M-1 M-! 执行SHELL命令,命令输出插入光标位置,不打开新输出窗口<br />M-| 针对某一特定区域执行命令(shell-command-on-region), 比如 C-x h M-|uuencode</p>
				<p>
						<strong>窗口操作</strong>
				</p>
				<p>C-x 0 关闭本窗口<br />C-x 1 只留下一个窗口<br />C-x 2 垂直均分窗口<br />C-x 3 水平均分窗口<br />C-x o 切换到别的窗口<br />C-x s 保存所有窗口的缓冲<br />C-x b 选择当前窗口的缓冲区<br />C-x ^ 纵向扩大窗口<br />C-x } 横向扩大窗口</p>
				<p>
						<strong>缓冲区列表操作</strong>
				</p>
				<p>C-x C-b 打开缓冲区列表<br />d or k 标记为删除<br />~ 标记为未修改状态<br />% 标记为只读<br />s 保存缓冲<br />u 取消标记<br />x 执行标记的操作</p>
				<p>f 在当前窗口打开该缓冲区<br />o 在其他窗口打开该缓冲区</p>
				<p>
						<strong>目录操作</strong>
				</p>
				<p>C-x d 打开目录模式<br />s 按日期/文件名排序显示<br />v 阅读光标所在的文件<br />q 退出阅读的文件<br />d 标记为删除<br />x 执行标记<br />D 马上删除当前文件<br />C 拷贝当前文件<br />R 重名名当前文件<br />+ 新建文件夹<br />Z 压缩文件<br />! 对光标所在的文件执行SHELL命令<br />g 刷新显示<br />i 在当前缓冲区的末尾插入子目录的内容</p>
				<p>[n]m 标记光标所在的文件，如果指定n，则从光标所在的文件起后n个文件被标记<br />[n]u 取消当前光标标记的文件，n的含义同上<br />t 反向标记文件<br />%-m 正则标记</p>
				<p>q 退出目录模式</p>
				<p>说明：在目录模式中，如果输入!，在命令行中包含*或者?，有特殊的含义。*匹配当前光标所在的文件和所有标记的文件，?分别在每一个标记的文件上执行该命令。</p>
				<p>
						<strong>程序编译</strong>
				</p>
				<p>M-x compile 执行编译操作<br />M-x gdb GDB排错<br />M-x dbx DBX排错<br />M-x xdb XDB排错<br />M-x sdb SDB排错</p>
				<p>
						<strong>搜索模式</strong>
				</p>
				<p>C-s key 向前搜索<br />C-s 查找下一个<br />ENTER 停止搜索<br />C-r key 反向搜索<br />C-s C-w 以光标所在位置的字为关键字搜索<br />C-s C-s 重复上次搜索<br />C-r C-r 重复上次反向搜索<br />C-s ENTER C-w 进入单词搜索模式<br />C-r ENTER C-w 进入反向单词搜索模式<br />M-x replace-string ENTER search-string ENTER 替换<br />M-% search-string ENTER replace-string ENTER 交互替换<br />C-r 在进入查找/替换模式后，该命令进入迭代编辑模式<br />C-M-x 退出迭代编辑模式，返回到查找/替换模式<br />C-M-s 向前正则搜索<br />C-M-r 向后正则搜索<br />C-M-% 正则交互替换</p>
				<p>
						<strong>SHELL模式</strong>
				</p>
				<p>C-c C-c 相当于Bash下的C-c<br />C-c C-z 相当于Bash下的C-z<br />C-c C-d 相当于Bash下的C-d<br />M-p 执行前一条命令<br />C-n 执行下一条命令<br />C-c C-o 删除最后一条命令产生的输出<br />C-c C-r 屏幕滚动到最后一条命令输出的开头<br />C-c C-e 屏幕滚动到最后一套命令输出的结尾<br />C-c C-p 查看前一条命令的输出<br />C-c C-n 查看后一条命令的输出</p>
				<p>
						<strong>打印资料</strong>
				</p>
				<p>M-x print-buffer 先使用pr,然后使用lpr<br />M-x lpr-buffer 直接使用lpr<br />M-x print-region<br />M-x lpr-region</p>
				<p>
						<strong>收发邮件</strong>
				</p>
				<p>M-x mail 发送邮件, C-c C-s 发送,C-c C-c 发送并退出<br />M-x rmail 接受邮件</p>
				<p>
						<strong>参考资料：</strong>
				</p>
				<p>O'Reilly, Emacs 3rd Edition<br /><br />from: <a href="http://www.devfront.com:8080/?q=node/115">http://www.devfront.com:8080/?q=node/115</a></p>
		</div>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/60865.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-07-30 15:56 <a href="http://www.blogjava.net/weidagang2046/articles/60865.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Using gdb under gnu Emacs</title><link>http://www.blogjava.net/weidagang2046/articles/60685.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sat, 29 Jul 2006 00:51:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/60685.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/60685.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/60685.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/60685.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/60685.html</trackback:ping><description><![CDATA[
		<p>
				<font face="Verdana" size="2">A special interface allows you to use gnu Emacs to view (and edit) the source files for the program you are debugging with gdb. </font>
		</p>
		<p>
				<font face="Verdana" size="2">To use this interface, use the command <tt class="USERINPUT"><b>M-x gdb</b></tt> in Emacs. Give the executable file you want to debug as an argument. This command starts gdb as a subprocess of Emacs, with input and output through a newly created Emacs buffer. </font>
		</p>
		<p>
				<font face="Verdana" size="2">Using gdb under Emacs is just like using gdb normally except for two things: </font>
		</p>
		<p>
				<font face="Verdana" size="2">
				</font>
		</p>
		<ul>
				<li style="LIST-STYLE-TYPE: disc">
						<p>
								<font face="Verdana" size="2">All "terminal" input and output goes through the Emacs buffer. </font>
						</p>
				</li>
		</ul>
		<p>
				<font face="Verdana" size="2">This applies both to gdb commands and their output, and to the input and output done by the program you are debugging. </font>
		</p>
		<p>
				<font face="Verdana" size="2">This is useful because it means that you can copy the text of previous commands and input them again; you can even use parts of the output in this way. </font>
		</p>
		<p>
				<font face="Verdana" size="2">All the facilities of Emacs' Shell mode are available for interacting with your program. In particular, you can send signals the usual way--for example, <tt class="USERINPUT"><b>C-c C-c</b></tt> for an interrupt, <tt class="USERINPUT"><b>C-c C-z</b></tt> for a stop. </font>
		</p>
		<p>
				<font face="Verdana" size="2">
				</font>
		</p>
		<ul>
				<li style="LIST-STYLE-TYPE: disc">
						<p>
								<font face="Verdana" size="2">gdb displays source code through Emacs. </font>
						</p>
				</li>
		</ul>
		<p>
				<font face="Verdana" size="2">Each time gdb displays a stack frame, Emacs automatically finds the source file for that frame and puts an arrow (<tt class="COMMAND">=&gt;</tt>) at the left margin of the current line. Emacs uses a separate buffer for source display, and splits the screen to show both your gdb session and the source. </font>
		</p>
		<p>
				<font face="Verdana" size="2">Explicit gdb <tt class="COMMAND">list</tt> or search commands still produce output as usual, but you probably have no reason to use them from Emacs. </font>
		</p>
		<a name="AEN12915">
		</a>
		<blockquote class="BLOCKQUOTE">
				<p>
						<font face="Verdana">
								<font size="2">
										<i class="EMPHASIS">Warning:</i> If the directory where your program resides is not your current directory, it can be easy to confuse Emacs about the location of the source files, in which case the auxiliary display buffer does not appear to show your source. gdb can find programs by searching your environment's <tt class="COMMAND">PATH</tt> variable, so the gdb input and output session proceeds normally; but Emacs does not get enough information back from gdb to locate the source files in this situation. To avoid this problem, either start gdb mode from the directory where your program resides, or specify an absolute file name when prompted for the <tt class="USERINPUT"><b>M-x gdb</b></tt> argument. </font>
						</font>
				</p>
				<p>
						<font face="Verdana" size="2">A similar confusion can result if you use the gdb <tt class="COMMAND">file</tt> command to switch to debugging a program in some other location, from an existing gdb buffer in Emacs. </font>
				</p>
		</blockquote>
		<p>
				<font face="Verdana" size="2">By default, <tt class="USERINPUT"><b>M-x gdb</b></tt> calls the program called <tt class="COMMAND">gdb</tt>. If you need to call gdb by a different name (for example, if you keep several configurations around, with different names) you can set the Emacs variable <tt class="COMMAND">gdb-command-name</tt>; for example, </font>
		</p>
		<p>
		</p>
		<table class="SCREEN" width="100%" bgcolor="#dcdcdc">
				<tbody>
						<tr>
								<td>
										<pre class="SCREEN">
												<font face="Verdana" size="2">(setq gdb-command-name "mygdb")</font>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font face="Verdana" size="2">(preceded by <tt class="USERINPUT"><b>M-:</b></tt> or <tt class="USERINPUT"><b>ESC :</b></tt>, or typed in the <tt class="COMMAND">*scratch*</tt> buffer, or in your <tt class="COMMAND">.emacs</tt> file) makes Emacs call the program named "<tt class="COMMAND">mygdb</tt>" instead. </font>
		</p>
		<p>
				<font face="Verdana" size="2">In the gdb I/O buffer, you can use these special Emacs commands in addition to the standard Shell mode commands: </font>
		</p>
		<p>
				<font face="Verdana" size="2">
				</font>
		</p>
		<div class="VARIABLELIST">
				<dl>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>C-h m</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Describe the features of Emacs' gdb Mode. </font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>M-s</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Execute to another source line, like the gdb <tt class="COMMAND">step</tt> command; also update the display window to show the current file and location. </font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>M-n</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Execute to next source line in this function, skipping all function calls, like the gdb <tt class="COMMAND">next</tt> command. Then update the display window to show the current file and location. </font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>M-i</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Execute one instruction, like the gdb <tt class="COMMAND">stepi</tt> command; update display window accordingly. </font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>M-x gdb-nexti</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Execute to next instruction, using the gdb <tt class="COMMAND">nexti</tt> command; update display window accordingly. </font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>C-c C-f</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Execute until exit from the selected stack frame, like the gdb <tt class="COMMAND">finish</tt> command. </font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>M-c</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Continue execution of your program, like the gdb <tt class="COMMAND">continue</tt> command. </font>
								</p>
								<p>
										<font face="Verdana">
												<font size="2">
														<i class="EMPHASIS">Warning:</i> In Emacs v19, this command is <tt class="USERINPUT"><b>C-c C-p</b></tt>. </font>
										</font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>M-u</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Go up the number of frames indicated by the numeric argument (), like the gdb <tt class="COMMAND">up</tt> command. </font>
								</p>
								<p>
										<font face="Verdana">
												<font size="2">
														<i class="EMPHASIS">Warning:</i> In Emacs v19, this command is <tt class="USERINPUT"><b>C-c C-u</b></tt>. </font>
										</font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>M-d</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Go down the number of frames indicated by the numeric argument, like the gdb <tt class="COMMAND">down</tt> command. </font>
								</p>
								<p>
										<font face="Verdana">
												<font size="2">
														<i class="EMPHASIS">Warning:</i> In Emacs v19, this command is <tt class="USERINPUT"><b>C-c C-d</b></tt>. </font>
										</font>
								</p>
						</dd>
						<dt>
								<font face="Verdana">
										<font size="2">
												<tt class="USERINPUT">
														<b>C-x &amp;</b>
												</tt>
										</font>
								</font>
						</dt>
						<dd>
								<p>
										<font face="Verdana" size="2">Read the number where the cursor is positioned, and insert it at the end of the gdb I/O buffer. For example, if you wish to disassemble code around an address that was displayed earlier, type <tt class="USERINPUT"><b>disassemble</b></tt>; then move the cursor to the address display, and pick up the argument for <tt class="COMMAND">disassemble</tt> by typing <tt class="USERINPUT"><b>C-x &amp;</b></tt>. </font>
								</p>
								<p>
										<font face="Verdana" size="2">You can customize this further by defining elements of the list <tt class="COMMAND">gdb-print-command</tt>; once it is defined, you can format or otherwise process numbers picked up by <tt class="USERINPUT"><b>C-x &amp;</b></tt> before they are inserted. A numeric argument to <tt class="USERINPUT"><b>C-x &amp;</b></tt> indicates that you wish special formatting, and also acts as an index to pick an element of the list. If the list element is a string, the number to be inserted is formatted using the Emacs function <tt class="COMMAND">format</tt>; otherwise the number is passed as an argument to the corresponding list element. </font>
								</p>
						</dd>
				</dl>
		</div>
		<p>
				<font face="Verdana" size="2">In any source file, the Emacs command <tt class="USERINPUT"><b>C-x SPC</b></tt> (<tt class="COMMAND">gdb-break</tt>) tells gdb to set a breakpoint on the source line point is on. </font>
		</p>
		<p>
				<font face="Verdana" size="2">If you accidentally delete the source-display buffer, an easy way to get it back is to type the command <tt class="COMMAND">f</tt> in the gdb buffer, to request a frame display; when you run under Emacs, this recreates the source buffer if necessary to show you the context of the current frame. </font>
		</p>
		<p>
				<font face="Verdana" size="2">The source files displayed in Emacs are in ordinary Emacs buffers which are visiting the source files in the usual way. You can edit the files with these buffers if you wish; but keep in mind that gdb communicates with Emacs in terms of line numbers. If you add or delete lines from the text, the line numbers that gdb knows cease to correspond properly with the code. <br /><br />from: <a href="http://www.redhat.com/docs/manuals/enterprise/RHEL-3-Manual/gdb/emacs.html">http://www.redhat.com/docs/manuals/enterprise/RHEL-3-Manual/gdb/emacs.html</a></font>
		</p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/60685.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-07-29 08:51 <a href="http://www.blogjava.net/weidagang2046/articles/60685.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在Emacs下用C/C++编程</title><link>http://www.blogjava.net/weidagang2046/articles/60668.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 28 Jul 2006 15:23:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/60668.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/60668.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/60668.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/60668.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/60668.html</trackback:ping><description><![CDATA[
		<h2>
				<font face="Verdana" size="2">版权说明和参考文献</font>
		</h2>
		<p>
				<font face="Verdana" size="2">按照惯例，我写的文章在最开始处放版权说明和参考文献。 </font>
		</p>
		<h3>
				<font face="Verdana" size="2">参考文献：</font>
		</h3>
		<ul>
				<li>
						<font face="Verdana" size="2">hhuu @ newsmth 的《Emacs的日常生活》 </font>
				</li>
				<li>
						<font face="Verdana" size="2">emacs 的文档 </font>
				</li>
				<li>
						<font face="Verdana" size="2">emacs 相关插件的文档 </font>
				</li>
		</ul>
		<h3>
				<font face="Verdana" size="2">版权说明：</font>
		</h3>
		<p>
				<font face="Verdana" size="2">转载请注明转自</font>
				<a href="http://www.caole.net/">
						<font face="Verdana" size="2">曹乐的个人主页</font>
				</a>
				<font face="Verdana" size="2"> www.caole.net，请保证全文转载，尤其不可省略这一部分。 </font>
		</p>
		<h2>
				<font face="Verdana" size="2">序</font>
		</h2>
		<p>
				<font face="Verdana" size="2">用emacs写程序也有5个年头了，深切地体会到Emacs的强大。程序员有三种，一种是用vi的，一种是用emacs的，还有一种是其他。或许有些夸张，但也颇能体现出emacs在程序员中的地位。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">emacs最大的问题在于入门门槛较高。它看起来和多数人想象中的IDE相差甚远，很多人看到emacs的第一眼就觉得它是个记事本（还是个非常难用的记事本），稍微好些的往往觉得emacs也就是个ultraEditor而已，真是暴殄天物了。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">我是个懒人，不喜欢记太多的快捷键，相信很多人和我一样。所以从我后面的叙述可以看出来，除了常用的命令都是快捷键外，其他命令多数都是用M-x执行或者用鼠标点菜单。这仅仅是个人风格问题，先说明一下。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">我的基本编程环境是： </font>
		</p>
		<ul>
				<li>
						<font face="Verdana" size="2">Debian GNU/Linux sid 操作系统 </font>
				</li>
				<li>
						<font face="Verdana" size="2">Gnome 2.10.0 桌面环境 </font>
				</li>
				<li>
						<font face="Verdana" size="2">GUN Emacs 23.0.0.1 for debian </font>
				</li>
				<li>
						<font face="Verdana" size="2">使用 Gnu tool chains(gcc,make,gdb等等) </font>
				</li>
		</ul>
		<p>
				<font face="Verdana" size="2">后面的叙述都基于上述环境。另外，本文主要针对C/C++程序开发，对其他语言有些也适用，从难度上说，本文主要针对入门者。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">本文肯定会有很多错误，</font>
				<a href="mailto:caole82%40gmail.com">
						<font face="Verdana" size="2">请指正</font>
				</a>
				<font face="Verdana" size="2">，谢谢。 </font>
		</p>
		<h2>
				<font face="Verdana" size="2">基本流程</font>
		</h2>
		<p>
				<font face="Verdana" size="2">写C++程序基本上是这么几个步骤： </font>
		</p>
		<ol>
				<li>
						<font face="Verdana" size="2">编辑代码 </font>
				</li>
				<li>
						<font face="Verdana" size="2">编写Makefile </font>
				</li>
				<li>
						<font face="Verdana" size="2">编译代码，修改编译错误 </font>
				</li>
				<li>
						<font face="Verdana" size="2">调试代码，修改逻辑错误 </font>
				</li>
		</ol>
		<p>
				<font face="Verdana" size="2">当然，往往还需要阅读别人的代码。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">根据上述步骤，本文主要针对以下几个方面： </font>
		</p>
		<ul>
				<li>
						<font face="Verdana" size="2">配置Emacs，建立便利的代码编辑环境和Makefile编写环境。 </font>
				</li>
				<li>
						<font face="Verdana" size="2">在Emacs中编译代码，并修改编译错误。 </font>
				</li>
				<li>
						<font face="Verdana" size="2">在Emacs中配合GDB调试程序。 </font>
				</li>
				<li>
						<font face="Verdana" size="2">利用cscope和ecb在emacs中阅读代码。 </font>
				</li>
		</ul>
		<h2>
				<font face="Verdana" size="2">基本环境设置</font>
		</h2>
		<h3>
				<font face="Verdana" size="2">编辑环境配置</font>
		</h3>
		<p>
				<font face="Verdana" size="2">要写C++程序，当然要用到cc-mode插件。CC-Mode原本是支持C语言的，但现在也能支持很多语言，比如C++，Java，Objective-C，CORBA，AWK，Pike等等。CC-Mode是gnu-emacs的标准插件。如果您要求不高，那么默认的配置或许就能满足。CC-Mode的各种行为都可以自由地定制，您可以参考这里的文档：</font>
				<a href="http://cc-mode.sourceforge.net/html-manual/index.html">
						<font face="Verdana" size="2">CC-Mode参考文档</font>
				</a>
		</p>
		<p>
				<font face="Verdana" size="2">这里是我的.emacs文件中关于CC-Mode配置的部分，仅供参考： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">;;;; CC-mode配置  http://cc-mode.sourceforge.net/
(require 'cc-mode)
(c-set-offset 'inline-open 0)
(c-set-offset 'friend '-)
(c-set-offset 'substatement-open 0)
</font>
		</pre>
		<pre class="example">
				<font face="Verdana" size="2">;;;;我的C/C++语言编辑策略

(defun my-c-mode-common-hook()
  (setq tab-width 4 indent-tabs-mode nil)
  ;;; hungry-delete and auto-newline
  (c-toggle-auto-hungry-state 1)
  ;;按键定义
  (define-key c-mode-base-map [(control \`)] 'hs-toggle-hiding)
  (define-key c-mode-base-map [(return)] 'newline-and-indent)
  (define-key c-mode-base-map [(f7)] 'compile)
  (define-key c-mode-base-map [(meta \`)] 'c-indent-command)
;;  (define-key c-mode-base-map [(tab)] 'hippie-expand)
  (define-key c-mode-base-map [(tab)] 'my-indent-or-complete)
  (define-key c-mode-base-map [(meta ?/)] 'semantic-ia-complete-symbol-menu)
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">注意一下，上面最后两行是代码自动补齐的快捷键。后面我会提到代码自动补齐。 </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">  ;;预处理设置
  (setq c-macro-shrink-window-flag t)
  (setq c-macro-preprocessor "cpp")
  (setq c-macro-cppflags " ")
  (setq c-macro-prompt-flag t)
  (setq hs-minor-mode t)
  (setq abbrev-mode t)
)
(add-hook 'c-mode-common-hook 'my-c-mode-common-hook)

;;;;我的C++语言编辑策略
(defun my-c++-mode-hook()
  (setq tab-width 4 indent-tabs-mode nil)
  (c-set-style "stroustrup")
;;  (define-key c++-mode-map [f3] 'replace-regexp)
)
</font>
		</pre>
		<h3>
				<font face="Verdana" size="2">自动补齐</font>
		</h3>
		<p>
				<font face="Verdana" size="2">自动补齐通常用的都是hippie-expand，我也用了很长时间。不过有时候会觉得这个自动补齐“傻”了一点，常会补齐出一些毫不相干的东西，因为hippie-expand是根据你敲过的词和kill-ring等进行判断的，并不对程序语法进行分析。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">所以你还需要安装一个代码分析工具，然后把它加进hippie-expand的扩展策略里去。我们可以用semantic。实际上，hippie-expand＋semantic是我所发现的最好的选择了，如果您有更好的，请您也告诉我一声:) </font>
		</p>
		<p>
				<font face="Verdana" size="2">Semantic是</font>
				<a href="http://cedet.sourceforge.net/">
						<font face="Verdana" size="2">CEDET</font>
				</a>
				<font face="Verdana" size="2">中的一个工具，CEDET是Collection of Emacs Development Environment Tools的缩写，它包含了好几个工具，都挺不错的。可惜我只会用其中两个。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">您可以在.emacs中对Semantic进行配置，下面是我的.emacs相关的配置，仅供参考： </font>
		</p>
		<p>
				<font face="Verdana" size="2">导入cedet： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">(load-file "~/lib/emacs-lisp/cedet-1.0pre3/common/cedet.el")
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">配置Semantic的检索范围: </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">(setq semanticdb-project-roots 
	  (list
        (expand-file-name "/")))
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">自定义自动补齐命令，这部分是抄hhuu的，如果在单词中间就补齐，否则就是tab。 </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">(defun my-indent-or-complete ()
   (interactive)
   (if (looking-at "\\&gt;")
 	  (hippie-expand nil)
 	  (indent-for-tab-command))
 )

(global-set-key [(control tab)] 'my-indent-or-complete)
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">hippie的自动补齐策略，优先调用了senator的分析结果： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">(autoload 'senator-try-expand-semantic "senator")

(setq hippie-expand-try-functions-list
 	  '(
		senator-try-expand-semantic
		try-expand-dabbrev
		try-expand-dabbrev-visible
		try-expand-dabbrev-all-buffers
		try-expand-dabbrev-from-kill
		try-expand-list
		try-expand-list-all-buffers
		try-expand-line
        try-expand-line-all-buffers
        try-complete-file-name-partially
        try-complete-file-name
        try-expand-whole-kill
        )
)
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">注意一下我前面CC-Mode配置中有这么两行： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">  (define-key c-mode-base-map [(tab)] 'my-indent-or-complete)
  (define-key c-mode-base-map [(meta ?/)] 'semantic-ia-complete-symbol-menu)
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">这样，我们在CC-Mode中就可以调用自定义的hippie补全了，快捷键是Tab。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">另外，我还把快捷键“Alt + / ”绑定到了semantic-ia-complete-symbol-menu命令上，这是semantic的命令，它会根据分析结果弹出补齐的菜单，效果如图显示： </font>
		</p>
		<p>
				<a href="http://www.caole.net/diary/pic/semantic-ia.jpg">
						<font face="Verdana" size="2">
								<img src="http://www.caole.net/diary/pic/semantic-ia.jpg" width="400" align="center" />
						</font>
				</a>
		</p>
		<p>
				<font face="Verdana" size="2">CEDET中还有一个不错的工具是speedbar，你可以用它在多个文件中快速切换。在我的.emacs配置文件里，我把speedbar关联到了F5上： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">(global-set-key [(f5)] 'speedbar)
</font>
		</pre>
		<font face="Verdana" size="2">这样用F5就可以调出speedbar，效果如下： </font>
		<p>
		</p>
		<p>
				<a href="http://www.caole.net/diary/pic/speedbar.jpg">
						<font face="Verdana" size="2">
								<img src="http://www.caole.net/diary/pic/speedbar.jpg" width="400" align="center" />
						</font>
				</a>
		</p>
		<p>
				<font face="Verdana" size="2">不过说实话，我自己很少用到speedbar，我通常都是用dired配合bookmark使用:) </font>
		</p>
		<h2>
				<font face="Verdana" size="2">编译和调试程序</font>
		</h2>
		<p>
				<font face="Verdana" size="2">按上面的配置，写完程序和Makefile文件后，在Emacs源代码窗口中按F7就可以进行编译。因为在my-c-mode-common-hook()函数里，有这么一行： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2"> (define-key c-mode-base-map [(f7)] 'compile)
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">默认情况下，emacs的compile命令是调用make -k，我把它改成了make。你也可以把它改成其他的，比如gcc之类的。改下面的“make”就行了。 </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">'(compile-command "make")
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">Emacs会划分一个窗格显示编译的消息，在编译结束后，emacs会自动将编译器的输出和程序关联起来，告诉你第几行的程序有问题。直接在出错的行号上按Enter，就可以跳转到相应文件的相应行。其实我通常都是用鼠标中键去点出错行号:) </font>
		</p>
		<p>
				<font face="Verdana" size="2">搞定了编译错误后，接着要和逻辑错误斗争了。其实对简单的程序来说，把中间结果打印到终端是最简单好用的调试办法:)不过稍微复杂点的程序就会晕菜了，这时我们就需要拿gdb跟踪程序流程了。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">你用下面的命令就可以启动gdb了。 </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">M-x gdb
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">通常我喜欢进入gdb-many-windows模式，这样就会把一个Frame划分为5个窗格，同时显示：gdb命令窗口，当前局部变量，程序文本，调用栈和断点。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">gdb的命令就不在这里说了，它的文档几乎到处都是。emacs把gdb的命令和快捷键做了绑定，对于常用的命令，还是输入快捷键比较方便。比如，C-c C-n是Next line，C-c C-s是step in，其实用的最多的快捷键也就是这两个。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">下面是我的gdb效果图： </font>
		</p>
		<p>
				<a href="http://www.caole.net/diary/pic/gdb.jpg">
						<font face="Verdana" size="2">
								<img src="http://www.caole.net/diary/pic/gdb.jpg" width="400" align="center" />
						</font>
				</a>
		</p>
		<h2>
				<font face="Verdana" size="2">阅读代码</font>
		</h2>
		<p>
				<font face="Verdana" size="2">在emacs下读代码通常有三种工具，最简单的是etags，最复杂的是ecb（emacs code browser），位于中间的是cscope。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">etags和ctags一样，只不过前者是用于emacs的，后者是用于vi的。我个人觉得etags功能稍稍显得不够用一点，当然，也可能是我用的不好:) 欢迎大牛指导。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">使用tags之前要先对源代码分析建立tags文件，在代码所在目录中运行：etags -R 即可。 </font>
		</p>
		<p>
				<font face="Verdana" size="2">我常用的就这几个命令和快捷键： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">M-x visit-tags-table &lt;RET&gt; FILE &lt;RET&gt;   选择tags文件
M-. [TAG] &lt;RET&gt;                         访问标签
M-*                                     返回
C-u M-.                                 寻找标签的下一个定义
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">ecb据说功能强大，但是太复杂了，我懒得折腾它。谁搞定了教教我吧:) 下面是一张ecb的效果图。 </font>
		</p>
		<p>
				<a href="http://www.caole.net/diary/pic/ecb.jpg">
						<font face="Verdana" size="2">
								<img src="http://www.caole.net/diary/pic/ecb.jpg" width="400" align="center" />
						</font>
				</a>
		</p>
		<p>
				<font face="Verdana" size="2">cscope是我感觉比较合适的一个工具。它其实是一个独立的软件，完全可以脱离vi和emacs使用。但是结合emacs的强大功能，cscope就显得更加方便了。GNU Emacs默认自带cscope的支持。在使用之前，cscope也需要对代码进行索引。在emacs中可以这样做： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">C-c s a             设定初始化的目录，一般是你代码的根目录
C-s s I             对目录中的相关文件建立列表并进行索引
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">建完索引之后，你就可以用cscope在代码里游荡了。常用的一些命令如下： </font>
		</p>
		<pre class="example">
				<font face="Verdana" size="2">C-c s s             序找符号
C-c s g             寻找全局的定义
C-c s c             看看指定函数被哪些函数所调用
C-c s C             看看指定函数调用了哪些函数
C-c s e             寻找正则表达式
C-c s f             寻找文件
C-c s i             看看指定的文件被哪些文件include
</font>
		</pre>
		<p>
				<font face="Verdana" size="2">上面这些快捷键其实我自己也常常记不全，没关系，抬头看看上面的菜单栏，有一栏就是Cscope，这些命令里头都有:) </font>
		</p>
		<p>
				<font face="Verdana" size="2">贴一个cscope的效果图吧: </font>
		</p>
		<p>
				<a href="http://www.caole.net/diary/pic/cscope.jpg">
						<font face="Verdana" size="2">
								<img src="http://www.caole.net/diary/pic/cscope.jpg" width="400" align="center" />
						</font>
				</a>
		</p>
		<p>
				<font face="Verdana" size="2">写完了。希望这篇文章对您能有一些用处。有问题或建议可以和</font>
				<a href="mailto:caole82%40gmail.com">
						<font face="Verdana" size="2">我</font>
				</a>
				<font face="Verdana" size="2">联系。 </font>
		</p>
		<!-- Page published by Emacs Wiki ends here -->
<img src ="http://www.blogjava.net/weidagang2046/aggbug/60668.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-07-28 23:23 <a href="http://www.blogjava.net/weidagang2046/articles/60668.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Emacs常用命令速查</title><link>http://www.blogjava.net/weidagang2046/articles/60664.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 28 Jul 2006 14:52:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/60664.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/60664.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/60664.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/60664.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/60664.html</trackback:ping><description><![CDATA[
		<font face="Verdana" size="2">Emacs常用命令速查 </font>
		<p>
				<font size="2">
						<font face="Verdana">现在我已经能够熟练使用这些命令了,基本上可以算一个初段的Emacser了,哈哈,总结一下,把这些命令打印出来贴在</font>
						<a class="wordstyle" href="http://www.newasp.cn/" target="_blank">
								<font face="Verdana">电脑</font>
						</a>
						<font face="Verdana">上,不记得了再查查,从今以后尽量做到写代码和文档都用Emacs来完成. <br />1)与文件操作有关的命令 <br />C-x C-f 查找文件并且在新缓冲区中打开 <br />C-x C-v 读入另一个文件替换掉用C-x C-f打开的文件 <br />C-x i 把文件插入到光标的当前位置 <br />C-x C-s 保存文件 <br />C-x C-w 把缓冲区内容写入一个文件 <br />C-x C-c 退出Emacs </font>
				</font>
		</p>
		<p>
				<font face="Verdana" size="2">2)与光标移动操作有关的命令 <br />C-f 光标前移一个字符(右) <br />C-b 光标后移一个字符(左) <br />C-p 光标前移一行(上) <br />C-n 光标后移一行(下) <br />M-f 前移一个单词 <br />M-b 后移一个单词 <br />C-a 移动到行首 <br />C-e 移动到行尾 <br />M-e 前移一个句子 <br />M-a 后移一个句子 <br />M-} 前移一个段落 <br />M-{ 后移一个段落 <br />C-v 屏幕上卷一屏 <br />M-v 屏幕下卷一屏 <br />C-x ] 前移一页 <br />C-x [ 后移一页 <br />M-&lt; 前移到文件头 <br />M-&gt; 后移到文件尾 <br />C-l 重新绘制屏幕，当前行放在画面中心 <br />M-n 或者 C-u n 重复执行n次后续命令 <br />按下M-x后在辅助输入区中输入"goto-line"跳到指定的行，输入"goto-char"跳到指定的字符 </font>
		</p>
		<p>
				<font face="Verdana" size="2">3)与文件删除操作有关的命令 <br />C-d 删除光标位置上的字符 <br />DEL 删除光标前面的字符 <br />M-d 删除光标后面的单词 <br />M-DEL 删除光标前面的单词 <br />C-k 从光标位置删除到行尾 <br />M-k 删除光标后面的句子 <br />C-x DEL 删除光标前面的句子 <br />C-y 恢复被删除的文本或者粘贴最近删除或复制的文本 <br />C-w 删除文件块 <br />按下M-x后在辅助输入区中输入"kill-paragraph"删除光标后面的段落，按下"backward-kill-paragraph"删除光标前面的段落 </font>
		</p>
		<p>
				<font face="Verdana" size="2">4)与文本块操作有关的命令 <br />C-@ 标记文本块的开始(或结束)位置 <br />C-x C-x 互换插入点和文本标记的位置 <br />C-w 或 SHIFT-DEL 删除文本块 <br />M-w 复制文本块 <br />M-h 标记段落 <br />C-x C-p 标记页面 <br />C-x h 标记整个缓冲区 </font>
		</p>
		<p>
				<font face="Verdana" size="2">5)与位置交换操作有关的命令 <br />C-t 交换两个字符的位置 <br />M-t 交换两个单词的位置 <br />C-x C-t 交换两个文本行的位置 <br />按下M-x后在辅助输入区中输入"transpose-sentences"交换两个句子的位置，按下"transpose-paragraph"交换两个段落的位置 </font>
		</p>
		<p>
				<font face="Verdana" size="2">6)与改变字母大小写操作有关的命令 <br />M-c 单词首字母改为大写 <br />M-u 单词的字母全部改为大写 <br />M-l 单词的字母全部改为小写 </font>
		</p>
		<p>
				<font face="Verdana" size="2">7)与查找操作相关的命令 <br />C-s 向前递增查找 <br />C-r 向后递增查找 <br />C-s C-w 开始递增查找，把光标位置的单词做查找字符串 <br />C-s C-y 开始递增查找，把光标位置到行尾之间的文本做查找字符串 <br />C-s return searchstring return 向前开始非递增查找操作 <br />C-r return searchstring return 向后开始非递增查找操作 <br />C-s return C-w 向前开始单词查找(不受换行符、空格、标点符号影响) <br />C-r return C-w 向后开始单词查找(不受换行符、空格、标点符号影响) </font>
		</p>
		<p>
				<font face="Verdana" size="2">8)与使用编辑缓冲区和窗口有关的命令 <br />C-x b 如果输入一个新的文件名则新建一个文件并且编辑,否则打开该文件 <br />C-x s 保存全部缓冲区 <br />C-x b 删除缓冲区 <br />M-x rename-buffer 重命名当前缓冲区 <br />C-x C-q 把当前编辑缓冲区设置为只读属性 <br />C-x 0 删除当前所在的窗口 <br />C-x 1 当前缓冲区满屏显示 <br />C-x 2 创建上下排列的窗口 <br />C-x 3 创建左右排列的窗口 <br />C-x o 在窗口之间移动</font>
		</p>
		<p>
				<font face="Verdana">from: </font>
				<a href="http://www.81819.com/article/131/Linux/2006/2006050114606.html">
						<font face="Verdana">http://www.81819.com/article/131/Linux/2006/2006050114606.html</font>
				</a>
		</p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/60664.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-07-28 22:52 <a href="http://www.blogjava.net/weidagang2046/articles/60664.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Unix/Linux文件比较命令: comm命令, Diff命令</title><link>http://www.blogjava.net/weidagang2046/articles/33884.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 06 Mar 2006 08:53:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/33884.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/33884.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/33884.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/33884.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/33884.html</trackback:ping><description><![CDATA[<H3 class=title>Comm命令 </H3>
<P>如果想对两个有序的文件进行比较，可以使用comm命令。 </P>
<P>语法：comm [- 123 ] file1 file2 </P><A name=more></A>
<P>说明：该命令是对两个已经排好序的文件进行比较。其中file1和file2是已排序的文件。comm读取这两个文件，然后生成三列输出：仅在file1中出现的行；仅在file2中出现的行；在两个文件中都存在的行。如果文件名用“- ”，则表示从标准输入读取。 </P>
<P>选项1、2或3抑制相应的列显示。例如<BR>comm - 12就只显示在两个文件中都存在的行；<BR>comm - 23只显示在第一个文件中出现而未在第二个文件中出现的行；<BR>comm - 123则什么也不显示。 </P>
<P>例如：假设要对文件myfile1和myfile2进行比较 </P>
<P>$ cat myfile1 </P>
<P>main（ ） </P>
<P>{ </P>
<P>float a,b, i, j ,z ; </P>
<P>a=i=10 ; b=j=5 ; </P>
<P>z= i + j ; </P>
<P>printf（“z=%d\\\\n”,z） ; </P>
<P>} </P>
<P>$ cat myfile2 </P>
<P>#include </P>
<P>main（ ） </P>
<P>{ </P>
<P>float i, j ,z ; </P>
<P>i=10 ; j=5 ; </P>
<P>z= i + j ; </P>
<P>printf（“z=%f\\\\n”,z） ; </P>
<P>} </P>
<P>$ comm - 12 myfile1 myfile2 </P>
<P>main（ ） </P>
<P>{ </P>
<P>z= i + j ; </P>
<P>} </P>
<P>就只显示文件myfile1和myfile2中共有的行。 </P>
<P>----------------------------------------------------<BR><STRONG>Diff命令</STRONG> </P>
<P>该命令的功能为逐行比较两个文本文件，列出其不同之处。它比comm命令完成更复杂的检查。它对给出的文件进行系统的检查，并显示出两个文件中所有不同的行，不要求事先对文件进行排序。 </P>
<P>语法：diff [选项] file1 file2 </P>
<P>说明：该命令告诉用户，为了使两个文件file1和file2一致，需要修改它们的哪些行。如果用“- ”表示file1或fiie2，则表示标准输入。如果file1或file2是目录，那么diff将使用该目录中的同名文件进行比较。例如： </P>
<P>diff /usr/xu mine </P>
<P>把目录/usr/xu 中名为mine的文件与当前目录中的mine文件进行比较。 </P>
<P>通常输出由下述形式的行组成： </P>
<P>n1 a n3，n4 </P>
<P>n1，n2 d n3 </P>
<P>n1，n2 c n3，n4 </P>
<P>这些行类似ed命令把filel转换成file2。字母（a、d和c）之前的行号（n1，n2）是针对file1的，其后面的行号（n3，n4）是针对file2的。字母a、d和c分别表示附加、删除和修改操作。 </P>
<P>在上述形式的每一行的后面跟随受到影响的若干行，以“＜”打头的行属于第一个文件，以“＞”打头的行属于第二个文件。 </P>
<P>diff能区别块和字符设备文件以及FIFO（管道文件），不会把它们与普通文件进行比较。 </P>
<P>如果file1和file2都是目录，则diff会产生很多信息。如果一个目录中只有一个文件，则产生一条信息，指出该目录路径名和其中的文件名。 </P>
<P>diff各选项的含义如下： </P>
<P>- b 忽略行尾的空格，而字符串中的一个或多个空格符都视为相等。如How are you与How are you被视为相同的字符串。 </P>
<P>- c 采用上下文输出格式（提供三行上下文）。 </P>
<P>- C n 采用上下文输出格式（提供n行上下文）。 </P>
<P>- e 产生一个合法的ed脚本作为输出。 </P>
<P>- r 当file1和file2是目录时，递归作用到各文件和目录上。 </P>
<P>例如，文件ml.c的内容为（左边行号是有意加上的，以便前后对照）： </P>
<P>1 main（ ） </P>
<P>{ </P>
<P>printf（“Hello！＼n”）； </P>
<P>} </P>
<P>5 </P>
<P>文件m2.c的内容为： </P>
<P>1 main（） </P>
<P>2 { </P>
<P>3 int n , m ; </P>
<P>4 n＝ 10 ; </P>
<P>5 printf （ “ % d \\\\ n ” , m = n * 10）; </P>
<P>6 } </P>
<P>输入命令： </P>
<P>$ diff m1.c m2.c </P>
<P>屏幕上显示： </P>
<P>3，5 c 3，6 </P>
<P>printf（“Hello！＼n”）； </P>
<P>} </P>
<P>＜5 </P>
<P>＞3 int n，m； </P>
<P>＞4 n＝10 ; </P>
<P>＞5 printf （ “ % d \\\\ n ” , m = n * 10）; </P>
<P>＞6 } </P>
<P>　表示把文件m1.c的3至5行改成m2.c的3至6行后，两个文件相同。 </P>
<P>from: <A href="http://www.winterxy.com/blog/000082.html">http://www.winterxy.com/blog/000082.html</A></P><img src ="http://www.blogjava.net/weidagang2046/aggbug/33884.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-03-06 16:53 <a href="http://www.blogjava.net/weidagang2046/articles/33884.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理解 Xwindow</title><link>http://www.blogjava.net/weidagang2046/articles/31136.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 17 Feb 2006 01:53:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/31136.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/31136.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/31136.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/31136.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/31136.html</trackback:ping><description><![CDATA[<P>很多人在用 Xwindow，但是他们是在用一些包装掩盖下的 Xwindow，那些包装有些很不稳定，占用大量资源，以至于有人说 Xwindow 是垃圾。其实 Xwindow 是一个非常出色的图形系统，你应该抱怨的是那些不稳定的包装，而不是 Xwindow 本身。 
<P>Xwindow 是非常巧妙的设计，很多时候它在概念上比其它窗口系统先进，以至于经过很多年它仍然是工作站上的工业标准。许多其它窗口系统的概念都是从 Xwindow 学来的。 
<P>Xwindow 可以说的东西太多了。下面只分辨一些容易混淆的概念，提出一些正确使用它的建议。 
<H2>分辨 X server 和 X client</H2>
<P>这是我被别人问了好多次的问题，我实在不想再对人说一遍了，所以写在这里偷个懒，嘿嘿。 
<P>很多熟悉 Internet 原理的人首次遇到 Xwindow 的这两个概念都会搞错。如果他从一台 Windows 机器上使用 Exceed 通过 XDMCP 登录到一台 Sun 服务器，他就说 Exceed 是客户端(client)，而 Sun 机器是服务器(server)。这就完全搞错了。 
<P>理解了 Xwindow 的工作原理，这个区别就会很明显。X server 不是指你登录的那台机器，而是指一个程序，它负责在某台机器上接受客户的要求，在屏幕上显示客户请求的图形，并且把消息(键盘，鼠标，窗口消息)通知客户程序。 
<P>比如上面例子里的 Exceed 就是一个 X server，它负责控制那台 Windows 机器上的显示(display)，Sun 机器上的程序，xterm, xxgdb, dtwm(CDE的窗口管理器)，……是客户程序。它们通常会使用 TCP 6000 号端口连接 Windows 机器，而Windows机器的 6000 号端口是由 Exceed bind 和 listen 的，怎么样，我们的 Internet 专家，Exceed 看起来是一个 server 吧 :) 
<P>比如，当你通过 telnet 启动 Sun 机器上的 xterm，就会在 Exceed 的屏幕上显示一个窗口。实际发生的事情是： xterm <EM>请求</EM>连接 Windows 机器的 6000 号端口，与Exceed 跟 Exceed 连接，然后xterm<EM>请求</EM>得到资源，然后 xterm <EM>请求</EM>在屏幕上显示一个窗口。 
<P>你在 xterm 的窗口里按下"A"键时，Exceed 会把这个事件<EM>通知</EM> xterm 进程，然后 xterm 会发送数据报，<EM>请求</EM> Exceed, “请在坐标(100,30)处显示一个字母A，然后在后面显示一个矩形作为光标。”，这样你的 xterm 窗口里就会多显示一个字母。 
<P>这下你理解了吗？不？那还是自己多用用，体会体会吧。 
<H2>Xwindow 的网络透明性</H2>
<P>刚才那个 Exceed 用户，他的客户程序不仅可以运行在那台 Sun 机器上，而且可以运行在网络里其它的机器上，或者在本机上。比如他有可能同时通过 telnet 登录到几台 Sun 工作站，几台 Linux PC，他可以在那些机器上都起动 xterm，在这台 Windows 机器上的 Exceed 上显示。如果它安装了 cygwin，他还可以启动本机上的 cygwin里 的 xterm，用同样的方式显示。 
<P>如果他使用 xrdb 在本机的 Exceed 设置了 xterm 的样式，比如背景 midnightblue, 前景 白色，字体 -*-adobe-courier-*，……，那么，Sun, Linux, cygwin 里的 xterm，虽然没有经过配置，但是它们知道：“这个 X server 要求我用这个背景色，这个前景色，这个字体……”，它们会在你的屏幕上显示同样风格的窗口。 
<P>使用鼠标选择一段字符，然后就可以在另外的程序里按鼠标中间，把字符粘贴过去。不论这个程序运行在哪里。 
<P>看看更大的一个图景：你的屏幕上有三台机器上的 xterm, 两台机器上的 gvim，两台机器上的 mozilla 在显示，而它们都受本机的 FVWM 操纵，它们之间可以随意进行拷贝粘贴…… 从这里你可以初步体会到 Xwindow 的网络透明性，它使你方便的操作很多机器。 
<H2>什么是窗口管理器？</H2>
<P>很多人不知道窗口管理器是怎么回事。他们认为 Gnome 和 KDE 是窗口管理器，认为窗口管理器就是能够提供一个工具条，能够配置桌面背景，能够设置很多菜单的东西。而其实，窗口管理器只是 Gnome 和 KDE 的一部分，它的主要功能是你平时根本没有注意，但是却非常重要的操作。窗口管理器的主要功能是：移动窗口，改变窗口大小，图标化（最小化）窗口，改变窗口层叠顺序…… 
<P>通常的X客户程序不需要知道有人想移动它，它只知道乖乖听窗口管理器的话。如果没有窗口管理器运行，你的程序会一个堆一个，你没有办法操纵被盖在下面的程序，你只能使用最上面一个程序，而且你不能移动它，你不能改变它的大小。这样的系统根本不能用！ 
<P>其实你的窗口上的标题，按钮，漂亮的边框，全都是窗口管理器提供的，而不是程序自己的，这样你用窗口管理器就能改变任何窗口的样式了。当你点击关闭窗口的那个按钮，你其实点击的是窗口管理器放在你的程序窗口上面的一个小窗口，发现它受到点击后，窗口管理器就会通知那个程序：“喂！有人想关掉你，你自己准备准备后事，然后退出吧。” 
<P>不同的机器在本机显示的窗口，由窗口管理器统一装饰和指挥。比如，窗口管理器决定： xterm 窗口上面都应该有四个按钮，一个在左边，点击它会显示窗口操作菜单，另外三个在右边，分别是最大化，最小化和关闭。窗口都使用 7pixel 厚的边框，窗口首次出现的时候首先在桌面上找一个空位置，如果找不到，就找一个能够最少的遮盖其它窗口的位置…… 
<P>这些都是窗口管理器的职责。 
<H2>Gnome 和 KDE 是什么？</H2>
<P>有人把 Gnome 和 KDE 叫做窗口管理器，甚至还有人把它们叫做 Xwindow。经常看到有人问：“装哪种 Xwindow 好啊？Gnome 还是 KDE？” 其实你不安装 Gnome 和 KDE 也可以使用 Xwindow. 
<P>Gnome 和 KDE 是“桌面系统”，一种很多程序和函数库的集合。它们的设计目的是提供一致的方便的操作方式来满足普通用户的需要。它们不但包含窗口管理器，还有很多实用程序和应用程序，比如配置程序，工具条，编辑器，绘图工具…… 其实 Gnome 可以和很多窗口管理器合作，在历史上，Gnome 使用过的窗口管理器包括 englightenment, sawmill, sawfish, metacity。KDE 的窗口管理器叫做 kwin。 
<P>你也知道，设计整整一套应用程序：编辑器，绘图程序，浏览器，…… 是非常不容易的。所以它们肯定是不如专用的编辑器，绘图程序，浏览器的。不过要求不太高的用户也可以用它们。 
<H2>用 Xwindow 做一个有趣的试验</H2>
<P>现在我们来做一个试验来引起你对 Xwindow 的兴趣。 
<P>很多人离开了 xdm 就不知道怎样启动 "Xwindow" 了。其实 Xwindow 的启动方式很简单。 
<P>首先，启动你的 X server。在 Linux 下，你可以直接输入 "X&amp;"，让一个 X server 在后台运行。这样，一个占据整个屏幕的布满斜纹的窗口（根窗口）就出现了。如果是 Exceed 或者 X-win32，你需要把屏幕设置为 "single window" 才能看到这个窗口。 
<P>接着，Linux 用户需要用 xauth 给你的 X server 设置一个密码，这样别人就不能非法连接你的 X server。你需要Ctrl-Alt-F1切换到tty1才能输入这个命令。Exceed 和 X-win32 的用户不需要这步。以下的例子只用 Linux 作为实例，其它系统的用户可以依葫芦画瓢。 <PRE>xauth add :0 . `mcookie`
</PRE>
<P>好了，现在你可以试试启动一个 xterm 到这个 X server 上： <PRE>xterm -display :0
</PRE>
<P>按 Alt-F7 切换到 X，你看到了一个不受窗口管理器管理的 xterm。试试移动它呢？再切换到 tty1 启动另一个 xterm，两个 xterm 重合了吗？你怎样在第一个 xterm 里输入呢？这样用起来很痛苦吧？那么你可以在 xterm 里启动一个窗口管理器，比如 twm，直接运行 "twm&amp;" 就行了。 
<P>你发现xterm的窗口都被加上了标题栏，按钮和边框，你现在可以移动它们了。试试把 twm 杀死，看看那些标题栏，按钮，边框是不是都消失了？你知道了窗口管理器的作用了吧。 
<P>现在告诉你怎样不用 XDMCP broadcast，绕过 CDE 的登陆界面，而使用 Sun 机器上的 CDE。其实你只需要把刚才那个例子里的 "twm" 换成 CDE 的窗口管理器 dtwm 就行了，或者启动 dtsession。它们一般在 /usr/dt/bin 下。Sun 还有另外一个窗口管理器叫做 OpenLook, 它在系统里的名字叫做 olwm，你自己找找吧。 
<P>现在如果你自己在 Sun 机器上装一个 FVWM，你知道怎么绕过 CDE 登录而启动 FVWM 了吧？你不需要成为 root 就可以使用你喜欢的FVWM了。 
<H2>.Xdefaults 文件的作用</H2>
<P>弄明白 X 的工作原理后，你就可以使用 .Xdefaults 来配置你的程序了。有人不理解 .Xdefaults 是用来干什么的，喜欢用 shell alias 出一些带有很多参数的命令，或者写一些 shell script，里面只有一行带有很多参数的程序调用，然后他说：“.Xdefaults 没用。” 
<P>其实 .Xdefaults 比起这些办法有很多好处。首先，几乎所有的 X 客户程序都可以从 .Xdefaults 文件得到配置信息，比如前景色，背景色，字体…… 这样你可以在同一个文件里配置所有 X 程序的样式，而不用写那么多 shell script。 
<P>另外，如果你用 xrdb .Xdefaults 把这些配置信息写入到根窗口的数据结构里，不是本地机器上的 X 程序也会遵守同样的样式。比如如果你的 .Xdefaults 里面有这些内容： <PRE>XTerm.background: midnightblue
XTerm.foreground: white
</PRE>
<P>然后你用 xrdb ~/.Xdefaults，那么所有的 xterm 都会使用 midnightblue 作为背景色，使用白色显示字符。这些配置，用X术语叫资源(resource)。 
<P>有时候你会开很多 xterm 窗口在机器上，它们来自不同的远程机器，这时候如果它们都用同样的样色和字体，你就不容易分辨它们了。比如有一天，我在实验室的PC机上打开了很多 xterm，它们来自本机，实验室的 Sun 服务器，我宿舍的机器和数据库实验室一台 HP 服务器。在我走之前，想关闭本地的机器，我输入 “shutdown -h now” 在一个我自认是本地的 xterm 上，结果，等了一会儿，我回头看到机器没关掉，只是那个 xterm 停止了响应。我一拍脑袋：糟了，我把宿舍的机器给关掉了！ 
<P>为了避免这种情况，你可以在不同的机器上编辑不同的 .Xdefaults，而不使用 xrdb 在根窗口导入这些配置。这样不同的机器上的 xterm 使用不同的颜色，你就不容易搞错了。 
<H2>怎样更加深入的了解 Xwindow？</H2>
<P>哎哟！我本来想写很多东西，后来发现要需要写的实在太多了。我没那么多时间，没办法，给参考书目吧，你们自己去看。 
<P>学习使用 Xwindow 不是学习使用 Gnome，KDE，…… 很多书籍教用户怎样使用 Gnome, KDE 的菜单，配置程序…… 结果到了最后用户还是没能知道 Xwindow 是怎么回事。用那些菜单谁不会啊？真是浪费大家时间。 
<P>学习 Xwindow，其实最好的一个办法是看看 Xlib 编程的书籍，你可以在图书馆找到这样的书，我推荐 O'reily 的那本 Xlib 程序设计书（我忘了名字了），虽然它很老，但是那上面讲解的 Xwindow 的工作原理一直都没有变过。 
<P>如果你不会编程序，你可以不看编程的部分而得到很多深入的概念上的认识，比如窗口属性，字体，颜色，…… 这些东西在你使用 Xwindow 时会有很大帮助。如果你真的要编写 Xlib 程序，这本书就过时了一点，你最好到 www.x.org 去免费下载最新的 Xlib 手册来看。 </P>
<P>from: <A href="http://learn.tsinghua.edu.cn:8080/2001315450/x.html">http://learn.tsinghua.edu.cn:8080/2001315450/x.html</A></P><img src ="http://www.blogjava.net/weidagang2046/aggbug/31136.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-02-17 09:53 <a href="http://www.blogjava.net/weidagang2046/articles/31136.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>查看unix版本</title><link>http://www.blogjava.net/weidagang2046/articles/30116.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 10 Feb 2006 04:22:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/30116.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/30116.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/30116.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/30116.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/30116.html</trackback:ping><description><![CDATA[uname -a<img src ="http://www.blogjava.net/weidagang2046/aggbug/30116.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-02-10 12:22 <a href="http://www.blogjava.net/weidagang2046/articles/30116.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>去掉文件中的^M字符</title><link>http://www.blogjava.net/weidagang2046/articles/30112.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 10 Feb 2006 03:48:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/30112.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/30112.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/30112.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/30112.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/30112.html</trackback:ping><description><![CDATA[当你FTP一些DOS文件到unix下时，你经常会看见每行文件后面有个讨<BR>厌的^M 字符,(把 HTML 文件传输到 UNIX 系统可引起回车符转变成“^M”字符),<BR>有两个简单的方法可以取消它。<BR>用"vi"打开此文件，在Command mode下敲入：<BR>:%s/^V^M//g <BR>或者，在UNIX SHELL下敲入:<BR>sed 's/^V^M//g' foo &gt; foo.new <BR><BR>from: <A href="http://www.chinalinuxpub.com/bbs/archive/index.php/t-16983.html">http://www.chinalinuxpub.com/bbs/archive/index.php/t-16983.html</A><img src ="http://www.blogjava.net/weidagang2046/aggbug/30112.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-02-10 11:48 <a href="http://www.blogjava.net/weidagang2046/articles/30112.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>文件内容统计命令:wc</title><link>http://www.blogjava.net/weidagang2046/articles/28165.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 16 Jan 2006 02:18:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/28165.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/28165.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/28165.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/28165.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/28165.html</trackback:ping><description><![CDATA[<FONT color=#000000>wc命令的功能为统计指定文件中的字节数、字数、行数,&nbsp;并将统计结果显示输出。&nbsp; <BR><BR>语法：wc&nbsp;[选项]&nbsp;文件…&nbsp; <BR><BR>说明：该命令统计给定文件中的字节数、字数、行数。如果没有给出文件名，则从标准输入读取。wc同时也给出所有指定文件的总统计数。字是由空格字符区分开的最大字符串。&nbsp; <BR><BR>该命令各选项含义如下：&nbsp; <BR><BR>-&nbsp;c&nbsp;统计字节数。&nbsp; <BR><BR>-&nbsp;l&nbsp;统计行数。&nbsp; <BR><BR>-&nbsp;w&nbsp;统计字数。&nbsp; <BR><BR>这些选项可以组合使用。&nbsp; <BR><BR>输出列的顺序和数目不受选项的顺序和数目的影响。总是按下述顺序显示并且每项最多一列。&nbsp; <BR><BR>行数、字数、字节数、文件名&nbsp; <BR><BR>如果命令行中没有文件名，则输出中不出现文件名。&nbsp; <BR><BR>例如：&nbsp; <BR><BR>$&nbsp;wc&nbsp;-&nbsp;lcw&nbsp;file1&nbsp;file2&nbsp; <BR><BR>4&nbsp;33&nbsp;file1&nbsp; <BR><BR>7&nbsp;52&nbsp;file2&nbsp; <BR><BR>11&nbsp;11&nbsp;85&nbsp;total&nbsp; <BR><BR>省略任选项-lcw，wc命令的执行结果与上面一样。<BR><BR>from: <A href="http://fanqiang.chinaunix.net/a1/b5/20010508/142415.html">http://fanqiang.chinaunix.net/a1/b5/20010508/142415.html</A></FONT><img src ="http://www.blogjava.net/weidagang2046/aggbug/28165.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-01-16 10:18 <a href="http://www.blogjava.net/weidagang2046/articles/28165.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux文件查找命令find,xargs详述</title><link>http://www.blogjava.net/weidagang2046/articles/25634.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 27 Dec 2005 14:51:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/25634.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/25634.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/25634.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/25634.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/25634.html</trackback:ping><description><![CDATA[<P><FONT id=00 size=4><B>前言：关于find命令</B></FONT></P>
<P>由于find具有强大的功能，所以它的选项也很多，其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS)，find命令在该文件系统中同样有效，只你具有相应的权限。</P>
<P>在运行一个非常消耗资源的find命令时，很多人都倾向于把它放在后台执行，因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。</P>
<P><FONT id=1 size=4><B><BR>一、find 命令格式<BR></B></FONT></P>
<P><FONT id=1.1 size=3><B><BR>1、find命令的一般形式为；<BR></B></FONT></P>
<P>
<DIV class=codeblock><CODE>find pathname -options [-print -exec -ok ...]</CODE></DIV>
<P></P>
<P><FONT id=1.2 size=3><B><BR>2、find命令的参数；<BR></B></FONT></P>
<P>
<DIV class=codeblock><CODE>pathname: find命令所查找的目录路径。例如用.来表示当前目录，用/来表示系统根目录。<BR>-print： find命令将匹配的文件输出到标准输出。<BR>-exec： find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为'command' { } \;，注意{ }和\；之间的空格。<BR>-ok： 和-exec的作用相同，只不过以一种更为安全的模式来执行该参数所给出的shell命令，在执行每一个命令之前，都会给出提示，让用户来确定是否执行。</CODE></DIV>
<P></P>
<P><FONT id=1.3 size=3><B><BR>3、find命令选项<BR></B></FONT><BR>
<DIV class=codeblock><CODE>-name <BR>按照文件名查找文件。<BR>-perm<BR>按照文件权限来查找文件。<BR>-prune<BR>使用这一选项可以使find命令不在当前指定的目录中查找，如果同时使用-depth选项，那么-prune将被find命令忽略。<BR>-user<BR>按照文件属主来查找文件。<BR>-group<BR>按照文件所属的组来查找文件。<BR>-mtime -n +n<BR>按照文件的更改时间来查找文件， - n表示文件更改时间距现在n天以内，+ n表示文件更改时间距现在n天以前。find命令还有-atime和-ctime 选项，但它们都和-m time选项。<BR>-nogroup<BR>查找无有效所属组的文件，即该文件所属的组在/etc/groups中不存在。<BR>-nouser<BR>查找无有效属主的文件，即该文件的属主在/etc/passwd中不存在。<BR>-newer file1 ! file2 <BR>查找更改时间比文件file1新但比文件file2旧的文件。<BR>-type <BR>查找某一类型的文件，诸如：<BR>b - 块设备文件。<BR>d - 目录。<BR>c - 字符设备文件。<BR>p - 管道文件。<BR>l - 符号链接文件。<BR>f - 普通文件。<BR>-size n：[c] 查找文件长度为n块的文件，带有c时表示文件长度以字节计。<BR>-depth：在查找文件时，首先查找当前目录中的文件，然后再在其子目录中查找。<BR>-fstype：查找位于某一类型文件系统中的文件，这些文件系统类型通常可以在配置文件/etc/fstab中找到，该配置文件中包含了本系统中有关文件系统的信息。<BR>-mount：在查找文件时不跨越文件系统mount点。<BR>-follow：如果find命令遇到符号链接文件，就跟踪至链接所指向的文件。<BR>-cpio：对匹配的文件使用cpio命令，将这些文件备份到磁带设备中。</CODE></DIV>
<P></P>
<P>另外,下面三个的区别:<BR>
<DIV class=codeblock><CODE>&nbsp;&nbsp;&nbsp;-amin n<BR>　　查找系统中最后N分钟访问的文件<BR>　　-atime n<BR>　　查找系统中最后n*24小时访问的文件<BR>　　-cmin n<BR>　　查找系统中最后N分钟被改变文件状态的文件<BR>　　-ctime n<BR>　　查找系统中最后n*24小时被改变文件状态的文件<BR>&nbsp;&nbsp;&nbsp;　-mmin n<BR>　　查找系统中最后N分钟被改变文件数据的文件<BR>　　-mtime n<BR>　　查找系统中最后n*24小时被改变文件数据的文件</CODE></DIV>
<P></P>
<P><FONT id=1.4 size=3><B><BR>4、使用exec或ok来执行shell命令<BR></B></FONT></P>
<P>使用find时，只要把想要的操作写在一个文件里，就可以用exec来配合find查找，很方便的</P>
<P>在有些操作系统中只允许-exec选项执行诸如l s或ls -l这样的命令。大多数用户使用这一选项是为了查找旧文件并删除它们。建议在真正执行rm命令删除文件之前，最好先用ls命令看一下，确认它们是所要删除的文件。</P>
<P>exec选项后面跟随着所要执行的命令或脚本，然后是一对儿{ }，一个空格和一个\，最后是一个分号。为了使用exec选项，必须要同时使用print选项。如果验证一下find命令，会发现该命令只输出从当前路径起的相对路径及文件名。</P>
<P>例如：为了用ls -l命令列出所匹配到的文件，可以把ls -l命令放在find命令的-exec选项中</P>
<P>
<DIV class=codeblock><CODE># find . -type f -exec ls -l { } \;<BR>-rw-r--r-- 1 root root 34928 2003-02-25 ./conf/httpd.conf<BR>-rw-r--r-- 1 root root 12959 2003-02-25 ./conf/magic<BR>-rw-r--r-- 1 root root 180 2003-02-25 ./conf.d/README</CODE></DIV>
<P></P>
<P>上面的例子中，find命令匹配到了当前目录下的所有普通文件，并在-exec选项中使用ls -l命令将它们列出。<BR>在/logs目录中查找更改时间在5日以前的文件并删除它们：</P>
<P>
<DIV class=codeblock><CODE>$ find logs -type f -mtime +5 -exec rm { } \;</CODE></DIV>
<P></P>
<P><B>记住：</B>在shell中用任何方式删除文件之前，应当先查看相应的文件，一定要小心！当使用诸如mv或rm命令时，可以使用-exec选项的安全模式。它将在对每个匹配到的文件进行操作之前提示你。</P>
<P>在下面的例子中， find命令在当前目录中查找所有文件名以.LOG结尾、更改时间在5日以上的文件，并删除它们，只不过在删除之前先给出提示。</P>
<P>
<DIV class=codeblock><CODE>$ find . -name "*.conf" -mtime +5 -ok rm { } \;<BR>&lt; rm ... ./conf/httpd.conf &gt; ? n</CODE></DIV>
<P></P>
<P>按y键删除文件，按n键不删除。</P>
<P>任何形式的命令都可以在-exec选项中使用。</P>
<P>在下面的例子中我们使用grep命令。find命令首先匹配所有文件名为“ passwd*”的文件，例如passwd、passwd.old、passwd.bak，然后执行grep命令看看在这些文件中是否存在一个sam用户。</P>
<P>
<DIV class=codeblock><CODE># find /etc -name "passwd*" -exec grep "sam" { } \;<BR>sam:x:501:501::/usr/sam:/bin/bash</CODE></DIV>
<P></P>
<P><FONT id=2 size=4><B><BR>二、find命令的例子；<BR></B></FONT></P>
<P><FONT id=2.1 size=3><B><BR>1、查找当前用户主目录下的所有文件：<BR></B></FONT></P>
<P>下面两种方法都可以使用</P>
<P>
<DIV class=codeblock><CODE>$ find $HOME -print<BR>$ find ~ -print</CODE></DIV><BR><FONT id=2.2 size=3><B><BR>2、让当前目录中文件属主具有读、写权限，并且文件所属组的用户和其他用户具有读权限的文件；<BR></B></FONT>
<P></P>
<P>
<DIV class=codeblock><CODE>$ find . -type f -perm 644 -exec ls -l { } \;</CODE></DIV>
<P></P>
<P><FONT id=2.3 size=3><B><BR>3、为了查找系统中所有文件长度为0的普通文件，并列出它们的完整路径；<BR></B></FONT></P>
<P>
<DIV class=codeblock><CODE>$ find / -type f -size 0 -exec ls -l { } \;</CODE></DIV>
<P></P>
<P><FONT id=2.4 size=3><B><BR>4、查找/var/logs目录中更改时间在7日以前的普通文件，并在删除之前询问它们；<BR></B></FONT></P>
<P>
<DIV class=codeblock><CODE>$ find /var/logs -type f -mtime +7 -ok rm { } \;</CODE></DIV>
<P></P>
<P><FONT id=2.5 size=3><B><BR>5、为了查找系统中所有属于root组的文件；<BR></B></FONT></P>
<P>
<DIV class=codeblock><CODE>$find . -group root -exec ls -l { } \;<BR>-rw-r--r-- 1 root root 595 10月 31 01:09 ./fie1</CODE></DIV>
<P></P>
<P><FONT id=2.6 size=3><B><BR>6、find命令将删除当目录中访问时间在7日以来、含有数字后缀的admin.log文件。<BR></B></FONT></P>
<P>该命令只检查三位数字，所以相应文件的后缀不要超过999。先建几个admin.log*的文件 ，才能使用下面这个命令</P>
<P>
<DIV class=codeblock><CODE>$ find . -name "admin.log[0-9][0-9][0-9]" -atime -7 -ok<BR>rm { } \;<BR>&lt; rm ... ./admin.log001 &gt; ? n<BR>&lt; rm ... ./admin.log002 &gt; ? n<BR>&lt; rm ... ./admin.log042 &gt; ? n<BR>&lt; rm ... ./admin.log942 &gt; ? n</CODE></DIV>
<P></P>
<P><FONT id=2.7 size=3><B><BR>7、为了查找当前文件系统中的所有目录并排序；<BR></B></FONT></P>
<P>
<DIV class=codeblock><CODE>$ find . -type d | sort</CODE></DIV>
<P></P>
<P><FONT id=2.8 size=3><B><BR>8、为了查找系统中所有的rmt磁带设备；<BR></B></FONT></P>
<P>
<DIV class=codeblock><CODE>$ find /dev/rmt -print</CODE></DIV>
<P></P>
<P><FONT id=3 size=4><B><BR>三、xargs<BR></B></FONT></P>
<P>xargs - build and execute command lines from standard input</P>
<P>在使用find命令的-exec选项处理匹配到的文件时， find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制，这样在find命令运行几分钟之后，就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在，特别是与find命令一起使用。</P>
<P>find命令把匹配到的文件传递给xargs命令，而xargs命令每次只获取一部分文件而不是全部，不像-exec选项那样。这样它可以先处理最先获取的一部分文件，然后是下一批，并如此继续下去。</P>
<P>在有些系统中，使用-exec选项会为处理每一个匹配到的文件而发起一个相应的进程，并非将匹配到的文件全部作为参数一次执行；这样在有些情况下就会出现进程过多，系统性能下降的问题，因而效率不高；</P>
<P>而使用xargs命令则只有一个进程。另外，在使用xargs命令时，究竟是一次获取所有的参数，还是分批取得参数，以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参数来确定。</P>
<P>来看看xargs命令是如何同find命令一起使用的，并给出一些例子。</P>
<P>下面的例子查找系统中的每一个普通文件，然后使用xargs命令来测试它们分别属于哪类文件</P>
<P>
<DIV class=codeblock><CODE>#find . -type f -print | xargs file<BR>./.kde/Autostart/Autorun.desktop: UTF-8 Unicode English text<BR>./.kde/Autostart/.directory: ISO-8859 text\<BR>......</CODE></DIV>
<P></P>
<P>在整个系统中查找内存信息转储文件(core dump) ，然后把结果保存到/tmp/core.log 文件中：</P>
<P>
<DIV class=codeblock><CODE>$ find / -name "core" -print | xargs echo "" &gt;/tmp/core.log</CODE></DIV>
<P></P>
<P>上面这个执行太慢，我改成在当前目录下查找</P>
<P>
<DIV class=codeblock><CODE>#find . -name "file*" -print | xargs echo "" &gt; /temp/core.log<BR># cat /temp/core.log<BR>./file6</CODE></DIV>
<P></P>
<P>在当前目录下查找所有用户具有读、写和执行权限的文件，并收回相应的写权限：</P>
<P>
<DIV class=codeblock><CODE># ls -l<BR>drwxrwxrwx 2 sam adm 4096 10月 30 20:14 file6<BR>-rwxrwxrwx 2 sam adm 0 10月 31 01:01 http3.conf<BR>-rwxrwxrwx 2 sam adm 0 10月 31 01:01 httpd.conf<BR># find . -perm -7 -print | xargs chmod o-w<BR># ls -l<BR>drwxrwxr-x 2 sam adm 4096 10月 30 20:14 file6<BR>-rwxrwxr-x 2 sam adm 0 10月 31 01:01 http3.conf<BR>-rwxrwxr-x 2 sam adm 0 10月 31 01:01 httpd.conf</CODE></DIV>
<P></P>
<P>用grep命令在所有的普通文件中搜索hostname这个词：</P>
<P>
<DIV class=codeblock><CODE># find . -type f -print | xargs grep "hostname"<BR>./httpd1.conf:# different IP addresses or hostnames and have them handled by the<BR>./httpd1.conf:# VirtualHost: If you want to maintain multiple domains/hostnames<BR>on your</CODE></DIV>
<P></P>
<P>用grep命令在当前目录下的所有普通文件中搜索hostnames这个词：</P>
<P>
<DIV class=codeblock><CODE># find . -name \* -type f -print | xargs grep "hostnames"<BR>./httpd1.conf:# different IP addresses or hostnames and have them handled by the<BR>./httpd1.conf:# VirtualHost: If you want to maintain multiple domains/hostnames<BR>on your</CODE></DIV>
<P></P>
<P>注意，在上面的例子中， \用来取消find命令中的*在shell中的特殊含义。</P>
<P>find命令配合使用exec和xargs可以使用户对所匹配到的文件执行几乎所有的命令。</P>
<P><FONT id=4 size=4><B><BR>四、find 命令的参数<BR></B></FONT></P>
<P>下面是find一些常用参数的例子，有用到的时候查查就行了，像上面前几个贴子，都用到了其中的的一些参数，也可以用man或查看论坛里其它贴子有find的命令手册</P>
<P><FONT id=4.1 size=3><B><BR>1、使用name选项<BR></B></FONT></P>
<P>文件名选项是find命令最常用的选项，要么单独使用该选项，要么和其他选项一起使用。</P>
<P>可以使用某种文件名模式来匹配文件，记住要用引号将文件名模式引起来。</P>
<P>不管当前路径是什么，如果想要在自己的根目录$HOME中查找文件名符合*.txt的文件，使用~作为 'pathname'参数，波浪号~代表了你的$HOME目录。</P>
<P>
<DIV class=codeblock><CODE>$ find ~ -name "*.txt" -print</CODE></DIV>
<P></P>
<P>想要在当前目录及子目录中查找所有的‘ *.txt’文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find . -name "*.txt" -print</CODE></DIV>
<P></P>
<P>想要的当前目录及子目录中查找文件名以一个大写字母开头的文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find . -name "[A-Z]*" -print</CODE></DIV>
<P></P>
<P>想要在/etc目录中查找文件名以host开头的文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find /etc -name "host*" -print</CODE></DIV>
<P></P>
<P>想要查找$HOME目录中的文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find ~ -name "*" -print 或find . -print</CODE></DIV>
<P></P>
<P>要想让系统高负荷运行，就从根目录开始查找所有的文件。</P>
<P>
<DIV class=codeblock><CODE>$ find / -name "*" -print</CODE></DIV>
<P></P>
<P>如果想在当前目录查找文件名以两个小写字母开头，跟着是两个数字，最后是.txt的文件，下面的命令就能够返回名为ax37.txt的文件：</P>
<P>
<DIV class=codeblock><CODE>$find . -name "[a-z][a-z][0--9][0--9].txt" -print</CODE></DIV>
<P></P>
<P><FONT id=4.2 size=3><B><BR>2、用perm选项<BR></B></FONT></P>
<P>按照文件权限模式用-perm选项,按文件权限模式来查找文件的话。最好使用八进制的权限表示法。</P>
<P>如在当前目录下查找文件权限位为755的文件，即文件属主可以读、写、执行，其他用户可以读、执行的文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find . -perm 755 -print</CODE></DIV>
<P></P>
<P>还有一种表达方法：在八进制数字前面要加一个横杠-，表示都匹配，如-007就相当于777，-006相当于666</P>
<P>
<DIV class=codeblock><CODE># ls -l<BR>-rwxrwxr-x 2 sam adm 0 10月 31 01:01 http3.conf<BR>-rw-rw-rw- 1 sam adm 34890 10月 31 00:57 httpd1.conf<BR>-rwxrwxr-x 2 sam adm 0 10月 31 01:01 httpd.conf<BR>drw-rw-rw- 2 gem group 4096 10月 26 19:48 sam<BR>-rw-rw-rw- 1 root root 2792 10月 31 20:19 temp<BR># find . -perm 006<BR># find . -perm -006<BR>./sam<BR>./httpd1.conf<BR>./temp</CODE></DIV>
<P></P>
<P>-perm mode:文件许可正好符合mode</P>
<P>-perm +mode:文件许可部分符合mode</P>
<P>-perm -mode: 文件许可完全符合mode</P>
<P><FONT id=4.3 size=3><B><BR>3、忽略某个目录<BR></B></FONT></P>
<P>如果在查找文件时希望忽略某个目录，因为你知道那个目录中没有你所要查找的文件，那么可以使用-prune选项来指出需要忽略的目录。在使用-prune选项时要当心，因为如果你同时使用了-depth选项，那么-prune选项就会被find命令忽略。</P>
<P>如果希望在/apps目录下查找文件，但不希望在/apps/bin目录下查找，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find /apps -path "/apps/bin" -prune -o -print</CODE></DIV>
<P></P>
<P><FONT id=4.4 size=3><B><BR>4、使用find查找文件的时候怎么避开某个文件目录<BR></B></FONT></P>
<P>比如要在/usr/sam目录下查找不在dir1子目录之内的所有文件</P>
<P>
<DIV class=codeblock><CODE>find /usr/sam -path "/usr/sam/dir1" -prune -o -print</CODE></DIV>
<P></P>
<P>
<DIV class=codeblock><CODE>find [-path ..] [expression] 在路径列表的后面的是表达式</CODE></DIV>
<P></P>
<P>-path "/usr/sam" -prune -o -print 是 -path "/usr/sam" -a -prune -o<BR>-print 的简写表达式按顺序求值, -a 和 -o 都是短路求值，与 shell 的 &amp;&amp; 和 || 类似如果 -path "/usr/sam" 为真，则求值 -prune , -prune 返回真，与逻辑表达式为真；否则不求值 -prune，与逻辑表达式为假。如果 -path "/usr/sam" -a -prune 为假，则求值 -print ，-print返回真，或逻辑表达式为真；否则不求值 -print，或逻辑表达式为真。</P>
<P>这个表达式组合特例可以用伪码写为</P>
<P>
<DIV class=codeblock><CODE>if -path "/usr/sam" then<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-prune<BR>else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-print</CODE></DIV>
<P></P>
<P>避开多个文件夹</P>
<P>
<DIV class=codeblock><CODE>find /usr/sam \( -path /usr/sam/dir1 -o -path /usr/sam/file1 \) -prune -o -print</CODE></DIV>
<P></P>
<P>圆括号表示表达式的结合。<BR>
<DIV class=codeblock><CODE>\ 表示引用，即指示 shell 不对后面的字符作特殊解释，而留给 find 命令去解释其意义。</CODE></DIV>
<P></P>
<P>查找某一确定文件，-name等选项加在-o 之后</P>
<P>
<DIV class=codeblock><CODE>#find /usr/sam \(-path /usr/sam/dir1 -o -path /usr/sam/file1 \) -prune -o -name "temp" -print</CODE></DIV>
<P></P>
<P><FONT id=4.5 size=3><B><BR>5、使用user和nouser选项<BR></B></FONT></P>
<P>按文件属主查找文件，如在$HOME目录中查找文件属主为sam的文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find ~ -user sam -print</CODE></DIV>
<P></P>
<P>在/etc目录下查找文件属主为uucp的文件：</P>
<P>
<DIV class=codeblock><CODE>$ find /etc -user uucp -print</CODE></DIV>
<P></P>
<P>为了查找属主帐户已经被删除的文件，可以使用-nouser选项。这样就能够找到那些属主在/etc/passwd文件中没有有效帐户的文件。在使用-nouser选项时，不必给出用户名； find命令能够为你完成相应的工作。</P>
<P>例如，希望在/home目录下查找所有的这类文件，可以用：<BR>
<DIV class=codeblock><CODE>$ find /home -nouser -print</CODE></DIV>
<P></P>
<P><FONT id=4.6 size=3><B><BR>6、使用group和nogroup选项<BR></B></FONT></P>
<P>就像user和nouser选项一样，针对文件所属于的用户组， find命令也具有同样的选项，为了在/apps目录下查找属于gem用户组的文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find /apps -group gem -print</CODE></DIV>
<P></P>
<P>要查找没有有效所属用户组的所有文件，可以使用nogroup选项。下面的find命令从文件系统的根目录处查找这样的文件</P>
<P>
<DIV class=codeblock><CODE>$ find / -nogroup-print</CODE></DIV>
<P></P>
<P><FONT id=4.7 size=3><B><BR>7、按照更改时间或访问时间等查找文件<BR></B></FONT></P>
<P>如果希望按照更改时间来查找文件，可以使用mtime,atime或ctime选项。如果系统突然没有可用空间了，很有可能某一个文件的长度在此期间增长迅速，这时就可以用mtime选项来查找这样的文件。</P>
<P>用减号-来限定更改时间在距今n日以内的文件，而用加号+来限定更改时间在距今n日以前的文件。</P>
<P>希望在系统根目录下查找更改时间在5日以内的文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find / -mtime -5 -print</CODE></DIV>
<P></P>
<P>为了在/var/adm目录下查找更改时间在3日以前的文件，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find /var/adm -mtime +3 -print</CODE></DIV>
<P></P>
<P><FONT id=4.8 size=3><B><BR>8、查找比某个文件新或旧的文件<BR></B></FONT></P>
<P>如果希望查找更改时间比某个文件新但比另一个文件旧的所有文件，可以使用-newer选项。它的一般形式为：</P>
<P>
<DIV class=codeblock><CODE>newest_file_name ! oldest_file_name</CODE></DIV>
<P></P>
<P>其中，！是逻辑非符号。</P>
<P>查找更改时间比文件sam新但比文件temp旧的文件：</P>
<P>例：有两个文件</P>
<P>
<DIV class=codeblock><CODE>-rw-r--r-- 1 sam adm 0 10月 31 01:07 fiel<BR>-rw-rw-rw- 1 sam adm 34890 10月 31 00:57 httpd1.conf<BR>-rwxrwxr-x 2 sam adm 0 10月 31 01:01 httpd.conf<BR>drw-rw-rw- 2 gem group 4096 10月 26 19:48 sam<BR>-rw-rw-rw- 1 root root 2792 10月 31 20:19 temp<BR># find -newer httpd1.conf ! -newer temp -ls<BR>1077669 0 -rwxrwxr-x 2 sam adm 0 10月 31 01:01 ./httpd.conf<BR>1077671 4 -rw-rw-rw- 1 root root 2792 10月 31 20:19 ./temp<BR>1077673 0 -rw-r--r-- 1 sam adm 0 10月 31 01:07 ./fiel</CODE></DIV>
<P></P>
<P>查找更改时间在比temp文件新的文件：</P>
<P>
<DIV class=codeblock><CODE>$ find . -newer temp -print</CODE></DIV><BR><FONT id=4.9 size=3><B><BR>9、使用type选项<BR></B></FONT>
<P></P>
<P>在/etc目录下查找所有的目录，可以用：</P>
<P>
<DIV class=codeblock><CODE>$ find /etc -type d -print</CODE></DIV>
<P></P>
<P>在当前目录下查找除目录以外的所有类型的文件，可以用：<BR>
<DIV class=codeblock><CODE>$ find . ! -type d -print</CODE></DIV>
<P></P>
<P>在/etc目录下查找所有的符号链接文件，可以用<BR>
<DIV class=codeblock><CODE>$ find /etc -type l -print</CODE></DIV>
<P></P>
<P><FONT id=4.10 size=3><B><BR>10、使用size选项<BR></B></FONT></P>
<P>可以按照文件长度来查找文件，这里所指的文件长度既可以用块（block）来计量，也可以用字节来计量。以字节计量文件长度的表达形式为N c；以块计量文件长度只用数字表示即可。</P>
<P>在按照文件长度查找文件时，一般使用这种以字节表示的文件长度，在查看文件系统的大小，因为这时使用块来计量更容易转换。<BR>在当前目录下查找文件长度大于1 M字节的文件：<BR>
<DIV class=codeblock><CODE>$ find . -size +1000000c -print</CODE></DIV>
<P></P>
<P>在/home/apache目录下查找文件长度恰好为100字节的文件：</P>
<P>
<DIV class=codeblock><CODE>$ find /home/apache -size 100c -print</CODE></DIV>
<P></P>
<P>在当前目录下查找长度超过10块的文件（一块等于512字节）： </P>
<P>
<DIV class=codeblock><CODE>$ find . -size +10 -print</CODE></DIV>
<P></P>
<P><FONT id=4.11 size=3><B><BR>11、使用depth选项<BR></B></FONT></P>
<P>在使用find命令时，可能希望先匹配所有的文件，再在子目录中查找。使用depth选项就可以使find命令这样做。这样做的一个原因就是，当在使用find命令向磁带上备份文件系统时，希望首先备份所有的文件，其次再备份子目录中的文件。</P>
<P>在下面的例子中， find命令从文件系统的根目录开始，查找一个名为CON.FILE的文件。</P>
<P>它将首先匹配所有的文件然后再进入子目录中查找。</P>
<P>
<DIV class=codeblock><CODE>$ find / -name "CON.FILE" -depth -print</CODE></DIV>
<P></P>
<P><FONT id=4.12 size=3><B><BR>12、使用mount选项<BR></B></FONT></P>
<P>在当前的文件系统中查找文件（不进入其他文件系统），可以使用find命令的mount选项。</P>
<P>从当前目录开始查找位于本文件系统中文件名以XC结尾的文件：</P>
<P>
<DIV class=codeblock><CODE>$ find . -name "*.XC" -mount -print</CODE></DIV>
<P></P>
<P><BR><BR><FONT id=5 size=4><B>五、关于本文</B></FONT></P>
<P>本文是find 命令的详细说明，可贵的是针对参数举了很多的实例，大量的例证，让初学者更为容易理解；本文是<A href="http://www.linuxsir.org/bbs/member.php?u=57037" target=_blank>zhy2111314</A>兄贴在论坛中；我对本文进行了再次整理，为方便大家阅读； ── 北南南北</P>
<P><FONT id=6 size=4><B>六、相关文档</B></FONT></P>
<P><STRONG><FONT size=4>from: <A href="http://www.linuxsir.org/main/?q=node/137">http://www.linuxsir.org/main/?q=node/137</A></FONT></STRONG></P><img src ="http://www.blogjava.net/weidagang2046/aggbug/25634.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-12-27 22:51 <a href="http://www.blogjava.net/weidagang2046/articles/25634.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>awk详解</title><link>http://www.blogjava.net/weidagang2046/articles/25374.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 25 Dec 2005 07:57:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/25374.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/25374.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/25374.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/25374.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/25374.html</trackback:ping><description><![CDATA[a w k是一种程序语言，对文档资料的处理具有很强的功能。awk 名称是由它三个最初设计<BR>者的姓氏的第一个字母而命名的： Alfred V. Aho、Peter J. We i n b e rg e r、Brian W. Kernighan。<BR>a w k最初在1 9 7 7年完成。1 9 8 5年发表了一个新版本的a w k，它的功能比旧版本增强了不少。a w k<BR>能够用很短的程序对文档里的资料做修改、比较、提取、打印等处理。如果使用C 或P a s c a l<BR>等语言编写程序完成上述的任务会十分不方便而且很花费时间，所写的程序也会很大。<BR>a w k不仅仅是一个编程语言，它还是L i n u x系统管理员和程序员的一个不可缺少的工具。<BR>a w k语言本身十分好学，易于掌握，并且特别的灵活。<BR>gawk 是G N U计划下所做的a w k，gawk 最初在1 9 8 6年完成，之后不断地被改进、更新。<BR>gawk 包含awk 的所有功能。<BR>6.1 gawk的主要功能<BR>gawk 的主要功能是针对文件的每一行( l i n e )，也就是每一条记录，搜寻指定的格式。当某<BR>一行符合指定的格式时，gawk 就会在此行执行被指定的动作。gawk 依此方式自动处理输入文<BR>件的每一行直到输入文件档案结束。<BR>g a w k经常用在如下的几个方面：<BR>• 根据要求选择文件的某几行，几列或部分字段以供显示输出。<BR>• 分析文档中的某一个字出现的频率、位置等。<BR>• 根据某一个文档的信息准备格式化输出。<BR>• 以一个功能十分强大的方式过滤输出文档。<BR>• 根据文档中的数值进行计算。<BR>6.2 如何执行gawk程序<BR>基本上有两种方法可以执行g a w k程序。<BR>如果gawk 程序很短，则可以将gawk 直接写在命令行，如下所示：<BR>gawk 'program' input-file1 input-file2 ...<BR>其中program 包括一些pattern 和a c t i o n。<BR>如果gawk 程序较长，较为方便的做法是将gawk 程序存在一个文件中，<BR>gawk 的格式如下所示：<BR>gawk -f program-file input-file1 input-file2 ...<BR>gawk 程序的文件不止一个时，执行gawk 的格式如下所示：<BR>gawk -f program-file1 -f program-file2 ... input-file1 input-file2 ...<BR>6.3 文件、记录和字段<BR>一般情况下，g a w k可以处理文件中的数值数据，但也可以处理字符串信息。如果数据没有<BR>存储在文件中，可以通过管道命令和其他的重定向方法给g a w k提供输入。当然， g a w k只能处<BR>理文本文件（A S C I I码文件）。 <BR>电话号码本就是一个g a w k可以处理的文件的简单例子。电话号码本由很多条目组成，每一<BR>个条目都有同样的格式：姓、名、地址、电话号码。每一个条目都是按字母顺序排列。<BR>在g a w k中，每一个这样的条目叫做一个记录。它是一个完整的数据的集合。例如，电话号<BR>码本中的Smith John这个条目，包括他的地址和电话号码，就是一条记录。<BR>记录中的每一项叫做一个字段。在g a w k中，字段是最基本的单位。多个记录的集合组成了<BR>一个文件。<BR>大多数情况下，字段之间由一个特殊的字符分开，像空格、TA B、分号等。这些字符叫做<BR>字段分隔符。请看下面这个/ e t c / p a s s w d文件：<BR>t p a r k e r ; t 3 6 s 6 2 h s h ; 5 0 1 ; 1 0 1 ; Tim Parker;/home/tparker;/bin/bash<BR>etreijs;2ys639dj3h;502;101;Ed Tr e i j s ; / h o m e / e t r e i j s ; / b i n / t c s h<BR>y c h o w ; 1 h 2 7 s j ; 5 0 3 ; 1 0 1 ; Yvonne Chow;/home/ychow;/bin/bash<BR>你可以看出/ e t c / p a s s w d文件使用分号作为字段分隔符。/ e t c / p a s s w d文件中的每一行都包括<BR>七个字段：用户名；口令；用户I D；工作组I D；注释； h o m e目录；启始的外壳。如果你想要<BR>查找第六个字段，只需数过五个分号即可。<BR>但考虑到以下电话号码本的例子，你就会发现一些问题：<BR>Smith John 13 Wilson St. 555-1283<BR>Smith John 2736 Artside Dr Apt 123 555-2736<BR>Smith John 125 Westmount Cr 555-1726<BR>虽然我们能够分辨出每个记录包括四个字段，但g a w k却无能为力。电话号码本使用空格作<BR>为分隔符，所以g a w k认为S m i t h是第一个字段， John 是第二个字段，1 3是第三个字段，依次类<BR>推。就g a w k而言，如果用空格作为字段分隔符的话，则第一个记录有六个字段，而第二个记<BR>录有八个字段。<BR>所以，我们必须找出一个更好的字段分隔符。例如，像下面一样使用斜杠作为字段分隔<BR>符：<BR>Smith/John/13 Wilson St./555-1283<BR>Smith/John/2736 Artside Dr/Apt/123/555-2736<BR>Smith/John/125 Westmount Cr/555-1726<BR>如果你没有指定其他的字符作为字段分隔符，那么g a w k将缺省地使用空格或TA B作为字段<BR>分隔符。<BR>6.4 模式和动作<BR>在g a w k语言中每一个命令都由两部分组成：一个模式（ p a t t e r n）和一个相应的动作<BR>（a c t i o n）。只要模式符合，g a w k就会执行相应的动作。其中模式部分用两个斜杠括起来，而动<BR>作部分用一对花括号括起来。例如：<BR>/ p a t t e r n 1 / { a c t i o n 1 }<BR>/ p a t t e r n 2 / { a c t i o n 2 }<BR>/ p a t t e r n 3 / { a c t i o n 3 }<BR>所有的g a w k程序都是由这样的一对对的模式和动作组成的。其中模式或动作都能够被省<BR>略，但是两个不能同时被省略。如果模式被省略，则对于作为输入的文件里面的每一行，动作<BR>都会被执行。如果动作被省略，则缺省的动作被执行，既显示出所有符合模式的输入行而不做<BR>任何的改动。<BR>下面是一个简单的例子，因为gawk 程序很短，所以将gawk 程序直接写在外壳命令行：<BR>gawk '/tparker/' /etc/passwd<BR><BR>此程序在上面提到的/ e t c / p a s s w d文件中寻找符合t p a r k e r模式的记录并显示（此例中没有动<BR>作，所以缺省的动作被执行）。<BR>让我们再看一个例子：<BR>gawk '/UNIX/{print $2}' file2.data<BR>此命令将逐行查找f i l e 2 . d a t a文件中包含U N I X的记录，并打印这些记录的第二个字段。<BR>你也可以在一个命令中使用多个模式和动作对，例如：<BR>gawk '/scandal/{print $1} /rumor/{print $2}' gossip_file<BR>此命令搜索文件g o s s i p _ f i l e中包括s c a n d a l的记录，并打印第一个字段。然后再从头搜索<BR>g o s s i p _ f i l e中包括r u m o r的记录，并打印第二个字段。<BR>6.5 比较运算和数值运算<BR>g a w k有很多比较运算符，下面列出重要的几个：<BR>= = 相等<BR>! = 不相等<BR>&gt; 大于<BR>&lt; 小于<BR>&gt; = 大于等于<BR>&lt; = 小于等于<BR>例如：<BR>gawk '$4 &gt; 100' testfile<BR>将会显示文件testfile 中那些第四个字段大于1 0 0的记录。<BR>下表列出了g a w k中基本的数值运算符。<BR>运算符说明示例<BR>+ 加法运算2+6<BR>- 减法运算6-3<BR>* 乘法运算2*5<BR>/ 除法运算8/4<BR>^ 乘方运算3^2 (=9)<BR>% 求余数9%4 (=1)<BR>例如：<BR>{print $3/2}<BR>显示第三个字段被2除的结果。<BR>在g a w k中，运算符的优先权和一般的数学运算的优先权一样。例如：<BR>{print $1+$2*$3}<BR>显示第二个字段和第三个字段相乘，然后和第一个字段相加的结果。<BR>你也可以用括号改变优先次序。例如：<BR>{print ($1+$2)*$3}<BR>显示第一个字段和第二个字段相加，然后和第三个字段相乘的结果。<BR>6.6 内部函数<BR>g a w k中有各种的内部函数，现在介绍如下： <BR>6.6.1 随机数和数学函数<BR>sqrt(x) 求x 的平方根<BR>sin(x) 求x 的正弦函数<BR>cos(x) 求x 的余弦函数<BR>a t a n 2 ( x，y) 求x / y的余切函数<BR>log(x) 求x 的自然对数<BR>exp(x) 求x 的e 次方<BR>int(x) 求x 的整数部分<BR>rand() 求0 和1之间的随机数<BR>srand(x) 将x 设置为r a n d ( )的种子数<BR>6.6.2 字符串的内部函数<BR>• i n d e x ( i n，find) 在字符串in 中寻找字符串find 第一次出现的地方，返回值是字符串<BR>find 出现在字符串in 里面的位置。如果在字符串in 里面找不到字符串f i n d，则返回值为<BR>0。<BR>例如：<BR>print index("peanut"，" a n " )<BR>显示结果3。<BR>• length(string) 求出string 有几个字符。<BR>例如：<BR>l e n g t h ( " a b c d e " )<BR>显示结果5。<BR>• m a t c h ( s t r i n g，r e g e x p ) 在字符串string 中寻找符合regexp 的最长、最靠左边的子字<BR>符串。返回值是regexp 在string 的开始位置，即i n d e x值。match 函数将会设置系统变量<BR>R S TA RT 等于i n d e x的值，系统变量RLENGTH 等于符合的字符个数。如果不符合，则会<BR>设置R S TA RT 为0、RLENGTH 为- 1。<BR>• s p r i n t f ( f o r m a t，e x p r e s s i o n 1，. . . ) 和printf 类似，但是sprintf 并不显示，而是返回字符<BR>串。<BR>例如：<BR>sprintf("pi = %.2f (approx.)"，2 2 / 7 )<BR>返回的字符串为pi = 3.14 (approx.)<BR>• s u b ( r e g e x p，r e p l a c e m e n t，t a rg e t ) 在字符串t a rget 中寻找符合regexp 的最长、最靠左的<BR>地方，以字串replacement 代替最左边的r e g e x p。<BR>例如：<BR>str = "water，w a t e r，e v e r y w h e r e "<BR>s u b ( / a t /， " i t h "，s t r )<BR>结果字符串s t r会变成<BR>w i t h e r，w a t e r，e v e r y w h e r e<BR>• g s u b ( r e g e x p，r e p l a c e m e n t，t a rget) 与前面的s u b类似。在字符串t a rget 中寻找符合<BR>r e g e x p的所有地方，以字符串replacement 代替所有的r e g e x p。<BR>例如：<BR>s t r = " w a t e r，w a t e r，e v e r y w h e r e "g s u b ( / a t /， " i t h "，s t r )<BR>结果字符串s t r会变成<BR>w i t h e r，w i t h e r，e v e r y w h e r e<BR>• s u b s t r ( s t r i n g，s t a r t，length) 返回字符串string 的子字符串，这个子字符串的长度为<BR>l e n g t h，从第start 个位置开始。<BR>例如：<BR>s u b s t r ( " w a s h i n g t o n "，5，3 )<BR>返回值为i n g<BR>如果没有length ，则返回的子字符串是从第start 个位置开始至结束。<BR>例如：<BR>s u b s t r ( " w a s h i n g t o n "，5 )<BR>返回值为i n g t o n。<BR>• tolower(string) 将字符串s t r i n g的大写字母改为小写字母。<BR>例如：<BR>tolower("MiXeD cAsE 123")<BR>返回值为mixed case 123。<BR>• toupper(string) 将字符串s t r i n g的小写字母改为大写字母。<BR>例如：<BR>toupper("MiXeD cAsE 123")<BR>返回值为MIXED CASE 123。<BR>6.6.3 输入输出的内部函数<BR>• close(filename) 将输入或输出的文件filename 关闭。<BR>• system(command) 此函数允许用户执行操作系统的指令，执行完毕后将回到g a w k程<BR>序。<BR>例如：<BR>BEGIN {system("ls")}<BR>6.7 字符串和数字<BR>字符串就是一连串的字符，它可以被g a w k逐字地翻译。字符串用双引号括起来。数字不能<BR>用双引号括起来，并且g a w k将它当作一个数值。例如：<BR>gawk '$1 != "Tim" {print}' testfile<BR>此命令将显示第一个字段和Ti m不相同的所有记录。如果命令中Ti m两边不用双引号，<BR>g a w k将不能正确执行。<BR>再如：<BR>gawk '$1 == "50" {print}' testfile<BR>此命令将显示所有第一个字段和5 0这个字符串相同的记录。g a w k不管第一字段中的数值<BR>的大小，而只是逐字地比较。这时，字符串5 0和数值5 0并不相等。<BR>6.8 格式化输出<BR>我们可以让动作显示一些比较复杂的结果。例如：<BR>gawk '$1 != "Tim" {print $1，$ 5，$ 6，$2}' testfile<BR>将显示t e s t f i l e文件中所有第一个字段和Ti m不相同的记录的第一、第五、第六和第二个字<BR>段。<BR>进一步，你可以在p r i n t动作中加入字符串，例如：<BR>gawk '$1 != "Tim" {print "The entry for "，$ 1，"is not Tim. "，$2}' testfile<BR>p r i n t动作的每一部分用逗号隔开。<BR>借用C语言的格式化输出指令，可以让g a w k的输出形式更为多样。这时，应该用p r i n t f而不<BR>是p r i n t。例如：<BR>{printf "%5s likes this language\n"，$ 2 }<BR>p r i n t f中的%5s 部分告诉gawk 如何格式化输出字符串，也就是输出5个字符长。它的值由<BR>printf 的最后部分指出，在此是第二个字段。\ n是回车换行符。如果第二个字段中存储的是人<BR>名，则输出结果大致如下：<BR>Tim likes this language<BR>G e o ff likes this language<BR>Mike likes this language<BR>Joe likes this language<BR>gawk 语言支持的其他格式控制符号如下：<BR>• c 如果是字符串，则显示第一个字符；如果是整数，则将数字以ASCII 字符的形式显示。<BR>例如：<BR>printf “% c”，6 5<BR>结果将显示字母A。<BR>• d 显示十进制的整数。<BR>• i 显示十进制的整数。<BR>• e 将浮点数以科学记数法的形式显示。<BR>例如：<BR>print “$ 4 . 3 e”，1 9 5 0<BR>结果将显示1 . 9 5 0 e + 0 3。<BR>• f 将数字以浮点的形式显示。<BR>• g 将数字以科学记数法的形式或浮点的形式显示。数字的绝对值如果大于等于0 . 0 0 0 1则<BR>以浮点的形式显示，否则以科学记数法的形式显示。<BR>• o 显示无符号的八进制整数。<BR>• s 显示一个字符串。<BR>• x 显示无符号的十六进制整数。1 0至1 5以a至f表示。<BR>• X 显示无符号的十六进制整数。1 0至1 5以A至F表示。<BR>• % 它并不是真正的格式控制字符，% %将显示%。<BR>当你使用这些格式控制字符时，你可以在控制字符前给出数字，以表示你将用的几位或几<BR>个字符。例如，6 d表示一个整数有6位。再请看下面的例子：<BR>{printf "%5s works for %5s and earns %2d an hour"，$ 1，$ 2，$ 3 }<BR>将会产生类似如下的输出：<BR>Joe works for Mike and earns 12 an hour<BR>当处理数据时，你可以指定数据的精确位数<BR>{printf "%5s earns $%.2f an hour"，$ 3，$ 6 }<BR>其输出将类似于：<BR>Joe earns $12.17 an hour<BR>你也可以使用一些换码控制符格式化整行的输出。之所以叫做换码控制符，是因为g a w k对<BR>这些符号有特殊的解释。下面列出常用的换码控制符：<BR>\a 警告或响铃字符。<BR>\b 后退一格。<BR>\f 换页。<BR>\n 换行。<BR>\r 回车。<BR>\t Ta b。<BR>\v 垂直的t a b。<BR>6.9 改变字段分隔符<BR>在g a w k中，缺省的字段分隔符一般是空格符或TA B。但你可以在命令行使用- F选项改变字<BR>符分隔符，只需在- F后面跟着你想用的分隔符即可。<BR>gawk -F" ;"'/tparker/{print}' /etc/passwd<BR>在此例中，你将字符分隔符设置成分号。注意： - F必须是大写的，而且必须在第一个引号<BR>之前。<BR>6.10 元字符<BR>g a w k语言在格式匹配时有其特殊的规则。例如， c a t能够和记录中任何位置有这三个字符<BR>的字段匹配。但有时你需要一些更为特殊的匹配。如果你想让c a t只和c o n c a t e n a t e匹配，则需要<BR>在格式两端加上空格：<BR>/ cat / {print}<BR>再例如，你希望既和c a t又和C AT匹配，则可以使用或（|）：<BR>/ cat | CAT / {print}<BR>在g a w k中，有几个字符有特殊意义。下面列出可以用在g a w k格式中的这些字符：<BR>• ^ 表示字段的开始。<BR>例如：<BR>$3 ~ /^b/<BR>如果第三个字段以字符b开始，则匹配。<BR>• $ 表示字段的结束。<BR>例如：<BR>$3 ~ /b$/<BR>如果第三个字段以字符b结束，则匹配。<BR>• . 表示和任何单字符m匹配。<BR>例如：<BR>$3 ~ /i.m/<BR>如果第三个字段有字符i，则匹配。<BR>• | 表示“或”。<BR>例如：<BR>/ c a t | C AT/<BR>和cat 或C AT字符匹配。<BR>• * 表示字符的零到多次重复。<BR>例如：<BR>/UNI*X/<BR>和U N X、U N I X、U N I I X、U N I I I X等匹配。<BR>• + 表示字符的一次到多次重复。<BR>例如：<BR>/UNI+X/<BR>和U N I X、U N I I X等匹配。<BR>• \{a，b\} 表示字符a次到b次之间的重复。<BR>例如：<BR>/ U N I \ { 1，3 \ } X<BR>和U N I X、U N I I X和U N I I I X匹配。<BR>• ? 表示字符零次和一次的重复。<BR>例如：<BR>/UNI?X/<BR>和UNX 和U N I X匹配。<BR>• [] 表示字符的范围。<BR>例如：<BR>/I[BDG]M/<BR>和I B M、I D M和I G M匹配<BR>• [^] 表示不在[ ]中的字符。<BR>例如：<BR>/I[^DE]M/<BR>和所有的以I开始、M结束的包括三个字符的字符串匹配，除了I D M和I E M之外。<BR>6.11 调用gawk程序<BR>当需要很多对模式和动作时，你可以编写一个g a w k程序（也叫做g a w k脚本）。在g a w k程序<BR>中，你可以省略模式和动作两边的引号，因为在g a w k程序中，模式和动作从哪开始和从哪结<BR>束时是很显然的。<BR>你可以使用如下命令调用g a w k程序：<BR>gawk -f script filename<BR>此命令使g a w k对文件f i l e n a m e执行名为s c r i p t的g a w k程序。<BR>如果你不希望使用缺省的字段分隔符，你可以在f选项后面跟着F选项指定新的字段分隔符<BR>（当然你也可以在g a w k程序中指定），例如，使用分号作为字段分隔符：<BR>gawk -f script -F";" filename<BR>如果希望gawk 程序处理多个文件，则把各个文件名罗列其后：<BR>gawk -f script filename1 filename2 filename3 ...<BR>缺省情况下， g a w k的输出将送往屏幕。但你可以使用L i n u x的重定向命令使g a w k的输出送<BR>往一个文件：<BR>gawk -f script filename &gt; save_file<BR>6.12 BEGIN和END<BR>有两个特殊的模式在g a w k中非常有用。B E G I N模式用来指明g a w k开始处理一个文件之前执行一些动作。B E G I N经常用来初始化数值，设置参数等。E N D模式用来在文件处理完成后<BR>执行一些指令，一般用作总结或注释。<BR>BEGIN 和E N D中所有要执行的指令都应该用花括号括起来。BEGIN 和E N D必须使用大写。<BR>请看下面的例子：<BR>BEGIN { print "Starting the process the file" }<BR>$1 == "UNIX" {print}<BR>$2 &gt; 10 {printf "This line has a value of %d"，$ 2 }<BR>END { print "Finished processing the file. Bye!"}<BR>此程序中，先显示一条信息： Starting the process the file，然后将所有第一个字段等于<BR>U N I X的整条记录显示出来，然后再显示第二个字段大于10 的记录，最后显示信息： F i n i s h e d<BR>processing the file. Bye!。<BR>6.13 变量<BR>在g a w k中，可以用等号( = )给一个变量赋值：<BR>var1 = 10<BR>在g a w k中，你不必事先声明变量类型。<BR>请看下面的例子：<BR>$1 == "Plastic" { count = count + 1 }<BR>如果第一个字段是P l a s t i c，则c o u n t的值加1。在此之前，我们应当给c o u n t赋予过初值，一<BR>般是在B E G I N部分。<BR>下面是比较完整的例子：<BR>BEGIN { count = 0 }<BR>$5 == "UNIX" { count = count + 1 }<BR>END { printf "%d occurrences of UNIX were found"，count }<BR>变量可以和字段和数值一起使用，所以，下面的表达式均为合法：<BR>count = count + $6<BR>count = $5 - 8<BR>count = $5 + var1<BR>变量也可以是格式的一部分，例如：<BR>$2 &gt; max_value {print "Max value exceeded by "，$2 - max_value}<BR>$4 - var1 &lt; min_value {print "Illegal value of "，$ 4 }<BR>6.14 内置变量<BR>g a w k语言中有几个十分有用的内置变量，现在列于下面：<BR>NR 已经读取过的记录数。<BR>FNR 从当前文件中读出的记录数。<BR>F I L E N A M E 输入文件的名字。<BR>FS 字段分隔符（缺省为空格）。<BR>RS 记录分隔符（缺省为换行）。<BR>OFMT 数字的输出格式（缺省为% g）。<BR>OFS 输出字段分隔符。<BR>ORS 输出记录分隔符。<BR>NF 当前记录中的字段数。<BR>如果你只处理一个文件，则NR 和FNR 的值是一样的。但如果是多个文件， N R是对所有<BR>的文件来说的，而FNR 则只是针对当前文件而言。<BR>例如：<BR>NR &lt;= 5 {print "Not enough fields in the record"}<BR>检查记录数是否小于5，如果小于5，则显示出错信息。<BR>F S十分有用，因为F S控制输入文件的字段分隔符。例如，在B E G I N格式中，使用如下的<BR>命令：<BR>F S = " : "<BR>6.15 控制结构<BR>6.15.1 if 表达式<BR>if 表达式的语法如下：<BR>if (expression){<BR>c o m m a n d s<BR>}<BR>e l s e {<BR>c o m m a n d s<BR>}<BR>例如：<BR># a simple if loop<BR>(if ($1 == 0){<BR>print "This cell has a value of zero"<BR>}<BR>else {<BR>printf "The value is %d\n"，$ 1<BR>} )<BR>再看下一个例子：<BR># a nicely formatted if loop<BR>(if ($1 &gt; $2){<BR>print "The first column is larger"<BR>}<BR>else {<BR>print "The second column is larger"<BR>} )<BR>6.15.2 while 循环<BR>while 循环的语法如下：<BR>while (expression){<BR>c o m m a n d s<BR>}<BR>例如：<BR># interest calculation computes compound interest<BR># inputs from a file are the amount，interest_rateand years<BR>{var = 1<BR>while (var &lt;= $3) {<BR>p r i n t f ( " % f \ n "，$ 1 * ( 1 + $ 2 ) ^ v a r )<BR>v a r + +}<BR>}<BR>6.15.3 for 循环<BR>for 循环的语法如下：<BR>for (initialization; expression; increment) {<BR>c o m m a n d<BR>}<BR>例如：<BR># interest calculation computes compound interest<BR># inputs from a file are the amount，interest_rateand years<BR>{for (var=1; var &lt;= $3; var++) {<BR>p r i n t f ( " % f \ n "，$ 1 * ( 1 + $ 2 ) ^ v a r )<BR>}<BR>}<BR>6.15.4 next 和exit<BR>next 指令用来告诉gawk 处理文件中的下一个记录， 而不管现在正在做什么。语法如下：<BR>{ command1<BR>c o m m a n d 2<BR>c o m m a n d 3<BR>n e x t<BR>c o m m a n d 4<BR>}<BR>程序只要执行到n e x t指令，就跳到下一个记录从头执行命令。因此，本例中， c o m m a n d 4<BR>指令永远不会被执行。<BR>程序遇到e x i t指令后，就转到程序的末尾去执行E N D，如果有E N D的话。<BR>6.16 数组<BR>g a w k语言支持数组结构。数组不必事先初始化。声明一个数组的方法如下：<BR>a r r a y n a m e [ n u m ] = v a l u e<BR>请看下面的例子：<BR># reverse lines in a file<BR>{line[NR] = $0 } # remember each line<BR>END {var=NR # output lines in reverse order<BR>while (var &gt; 0){<BR>print line[var]<BR>v a r - -<BR>}<BR>}<BR>此段程序读取一个文件的每一行，并用相反的顺序显示出来。我们使用N R作为数组的下<BR>标来存储文件的每一条记录，然后在从最后一条记录开始，将文件逐条地显示出来。<BR>6.17 用户自定义函数<BR>复杂的gawk 程序常常可以使用自己定义的函数来简化。调用用户自定义函数与调用内部<BR>函数的方法一样。函数的定义可以放在gawk 程序的任何地方。<BR>用户自定义函数的格式如下：<BR>function name (parameter-list) {<BR>b o d y - o f - f u n c t i o n<BR>}<BR>name 是所定义的函数的名称。一个正确的函数名称可包括一序列的字母、数字、下标线<BR>( u n d e r s c o r e s )，但是不可用数字做开头。p a r a m e t e r-list 是函数的全部参数的列表，各个参数之<BR>间以逗点隔开。body-of-function 包含gawk 的表达式，它是函数定义里最重要的部分，它决定<BR>函数实际要做的事情。<BR>下面这个例子，会将每个记录的第一个字段的值的平方与第二个字段的值的平方加起来。<BR>{print "sum ="，S q u a r e S u m ( $ 1，$ 2 ) }<BR>function SquareSum(x，y) {<BR>s u m = x * x + y * y<BR>return sum<BR>}<BR>到此，我们已经知道了g a w k的基本用法。g a w k语言十分易学好用，例如，你可以用g a w k<BR>编写一段小程序来计算一个目录中所有文件的个数和容量。如果用其他的语言，如C语言，则<BR>会十分的麻烦，相反，g a w k只需要几行就可以完成此工作。<BR>6.18 几个实例<BR>最后，再举几个g a w k的例子：<BR>gawk '{if (NF &gt; max) max = NF}<BR>END {print max}'<BR>此程序会显示所有输入行之中字段的最大个数。<BR>gawk 'length($0) &gt; 80'<BR>此程序会显示出超过80 个字符的每一行。此处只有模式被列出，动作是采用缺省值显示<BR>整个记录。<BR>gawk 'NF &gt; 0'<BR>显示拥有至少一个字段的所有行。这是一个简单的方法，将一个文件里的所有空白行删除。<BR>gawk 'BEGIN {for (i = 1; i &lt;= 7; i++)<BR>print int(101 * rand())}'<BR>此程序会显示出范围是0 到100 之间的7 个随机数。<BR>ls -l files | gawk '{x += $4}; END {print "total bytes: " x}'<BR>此程序会显示出所有指定的文件的总字节数。<BR>expand file | gawk '{if (x &lt; length()) x = length()}<BR>END {print "maximum line length is " x}'<BR>此程序会将指定文件里最长一行的长度显示出来。expand 会将tab 改成s p a c e，所以是用<BR>实际的右边界来做长度的比较。<BR>gawk 'BEGIN {FS = ":"}<BR>{print $1 | "sort"}' /etc/passwd<BR>此程序会将所有用户的登录名称，依照字母的顺序显示出来。<BR>gawk '{nlines++}<BR>END {print nlines}'<BR>此程序会将一个文件的总行数显示出来。<BR>gawk 'END {print NR}'<BR>此程序也会将一个文件的总行数显示出来，但是计算行数的工作由g a w k来做。<BR>gawk '{print NR，$ 0 } '<BR>此程序显示出文件的内容时，会在每行的最前面显示出行号，它的函数与‘ cat -n’类似。<BR>function name (parameter-list) {<BR>b o d y - o f - f u n c t i o n<BR>}<BR>name 是所定义的函数的名称。一个正确的函数名称可包括一序列的字母、数字、下标线<BR>( u n d e r s c o r e s )，但是不可用数字做开头。p a r a m e t e r-list 是函数的全部参数的列表，各个参数之<BR>间以逗点隔开。body-of-function 包含gawk 的表达式，它是函数定义里最重要的部分，它决定<BR>函数实际要做的事情。<BR>下面这个例子，会将每个记录的第一个字段的值的平方与第二个字段的值的平方加起来。<BR>{print "sum ="，S q u a r e S u m ( $ 1，$ 2 ) }<BR>function SquareSum(x，y) {<BR>s u m = x * x + y * y<BR>return sum<BR>}<BR>到此，我们已经知道了g a w k的基本用法。g a w k语言十分易学好用，例如，你可以用g a w k<BR>编写一段小程序来计算一个目录中所有文件的个数和容量。如果用其他的语言，如C语言，则<BR>会十分的麻烦，相反，g a w k只需要几行就可以完成此工作。<BR>6.18 几个实例<BR>最后，再举几个g a w k的例子：<BR>gawk '{if (NF &gt; max) max = NF}<BR>END {print max}'<BR>此程序会显示所有输入行之中字段的最大个数。<BR>gawk 'length($0) &gt; 80'<BR>此程序会显示出超过80 个字符的每一行。此处只有模式被列出，动作是采用缺省值显示<BR>整个记录。<BR>gawk 'NF &gt; 0'<BR>显示拥有至少一个字段的所有行。这是一个简单的方法，将一个文件里的所有空白行删除。<BR>gawk 'BEGIN {for (i = 1; i &lt;= 7; i++)<BR>print int(101 * rand())}'<BR>此程序会显示出范围是0 到100 之间的7 个随机数。<BR>ls -l files | gawk '{x += $4}; END {print "total bytes: " x}'<BR>此程序会显示出所有指定的文件的总字节数。<BR>expand file | gawk '{if (x &lt; length()) x = length()}<BR>END {print "maximum line length is " x}'<BR>此程序会将指定文件里最长一行的长度显示出来。expand 会将tab 改成s p a c e，所以是用<BR>实际的右边界来做长度的比较。<BR>gawk 'BEGIN {FS = ":"}<BR>{print $1 | "sort"}' /etc/passwd<BR>此程序会将所有用户的登录名称，依照字母的顺序显示出来。<BR>gawk '{nlines++}<BR>END {print nlines}'<BR>此程序会将一个文件的总行数显示出来。<BR>gawk 'END {print NR}'<BR>此程序也会将一个文件的总行数显示出来，但是计算行数的工作由g a w k来做。<BR>gawk '{print NR，$ 0 } '<BR>此程序显示出文件的内容时，会在每行的最前面显示出行号，它的函数与‘ cat -n’类似。<BR><BR>from: <A href="http://www.linuxsky.net/html/200502/1081.html">http://www.linuxsky.net/html/200502/1081.html</A><img src ="http://www.blogjava.net/weidagang2046/aggbug/25374.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-12-25 15:57 <a href="http://www.blogjava.net/weidagang2046/articles/25374.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Bash中的变量</title><link>http://www.blogjava.net/weidagang2046/articles/25373.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 25 Dec 2005 07:43:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/25373.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/25373.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/25373.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/25373.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/25373.html</trackback:ping><description><![CDATA[1.用户定义的变量 <BR>&nbsp;&nbsp;用户定义的变量有字母数字及下划线组成,并且变量名的第一个字符不能为数字. <BR>与其它UNIX名字一样,变量名是大小写敏感的. <BR>&nbsp;&nbsp;对于变量,用户可按如下方式赋值: <BR>&nbsp;&nbsp;name&nbsp;=&nbsp;value <BR>&nbsp;&nbsp;在引用变量时,需在前面加$符号，用户也可以在变量间进行相互赋值,如: <BR>&nbsp;&nbsp;(前面的$是命令提示符) <BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;$&nbsp;JOHN&nbsp;=&nbsp;john <BR>&nbsp;&nbsp;$&nbsp;NAME&nbsp;=&nbsp;$JOHN <BR>&nbsp;&nbsp;$&nbsp;echo&nbsp;Hello&nbsp;$NAME <BR>&nbsp;&nbsp;Hello&nbsp;john <BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;也可以用变量和其他字符组成新的字,这时可能需要把变量用{}括起,如: <BR><BR>&nbsp;&nbsp;$&nbsp;SAT&nbsp;=&nbsp;Satur <BR>&nbsp;&nbsp;$&nbsp;echo&nbsp;Today&nbsp;is&nbsp;${SAT}day <BR>&nbsp;&nbsp;Today&nbsp;is&nbsp;Saturday <BR><BR>&nbsp;&nbsp;对于未赋值的变量,Bash以空值对待,用户也可以用unset命令清除给变量 <BR>&nbsp;&nbsp;赋的值. <BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;Bash中还可以使用数组变量,其赋值有两种: <BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;(1)&nbsp;name[index]&nbsp;=&nbsp;value <BR>&nbsp;&nbsp;(2)&nbsp;name&nbsp;=&nbsp;(value1&nbsp;...&nbsp;valuen)&nbsp;此时下标从0开始 <BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;数组下标的范围没有任何限制,同时也不必使用连续的分量. <BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;Bash中关于变量的内建命令有: <BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;(1)&nbsp;declare和typeset.两者具有一样的功能.其选项有: <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[-/+]a&nbsp;&nbsp;&nbsp;&nbsp;设置/撤消变量的数组属性 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[-/+]i&nbsp;&nbsp;&nbsp;&nbsp;设置/撤消变量的整数属性 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[-/+]r&nbsp;&nbsp;&nbsp;&nbsp;设置/撤消变量的只读属性 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[-/+]x&nbsp;&nbsp;&nbsp;&nbsp;设置/撤消变量的输出属性 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-p&nbsp;var&nbsp;&nbsp;&nbsp;&nbsp;显示变量属性 <BR>&nbsp;&nbsp;(2)&nbsp;export和local. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;export把变量输出到环境中,用法为: <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;export&nbsp;name <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;export&nbsp;name&nbsp;=&nbsp;value&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这里需要简单介绍一下export的作用:当Bash&nbsp;shell执行一个 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;程序时,将首先为该程序建立一个新的执行环境,称为子shell, <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在Bash&nbsp;Shell中变量都是局部的,即它们只是在创建它们的子 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Shell中是有意义的,使用export后,变量被设置为全局变量,这 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;时可以被其它子Shell所识别 <BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local标记变量为局部的(如只能被函数内部使用),用法为: <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local&nbsp;name <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local&nbsp;name&nbsp;=&nbsp;value <BR>&nbsp;&nbsp;(3)&nbsp;readonly. <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;指定变量为只读,执行后,改变量不能被再次赋值,用法为: <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readonly&nbsp;name <BR><BR>2.位置变量或Shell参数 <BR>&nbsp;&nbsp;Bash&nbsp;Shell在解释用户命令时,将把命令行的第一个子作为命令,而其它字作为 <BR>参数通过位置变量传递给程序.$1,...,$9分别代表第一,...,九个参数.其中1-9 <BR>是真正的参数名,"$"符只是用来标识变量的替换. <BR>&nbsp;&nbsp;位置变量$0指命令对应的可执行名. <BR>&nbsp;&nbsp;其它的还有: <BR>&nbsp;&nbsp;$#&nbsp;&nbsp;&nbsp;&nbsp;送给命令的参数个数 <BR>&nbsp;&nbsp;$@&nbsp;&nbsp;&nbsp;&nbsp;所有的参数,每个用双括号括起 <BR>&nbsp;&nbsp;$*&nbsp;&nbsp;&nbsp;&nbsp;所有的参数,用双括号括起 <BR>3.与Shell有关的变量 <BR>&nbsp;(1)&nbsp;Shell自身设置的一些常用变量: <BR>&nbsp;&nbsp;&nbsp;LINENO&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;正在执行的命令在脚本中的行号 <BR>&nbsp;&nbsp;&nbsp;PWD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;用户当前目录的全名 <BR>&nbsp;&nbsp;&nbsp;OLDPWD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;最近一次执行cd之前,用户当前目录的全名 <BR>&nbsp;&nbsp;&nbsp;PPID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;父进程ID <BR>&nbsp;&nbsp;&nbsp;$&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当前进程ID <BR>&nbsp;&nbsp;&nbsp;RANDOM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;随机数(范围0-32767) <BR>&nbsp;&nbsp;&nbsp;SECONDS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Bash&nbsp;Shell的运行时间,单位是秒 <BR>&nbsp;&nbsp;&nbsp;REPLY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select和read命令使用,以后会讲到 <BR>&nbsp;&nbsp;&nbsp;OPTARG <BR>&nbsp;&nbsp;&nbsp;ORTIND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这两个变量由getopt命令设置 <BR>&nbsp;&nbsp;&nbsp;UID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当前用户的User&nbsp;ID <BR>&nbsp;&nbsp;&nbsp;_&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上一条命令使用的最后一个参数 <BR>&nbsp;(2)&nbsp;影响Shell行为的一些常用环境变量: <BR>&nbsp;&nbsp;&nbsp;PATH&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;命令搜索路径,以冒号为分隔符.注意与DOS下不同的是, <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当前目录不在系统路径里 <BR>&nbsp;&nbsp;&nbsp;HOME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;用户home目录的路径名,是cd命令的默认参数 <BR>&nbsp;&nbsp;&nbsp;COLUMNS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;定义了命令编辑模式下可使用命令行的长度 <BR>&nbsp;&nbsp;&nbsp;EDITOR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;默认的行编辑器 <BR>&nbsp;&nbsp;&nbsp;VISUAL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;默认的可视编辑器 <BR>&nbsp;&nbsp;&nbsp;FCEDIT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;命令fc使用的编辑器 <BR>&nbsp;&nbsp;&nbsp;HISTFILE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;命令历史文件 <BR>&nbsp;&nbsp;&nbsp;HISTSIZE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;命令历史文件中最多可包含的命令条数 <BR>&nbsp;&nbsp;&nbsp;HISTFILESIZE&nbsp;命令历史文件中包含的最大行数 <BR>&nbsp;&nbsp;&nbsp;IFS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;定义SHELL使用的分隔符 <BR>&nbsp;&nbsp;&nbsp;LOGNAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;用户登录名 <BR>&nbsp;&nbsp;&nbsp;MAIL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;指向一个需要SHELL监视其修改时间的文件.当该文件修改后, <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SHELL将发消息You&nbsp;hava&nbsp;mail给用户 <BR>&nbsp;&nbsp;&nbsp;MAILCHECK&nbsp;&nbsp;&nbsp;&nbsp;SHELL检查MAIL文件的周期,单位是秒 <BR>&nbsp;&nbsp;&nbsp;MAILPATH&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;功能与MAIL类似.但可以用一组文件,以冒号分隔,每个文件后 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;可跟一个问号和一条发向用户的消息 <BR>&nbsp;&nbsp;&nbsp;SHELL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SHELL的路径名 <BR>&nbsp;&nbsp;&nbsp;TERM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;终端类型 <BR>&nbsp;&nbsp;&nbsp;TMOUT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SHELL自动退出的时间,单位为秒,若设为0则禁止SHELL自动退出 <BR>&nbsp;&nbsp;&nbsp;PROMPT_COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;指定在主命令提示符前应执行的命令 <BR>&nbsp;&nbsp;&nbsp;PS1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;主命令提示符 <BR>&nbsp;&nbsp;&nbsp;PS2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;二级命令提示符,命令执行过程中要求输入数据时用 <BR>&nbsp;&nbsp;&nbsp;PS3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select的命令提示符 <BR>&nbsp;&nbsp;&nbsp;PS4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;调试命令提示符 <BR>&nbsp;&nbsp;&nbsp;MANPATH&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;寻找手册页的路径,以冒号分隔 <BR>&nbsp;&nbsp;&nbsp;LD_LIBRARY_PATH&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;寻找库的路径,以冒号分隔 <BR><BR>from: <A href="http://www.7880.com/Info/Article-cbb4700.html">http://www.7880.com/Info/Article-cbb4700.html</A><img src ="http://www.blogjava.net/weidagang2046/aggbug/25373.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-12-25 15:43 <a href="http://www.blogjava.net/weidagang2046/articles/25373.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Getting started with awk</title><link>http://www.blogjava.net/weidagang2046/articles/25365.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 25 Dec 2005 06:57:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/25365.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/25365.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/25365.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/25365.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/25365.html</trackback:ping><description><![CDATA[<H2>Introduction</H2>
<UL>
<LI><I>awk</I> reads from a file or from its standard input, and outputs to its standard output. You will generally want to redirect that into a file, but that is not done in these examples just because it takes up space. awk does not get along with non-text files, like executables and FrameMaker files. If you need to edit those, use a binary editor like hexl-mode in emacs. 
<P></P>
<LI>The most frustrating thing about trying to learn awk is getting your program past the shell's parser. The proper way is to use single quotes around the program, like so: 
<P><B>&gt;</B><SAMP>awk '{print $0}' filename</SAMP> 
<P>The single quotes protect almost everything from the shell. In csh or tcsh, you still have to watch out for exclamation marks, but other than that, you're safe. 
<P></P>
<LI>The second most frustrating thing about trying to learn <I>awk</I> is the lovely error messages: <PRE>	awk '{print $0,}' filename
	awk: syntax error near line 1
	awk: illegal statement near line 1
</PRE><I>gawk</I> generally has better error messages. At least it tells you where in the line something went wrong: <PRE>	gawk '{print $0,}' filename
	gawk: cmd. line:1: {print $0,}
	gawk: cmd. line:1:           ^ parse error
</PRE>So, if you're having problems getting <I>awk</I> syntax correct, switch to <I>gawk</I> for a while. 
<P></P></LI></UL><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>
<HR>
<A name=basics></A>
<H2>Some basics:</H2>
<P>
<UL>
<LI>Awk recognizes the concepts of "file", "record", and "field". 
<LI>A file consists of records, which by default are the lines of the file. One line becomes one record. 
<LI>Awk operates on one record at a time. 
<LI>A record consists of fields, which by default are separated by any number of spaces or tabs. 
<LI>Field number 1 is accessed with $1, field 2 with $2, and so forth. $0 refers to the whole record. </LI></UL>
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>
<HR>
<A name=samples></A>
<H2>Some Samples</H2>Perhaps the quickest way of learning awk is to look at some sample programs. The one above will print the file in its entirety, just like <I>cat</I>(1). Here are some others, along with a quick description of what they do. 
<P><B>&gt;</B><SAMP>awk '{print $2,$1}' filename</SAMP> 
<P>will print the second field, then the first. All other fields are ignored. 
<P><B>&gt;</B><SAMP>awk '{print $1,$2,sin($3/$2)}' filename</SAMP> 
<P>will print the first and second fields, and then the sine of the third field divided by the second. So, the second and third field had better be numbers. Awk has other built in math functions like sine; read the manpage to see which ones. 
<P>"I still say awk '{print $1}' a lot."<BR><CITE>the inventor of PERL, Larry Wall (lwall@netlabs.com)</CITE> 
<P>What if you don't want to apply the program to each line of the file? Say, for example, that you only wanted to process lines that had the first field greater than the second. The following program will do that: 
<P><B>&gt;</B><SAMP>awk '$1 &gt; $2 {print $1,$2,$1-$2}' filename</SAMP> 
<P><A name=patact></A>The part outside the curly braces is called the "pattern", and the part inside is the "action". The comparison operators include the ones from C: <PRE>	== != &lt; &gt; &lt;= &gt;= ?:
</PRE>If no pattern is given, then the action applies to all lines. This fact was used in the sample programs above. If no action is given, then the entire line is printed. If "print" is used all by itself, the entire line is printed. Thus, the following are equivalent: <PRE>	awk '$1 &gt; $2'           filename
	awk '$1 &gt; $2{print}'    filename
	awk '$1 &gt; $2{print $0}' filename
</PRE>The various fields in a line can also be treated as strings instead of numbers. To compare a field to a string, use the following method: 
<P><B>&gt;</B><SAMP>awk '$1=="foo"{print $2}' filename</SAMP> 
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>
<HR>
<A name=regexp></A>
<H3>Using regular expressions</H3>What if you want lines in which a certain string is found? Just put a regular expression (in the manner of <I>egrep</I>(1) ) into the pattern, like so: 
<P><B>&gt;</B><SAMP>awk '/foo.*bar/{print $1,$3}' filename</SAMP> 
<P>This will print all lines containing the word "foo" and then later the word "bar". If you want only those lines where "foo" occurs in the second field, use the ~ ("contains") operator: 
<P><B>&gt;</B><SAMP>awk '$2~/foo/{print $3,$1}' filename</SAMP> 
<P><A name=negop></A>If you want lines where "foo" does not occur in the second field, use the negated ~ operator, !~ 
<P><B>&gt;</B><SAMP>awk '$2!~/foo/{print $3,$1}' filename</SAMP> 
<P>This operator can be read as "does not contain". 
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>
<HR>
<A name=bool></A>
<H3>Booleans</H3>You can produce complicated patterns with the boolean operators from C, which are ! for "not", &amp;&amp; for "and", and || for "or". Parentheses can be used for grouping. 
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>
<HR>
<A name=startend></A>
<H3>Start and End</H3>There are three special forms of patterns that do not fit the above descriptions. One is the start-end pair of regular expressions. For example, to print all lines between and including lines that contained "foo" and "bar", you would use 
<P><B>&gt;</B><SAMP>awk '/foo/,/bar/' filename</SAMP> 
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>
<HR>
<A name=begend></A>
<H3>Begin and End</H3>The other two special forms are similar; they are the BEGIN and END patterns. Any action associated with the BEGIN pattern will happen before any line-by-line processing is done. Actions with the END pattern will happen after all lines are processed. 
<P><A name=mult></A>But how do you put more than one pattern-action pair into an awk program? There are several choices. 
<OL>
<LI>One is to just mash them together, like so: 
<P><B>&gt;</B><PRE>awk 'BEGIN{print"fee"} $1=="foo"{print"fi"}
     END{print"fo fum"}' filename</PRE>
<P></P>
<LI>Another choice is to put the program into a file, like so: <PRE>	BEGIN{print"fee"}
	$1=="foo"{print"fi"}
	END{print"fo fum"}
</PRE>Let's say that's in the file <I>giant.awk</I>. Now, run it using the "-f" flag to awk: 
<P><B>&gt;</B><SAMP>awk -f giant.awk filename</SAMP> 
<P></P>
<LI>A third choice is to create a file that calls awk all by itself. The following form will do the trick: <PRE>	#!/usr/bin/awk -f
	BEGIN{print"fee"}
	$1=="foo"{print"fi"}
	END{print"fo fum"} 
</PRE></LI></OL>If we call this file <I>giant2.awk</I>, we can run it by first giving it execute permissions, 
<P><B>&gt;</B><SAMP>chmod u+x giant2.awk</SAMP> 
<P>and then just call it like so: 
<P><B>&gt;</B><SAMP>./giant2.awk filename</SAMP> 
<P>awk has variables that can be either real numbers or strings. For example, the following code prints a running total of the fifth column: 
<P><B>&gt;</B><SAMP>awk '{print x+=$5,$0 }' filename</SAMP> 
<P>This can be used when looking at file sizes from an "ls -l". It is also useful for balancing one's checkbook, if the amount of the check is kept in one column. 
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>
<HR>
<A name=awkvars></A>
<H3>Awk variables</H3>awk variables are initialized to either zero or the empty string the first time they are used. Which one depends on how they are used, of course. 
<P>Variables are also useful for keeping intermediate values. This example also introduces the use of semicolons for separating statements: 
<P><B>&gt;</B><SAMP>awk '{d=($2-($1-4));s=($2+$1);print d/sqrt(s),d*d/s }' filename</SAMP> 
<P>Note that the final statement, a "print" in this case, does not need a semicolon. It doesn't hurt to put it in, though. 
<P>
<UL>
<LI>Integer variables can be used to refer to fields. If one field contains information about which other field is important, this script will print only the important field: 
<P><B>&gt;</B><SAMP>awk '{imp=$1; print $imp }' filename</SAMP> 
<P></P>
<LI>The special variable <VAR>NF</VAR> tells you how many fields are in this record. This script prints the first and last field from each record, regardless of how many fields there are: 
<P><B>&gt;</B><SAMP>awk '{print $1,$NF }' filename</SAMP> 
<P></P>
<LI>The special variable <VAR>NR</VAR> tells you which record this is. It is incremented each time a new record is read in. This gives a simple way of adding line numbers to a file: 
<P><B>&gt;</B><SAMP>awk '{print NR,$0 }' filename</SAMP> 
<P></P></LI></UL>Of course, there are a myriad of other ways to put line numbers on a file using the various UNIX utilities. This is left as an exercise for the reader. 
<P>
<UL>
<LI>The special variable <VAR>FS</VAR> (Field Separator) determines how awk will split up each record into fields. This variable can be set on the command line. For example, <I>/etc/passwd</I> has its fields separated by colons. 
<P><B>&gt;</B><SAMP>awk -F: '{print $1,$3 }' /etc/passwd</SAMP> </P>This variable can actually be set to any regular expression, in the manner of <I>egrep</I>(1). </LI></UL>
<P>The various fields are also variables, and you can assign things to them. If you wanted to delete the 10th field from each line, you could do it by printing fields 1 through 9, and then from 11 on using a for-loop (see below). But, this will do it very easily: 
<P><B>&gt;</B><SAMP>awk '{$10=""; print }' filename</SAMP> 
<P><A name=awkfwd></A>In many ways, awk is like C. The "for", "while", "do-while", and "if" constructs all exist. Statements can be grouped with curly braces. This script will print each field of each record on its own line. 
<P><B>&gt;</B><SAMP>awk '{for(i=1;i&lt;=NF;i++) print $i }' filename</SAMP> 
<P>If you want to produce format that is a little better formatted than the "print" statement gives you, you can use "printf" just like in C. Here is an example that treats the first field as a string, and then does some numeric stuff 
<P><B>&gt;</B><SAMP>awk '{printf("%s %03d %02d %.15g\n",$1,$2,$3,$3/$2); }' filename</SAMP> 
<P>Note that with printf, you need the explicit newline character. 
<P>We can use "printf" to print stuff without the newline, which is useful in a for loop. This script prints each record with each of its fields reversed. Ok, so it isn't very useful. 
<P><B>&gt;</B><SAMP>awk '{for(i=NF;i &gt; 0;i--) printf("%s",$i); printf("\n"); }' filename</SAMP> 
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>
<HR>
<A name=awkarr></A>
<H3>Awk Arrays</H3>awk has arrays, but they are only indexed by strings. This can be very useful, but it can also be annoying. For example, we can count the frequency of words in a document (ignoring the icky part about printing them out): 
<P><B>&gt;</B><SAMP>awk '{for(i=1;i &lt;=NF;i++) freq[$i]++ }' filename</SAMP> 
<P>The array will hold an integer value for each word that occurred in the file. Unfortunately, this treats "foo", "Foo", and "foo," as different words. Oh well. How do we print out these frequencies? awk has a special "for" construct that loops over the values in an array. This script is longer than most command lines, so it will be expressed as an executable script: <PRE>	#!/usr/bin/awk -f
	{for(i=1;i &lt;=NF;i++) freq[$i]++ }
        END{for(word in freq) print word, freq[word]  }
</PRE>This loop-over-an-array seems to go in no particular order. Thus, the output from a program like this must often be piped through <I>sort</I>(1) in order to be useful. 
<P>Multi-dimensional arrays are implemented in an odd way. The given indices are concatenated together (with a special separator) to get one string, and it is used as the index. This program will print the word-pair frequencies: <PRE>	#!/usr/bin/awk -f
	{for(i=1;i &lt; NF;i++) freq[$i,$(i+1)]++ }
        END{for(words in freq) print words, freq[words]  }
</PRE>Unfortunately, this will print out the separator, which is by default not a common character. You can change this by assigning something logical like a space to the variable SUBSEP using nawk or gawk (it's not allowed in plain awk). <PRE>	#!/usr/bin/awk -f
	BEGIN{SUBSEP=""}
	{for(i=1;i &lt; NF;i++) freq[$i,$(i+1)]++}
        END{for(words in freq) print words, freq[words] }
</PRE>Unfortunately (that word seems to occur a lot when talking about awk arrays), this doesn't let you refer to the indices individually. The secret to this it to use the "split" function, which breaks a string up into an array. <PRE> 
	#!/usr/bin/awk -f
	BEGIN{SUBSEP="" }
	{for(i=1;i &lt; NF;i++) freq[$i,$(i+1)]++}
        END{ for(words in freq)
	       {
	       split(words,word,SUBSEP); 
	       print word[1], freq[words],word[2];
	       } 
	   }	
</PRE>
<HR>

<P><A name=awkscr></A>When you're using an awk script in a file, you can break your program across multiple lines to make it easier to read. Comments are started the same way as in <I>sh</I> programming, with a # <PRE>	#!/usr/bin/awk -f
	# this program prints the frequencies of word pairs
	BEGIN{SUBSEP=""} # set the index separator 
	                 # to a nice character
	{for(i=1;i &lt; NF;i++) freq[$i,$(i+1)]++}
        END{ for(words in freq)
	       {
	# just to show we can put a comment in here.
	       split(words,word,SUBSEP); # or here
	       print word[1], freq[words],word[2];
	       } 
	   }		
</PRE>You can use awk to create text, as well as just process existing text. It is useful for quickly generating tables of function values, without the hassle of compiling a C program. For example, it can show that sin(x)/x approaches 1 as x approaches zero: 
<P><B>&gt;</B><SAMP>awk '{x=1.0/NR; print x,sin(x)/x;}'</SAMP> 
<P>will print a new value each time it reads a new line. So, you can hit return until you have all the values you need. Alternately, if you need a set number of values, you can do 
<P><B>&gt;</B><SAMP>awk 'BEGIN{for(i=1;i &lt;=30;i++){x=1.0/i;print x,sin(x)/x;}}' /dev/null</SAMP> 
<P>where 30 is the set number of values. 
<P><A name=awkcc></A>It seems twisted*, but awk can be used to generate C code that one doesn't want to type by hand. For example, this script will generate an explicit 3x3 matrix multiplication routine: 
<P><PRE>gawk 'BEGIN{
        for(i=0;i&lt;3;i++)
          for(j=0;j&lt;3;j++){
            printf("d[%d][%d]=",i,j);
            for(k=0;k&lt;3;k++){
              printf("l[%d][%d]*r[%d][%d]%s",
                     i,k,k,j,(k&lt;2)?"+":";\n");
            }
          }
      }'</PRE>
<P>
<P><I>* ok, maybe it is twisted. </I>
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<HR>
<A name=punct></A>
<H2>Punctuation guide:</H2>
<DL compact>
<DT>{} 
<DD>used around the action, and to group statements in the action. 
<P></P>
<DT>$ 
<DD>denotes a field. $1 is the first field, $0 is the whole record. 
<P></P>
<DT>~ 
<DD>the "contains" operator. "foobar"~"foo" is true. Strings only. 
<P></P>
<DT>!~ 
<DD>the "does not contain" operator. Strings only. 
<P></P>
<DT>== 
<DD>the equality operator. Works for numbers or strings 
<P></P>
<DT>&lt; &gt; &lt;= &gt;= != 
<DD>inequality operators. Work for numbers or strings. 
<P></P>
<DT># 
<DD>the begin-comment character 
<P></P>
<DT>, 
<DD>separates things in a "print" or "printf" statement. 
<P></P>
<DT>; 
<DD>separates statements. 
<P></P>
<DT>// 
<DD>used around a regular expression 
<P></P>
<DT>&amp;&amp; 
<DD>Boolean and 
<P></P>
<DT>|| 
<DD>Boolean or 
<P></P>
<DT>! 
<DD>boolean not 
<P></P>
<DT>() 
<DD>used for grouping Boolean expressions, passing arguments to functions, and around conditions for "for","while", etc. 
<P></P></DD></DL>
<P><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<HR>
<A name=bigexampl></A>
<H2>And now for a grand example:</H2><PRE># This awk program collects statistics on two 
# "random variables" and the relationships 
# between them. It looks only at fields 1 and 
# 2 by default Define the variables F and G 
# on the command line to force it to look at
# different fields.  For example: 
# awk -f stat_2o1.awk F=2 G=3 stuff.dat \
# F=3 G=5 otherstuff.dat
# or, from standard input: 
# awk -f stat_2o1.awk F=1 G=3
# It ignores blank lines, lines where either 
# one of the requested fields is empty, and 
# lines whose first field contains a number 
# sign. It requires only one pass through the
# data. This script works with vanilla awk 
# under SunOS 4.1.3.
BEGIN{
  F=1;
  G=2;
}
length($F) &gt; 0 &amp;&amp; \
length($G) &gt; 0 &amp;&amp; \
$1 !~/^#/ {
  sx1+= $F; sx2 += $F*$F;
  sy1+= $G; sy2 += $G*$G;
  sxy1+= $F*$G;
  if( N==0 ) xmax = xmin = $F;
  if( xmin &gt; $F ) xmin=$F;
  if( xmax &lt; $F ) xmax=$F;
  if( N==0 ) ymax = ymin = $G;
  if( ymin &gt; $G ) ymin=$G;
  if( ymax &lt; $G ) ymax=$G;
  N++;
}
 
END {
  printf("%d # N\n"   ,N   );
  if (N &lt;= 1) 
    {
    printf("What's the point?\n");
    exit 1;
    }
  printf("%g # xmin\n",xmin);
  printf("%g # xmax\n",xmax);
  printf("%g # xmean\n",xmean=sx1/N);
  xSigma = sx2 - 2 * xmean * sx1+ N*xmean*xmean;
  printf("%g # xvar\n"         ,xvar =xSigma/  N  );
  printf("%g # xvar unbiased\n",xvaru=xSigma/(N-1));
  printf("%g # xstddev\n"         ,sqrt(xvar ));
  printf("%g # xstddev unbiased\n",sqrt(xvaru));
  
  printf("%g # ymin\n",ymin);
  printf("%g # ymax\n",ymax);
  printf("%g # ymean\n",ymean=sy1/N);
  ySigma = sy2 - 2 * ymean * sy1+ N*ymean*ymean;
  printf("%g # yvar\n"         ,yvar =ySigma/  N  );
  printf("%g # yvar unbiased\n",yvaru=ySigma/(N-1));
  printf("%g # ystddev\n"         ,sqrt(yvar ));
  printf("%g # ystddev unbiased\n",sqrt(yvaru));
  if ( xSigma * ySigma &lt;= 0 )
    r=0;
  else 
    r=(sxy1 - xmean*sy1- ymean * sx1+ N * xmean * ymean)
      /sqrt(xSigma * ySigma);
  printf("%g # correlation coefficient\n", r);
  if( r &gt; 1 || r &lt; -1 )
    printf("SERIOUS ERROR! CORRELATION COEFFICIENT");
    printf(" OUTSIDE RANGE -1..1\n");

  if( 1-r*r != 0 )
    printf("%g # Student's T (use with N-2 degfreed)\n&amp;", \
      t=r*sqrt((N-2)/(1-r*r)) );
  else
    printf("0 # Correlation is perfect,");
    printf(" Student's T is plus infinity\n");
  b = (sxy1 - ymean * sx1)/(sx2 - xmean * sx1);
  a = ymean - b * xmean;
  ss=sy2 - 2*a*sy1- 2*b*sxy1 + N*a*a + 2*a*b*sx1+ b*b*sx2 ;
  ss/= N-2;
  printf("%g # a = y-intercept\n", a);
  printf("%g # b = slope\n"      , b); 
  printf("%g # s^2 = unbiased estimator for sigsq\n",ss);
  printf("%g + %g * x # equation ready for cut-and-paste\n",a,b);
  ra = sqrt(ss * sx2 / (N * xSigma));
  rb = sqrt(ss       / (    xSigma));
  printf("%g # radius of confidence interval ");
  printf("for a, multiply by t\n",ra);
  printf("%g # radius of confidence interval ");
  printf("for b, multiply by t\n",rb);
}    

</PRE><A href="http://www.cs.hmc.edu/qref/awk.html#menu">back to the top</A> 
<P>This documentation was originally written by Andrew M. Ross. <BR><BR>from: <A href="http://www.cs.hmc.edu/qref/awk.html">http://www.cs.hmc.edu/qref/awk.html</A></P><img src ="http://www.blogjava.net/weidagang2046/aggbug/25365.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-12-25 14:57 <a href="http://www.blogjava.net/weidagang2046/articles/25365.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>vi常用命令</title><link>http://www.blogjava.net/weidagang2046/articles/23473.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 12 Dec 2005 06:29:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/23473.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/23473.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/23473.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/23473.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/23473.html</trackback:ping><description><![CDATA[n 重复上次 / 或 ? 命令 <BR>o 在当前行下开辟一新行 <BR>p 将用户缓冲区内容放到光标位置(参看P命令) <BR>r 替换当前字符 <BR>s 用一串字符替换当前字符 <BR>t 字符 光标移动至字符前 <BR>u 取消上次操作 <BR>w 光标移至下一字首 <BR>x 删除当前字符 <BR>yw 将当前字存入无名缓冲区，前面可加"x，表示存入名字为x的有名 <BR>缓冲区(x为a-z)，也可加数字表示存入的字数，以后可用P或p命 <BR>令取出 <BR>yy 将当前行存入无名缓冲区，用法参看yw <BR>{ 光标移动至前一段开头 <BR>| 光标移至行首，若前面加数字，则移到数字指定行的行首 <BR>} 光标移至下一段开头 <BR>在：提示符下，常用命令如下: <BR>：w 当前文件存盘 <BR>：w! 强制存盘 <BR>：w 文件 将内容写入指定文件 <BR>：w! 文件 强制写入指定文件 <BR>：x，y w 文件 将 x至 y 行写入指定文件中 <BR>：r 文件 将文件读到光标位置 <BR>：r ! 命令 将系统命令的输出读到光标位置 <BR>：q 退出编辑 <BR>：q! 强制退出 <BR>：x 与命令ZZ相同 <BR>：e 文件名 编辑另一文件 <BR>：e ! 重新编辑文件，放弃任何改变 <BR>：sh 执行sh，结束后回到编辑 <BR>：! 命令 执行命令后回到编辑 <BR>：n 编辑下一文件 <BR>：n 文件表 重新定义待编辑文件表 <BR>：set 设置 vi 的选项，例如 set nu 表示每行前显示行号，在选项前 <BR>加no则表示清除该选项，例如 set nonu 表示每行前不显示行 <BR>号，下面是一些常用的选项: <BR>ai 自动缩进 <BR>aw 编辑下一文件前自动存盘 <BR>ic 查找字符串时不区分大小写 <BR>nu 每行前显示行号 <BR>sm 输入)及}时显示与之配对的( 或 { <BR>slow 插入时延迟屏幕刷新 <BR>ws 使查找能绕过文件尾从头进行 <BR>wa 写文件之前不作对文件的检查 <img src ="http://www.blogjava.net/weidagang2046/aggbug/23473.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-12-12 14:29 <a href="http://www.blogjava.net/weidagang2046/articles/23473.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>GNU make 指南</title><link>http://www.blogjava.net/weidagang2046/articles/22739.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 06 Dec 2005 09:02:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/22739.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/22739.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/22739.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/22739.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/22739.html</trackback:ping><description><![CDATA[<P align=center><BIG><BIG><FONT color=#ff9900>GNU make 指南</FONT> </BIG></BIG></P>
<P align=right>翻译： 哈少 </P>
<P>译者按： 本文是一篇介绍 GNU Make 的文章，读完后读者应该基本掌握了 make 的用法。而 make 是所有想在 Unix （当然也包括 Linux ）系统上编程的用户必须掌握的工具。如果你写的程序中没有用到 make ，则说明你写的程序只是个人的练习程序，不具有任何实用的价值。也许这么说有点 儿偏激，但 make 实在是应该用在任何稍具规模的程序中的。希望本文可以为中国的 Unix 编程初学者提供一点儿有用的资料。中国的 Linux 用户除了学会安装红帽子以外， 实在应该尝试写一些有用的程序。个人想法，大家参考。 
<P>
<HR>
C-Scene 题目 #2<BR>多文件项目和 GNU Make 工具<BR>作者: 乔治富特 (Goerge Foot)<BR>电子邮件: george.foot@merton.ox.ac.uk<BR>Occupation: Student at Merton College, Oxford University, England<BR>职业:学生，默尔顿学院,牛津城大学,英格兰<BR>IRC匿名: gfoot<BR>
<HR>
拒绝承诺：作者对于任何因此而对任何事物造成的所有损害（你所拥有或不 拥有的实际的，抽象的，或者虚拟的）。所有的损坏都是你自己的责任，而 与我无关。<BR><BR>所有权： “多文件项目”部分属于作者的财产，版权归乔治富特１９９７年 五月至七月。其它部分属 CScene 财产，版权 CScene １９９７年，保留所有 版权。本 CScene 文章的分发，部分或全部，应依照所有其它 CScene 的文章 的条件来处理。<BR><BR>0) 介绍<BR>~~~~~~~~~~~~~~~<BR>本文将首先介绍为什么要将你的Ｃ源代码分离成几个合理的独立档案，什么时 候需要分，怎么才能分的好。然后将会告诉你 GNU Make 怎样使你的编译和连 接步骤自动化。对于其它 Make 工具的用户来说，虽然在用其它类似工具时要 做适当的调整，本文的内容仍然是非常有用的。如果对你自己的编程工具有怀 疑，可以实际的试一试，但请先阅读用户手册。<BR><BR>1) 多文件项目<BR>~~~~~~~~~~~~~~~~~~~~~~<BR>
<UL>1.1为什么使用它们?<BR><BR>首先，多文件项目的好处在那里呢？<BR>它们看起来把事情弄的复杂无比。又要 header 文件，又要 extern 声明，而且如果需要查找一个文件，你要在更多的文件里搜索。<BR><BR>但其实我们有很有力的理由支持我们把一个项目分解成小块。当你改动一行代码，编译器需要全部重新编译来生成一个新的可执行文件。但如果你的项目是分开在几个小文件里，当你改动其中一个文件的时候，别的源文件的目标文件(object files)已经存在，所以没有什么原因去重新编译它们。你所需要做的只是重现编译被改动过的那个文件，然后重新连接所有的目标文件罢了。在大型的项目中，这意味着从很长的（几分钟到几小时）重新编译缩短为十几，二十几秒的简单调整。<BR><BR>只要通过基本的规划，将一个项目分解成多个小文件可使你更加容易的找到一段代码。很简单，你根据代码的作用把你的代码分解到不同的文件里。当你要看一段代码时，你可以准确的知道在那个文件中去寻找它。<BR><BR>从很多目标文件生成一个程序包 (Library)比从一个单一的大目标文件生成要好的多。当然实际上这是否真是一个优势则是由你所用的系统来决定的。但是当使用 gcc/ld (一个 GNU C 编译／连接器) 把一个程序包连接到一个程序时，在连接的过程中，它会尝试不去连接没有使用到的部分。但它每次只能从程序包中把一个完整的目标文件排除在外。因此如果你参考一个程序包中某一个目标档中任何一个符号的话，那么这个目标文件整个都会被连接进来。要是一个程序包被非常充分的分解了的话，那么经连接后，得到的可执行文件会比从一个大目标文件组成的程序包连接得到的文件小得多。<BR><BR>又因为你的程序是很模块化的，文件之间的共享部分被减到最少，那就有很多好处——可以很容易的追踪到臭虫，这些模块经常是可以用在其它的项目里的，同时别人也可以更容易的理解你的一段代码是干 什么的。当然此外还有许多别的好处……<BR><BR>1.2 何时分解你的项目<BR><BR>很明显，把任何东西都分解是不合理的。象“世界，你们好”这样的简单程序根本就不能分，因为实在也没什么可分的。把用于测试用的小程序分解也是没什么意思的。但一般来说，当分解项目有助于布局、发展和易读性的时候，我都会采取它。在大多数的情况下，这都是适用的。（所谓“世界，你们好”，既 'hello world' ，只是一个介绍一种编程语言时惯用的范例程序，它会在屏幕上显示一行 'hello world' 。是最简单的程序。）<BR><BR>如果你需要开发一个相当大的项目，在开始前，应该考虑一下你将如何实现它，并且生成几个文件（用适当的名字）来放你的代码。当然，在你的项目开发的过程中，你可以建立新的文件，但如果你这么做的话，说明你可能改变了当初的想法，你应该想想是否需要对整体结构也进行相应的调整。<BR><BR>对于中型的项目，你当然也可以采用上述技巧，但你也可以就那么开始输入你的代码，当你的码多到难以管理的时候再把它们分解成不同的档案。但以我的经验来说，开始时在脑子里形成一个大概的方案，并且尽量遵从它，或在开发过程中，随着程序的需要而修改，会使开发变得更加容易。<BR><BR>1.3 怎样分解项目<BR><BR>先说明，这完全是我个人的意见，你可以（也许你真的会？）用别的方式来做。这会触动到有关编码风格的问题，而大家从来就没有停止过在这个问题上的争论。在这里我只是给出我自己喜欢的做法（同时也给出这么做的原因）：<BR>
<UL>i) 不要用一个 header 文件指向多个源码文件（例外：程序包 的 header 文件）。用一个 header定义一个源码文件的方式 会更有效，也更容易查寻。否则改变一个源文件的结构（并且 它的 header 文件）就必须重新编译好几个文件。<BR><BR>ii) 如果可以的话，完全可以用超过一个的 header 文件来指向同 一个源码文件。有时将不可公开调用的函数原型，类型定义 等等，从它们的Ｃ源码文件中分离出来是非常有用的。使用一 个 header 文件装公开符号，用另一个装私人符号意味着如果 你改变了这个源码文件的内部结构，你可以只是重新编译它而 不需要重新编译那些使用它的公开 header 文件的其它的源文 件。<BR><BR>iii) 不要在多个 header 文件中重复定义信息。 如果需要， 在其中一个 header 文件里 #include 另一个，但 是不要重复输入相同的 header 信息两次。原因是如果你以后改 变了这个信息，你只需要把它改变一次，不用搜索并改变另外一 个重复的信息。<BR><BR>iv) 在每一个源码文件里， #include 那些声明了源码文件中的符 号的所有 header 文件。这样一来，你在源码文件和 header 文件对某些函数做出的矛盾声明可以比较容易的被编译器发现。<BR><BR></UL>1.4 对于常见错误的注释<BR><BR>
<UL>a) 定义符 (Identifier) 在源码文件中的矛盾：在Ｃ里，变量和函数的缺 省状态是公用的。因此，任何Ｃ源码档案都可以引用存在于其它源 码档中的通用 (global) 函数和通用变量，既使这个档案没有那个变 量或函数的声明或原型。因此你必须保证在不同的两个档案里不能 用同一个符号名称，否则会有连接错误或者在编译时会有警告。<BR><BR>一种避免这种错误的方法是在公用的符号前加上跟其所在源文件有 关的前缀。比如：所有在 gfx.c 里的函数都加上前缀“gfx_”。如果 你很小心的分解你的程序，使用有意义的函数名称，并且不是过分 使用通用变量，当然这根本就不是问题。<BR><BR>要防止一个符号在它被定义的源文件以外被看到，可在它的定义前 加上关键字“static”。这对只在一个档案内部使用，其它档案都 都不会用到的简单函数是很有用的。 <BR><BR>b) 多次定义的符号： header 档会被逐字的替换到你源文件里 #include 的位置的。因此，如果 header 档被 #include 到一个以上的源文件 里，这个 header 档中所有的定义就会出现在每一个有关的源码文件 里。这会使它们里的符号被定义一次以上，从而出现连接错误（见 上）。<BR><BR>解决方法： 不要在 header 档里定义变量。你只需要在 header 档里声明它们然后在适当的Ｃ源码文件（应该 #include 那个 header 档的那个）里定义它们（一次）。对于初学者来说，定义和声明是 很容易混淆的。声明的作用是告诉编译器其所声明的符号应该存在， 并且要有所指定的类型。但是，它并不会使编译器分配贮存空间。 而定义的做用是要求编译器分配贮存空间。当做一个声明而不是做 定义的时候，在声明前放一个关键字“extern”。<BR><BR>例如，我们有一个叫“counter”的变量，如果想让它成为公用的， 我们在一个源码程序（只在一个里面）的开始定义它：“int counter;”，再在相关的 header 档里声明它：“extern int counter;”。<BR><BR>函数原型里隐含着 extern 的意思，所以不需顾虑这个问题。<BR><BR>c) 重复定义，重复声明，矛盾类型：<BR>请考虑如果在一个Ｃ源码文件中 #include 两个档 a.h 和 b.h， 而 a.h 又 #include 了 b.h 档（原因是 b.h 档定义了一些 a.h 需要的类型），会发生什么事呢？这时该Ｃ源码文件 #include 了 b.h 两次。因此每一个在 b.h 中的 #define 都发生了两次，每一 个声明发生了两次，等等。理论上，因为它们是完全一样的拷贝， 所以应该不会有什么问题，但在实际应用上，这是不符合Ｃ的语法 的，可能在编译时出现错误，或至少是警告。<BR><BR>解决的方法是要确定每一个 header 档在任一个源码文件中只被包 含了一次。我们一般是用预处理器来达到这个目的的。当我们进入 每一个 header 档时，我们为这个 header 档 #define 一个巨集 指令。只有在这个巨集指令没有被定义的前提下，我们才真正使用 该 header 档的主体。在实际应用上，我们只要简单的把下面一段 码放在每一个 header 档的开始部分：<BR><BR>#ifndef FILENAME_H<BR>#define FILENAME_H<BR><BR>然后把下面一行码放在最后：<BR><BR>#endif<BR><BR>用 header 档的档名（大写的）代替上面的 FILENAME_H，用底线 代替档名中的点。有些人喜欢在 #endif 加上注释来提醒他们这个 #endif 指的是什么。例如：<BR><BR>#endif /* #ifndef FILENAME_H */<BR><BR>我个人没有这个习惯，因为这其实是很明显的。当然这只是各人的 风格不同，无伤大雅。<BR><BR>你只需要在那些有编译错误的 header 档中加入这个技巧，但在所 有的 header 档中都加入也没什么损失，到底这是个好习惯。<BR><BR></UL>1.5 重新编译一个多文件项目<BR><BR>清楚的区别编译和连接是很重要的。编译器使用源码文件来产生某种 形式的目标文件(object files)。在这个过程中，外部的符号参考并 没有被解释或替换。然后我们使用连接器来连接这些目标文件和一些 标准的程序包再加你指定的程序包，最后连接生成一个可执行程序。 在这个阶段，一个目标文件中对别的文件中的符号的参考被解释，并 报告不能被解释的参考，一般是以错误信息的形式报告出来。<BR><BR>基本的步骤就应该是，把你的源码文件一个一个的编译成目标文件的格 式，最后把所有的目标文件加上需要的程序包连接成一个可执行文件。 具体怎么做是由你的编译器决定的。这里我只给出 gcc （GNU C 编译 器）的有关命令，这些有可能对你的非 gcc 编译器也适用。<BR><BR>gcc 是一个多目标的工具。它在需要的时候呼叫其它的元件（预处理 程序，编译器，组合程序，连接器）。具体的哪些元件被呼叫取决于 输入文件的类型和你传递给它的开关。<BR><BR>一般来说，如果你只给它Ｃ源码文件，它将预处理，编译，组合所有 的文件，然后把所得的目标文件连接成一个可执行文件（一般生成的 文件被命名为 a.out ）。你当然可以这么做，但这会破坏很多我们 把一个项目分解成多个文件所得到的好处。<BR><BR>如果你给它一个 -c 开关，gcc 只把给它的文件编译成目标文件， 用源码文件的文件名命名但把其后缀由“.c”或“.cc”变成“.o”。 如果你给它的是一列目标文件， gcc 会把它们连接成可执行文件， 缺省文件名是 a.out 。你可以改变缺省名，用开关 -o 后跟你指定 的文件名。<BR><BR>因此，当你改变了一个源码文件后，你需要重新编译它： 'gcc -c filename.c' 然后重新连接你的项目： 'gcc -o exec_filename *.o'。 如果你改变了一个 header 档，你需要重新编译所有 #include 过 这个档的源码文件，你可以用 'gcc -c file1.c file2.c file3.c' 然后象上边一样连接。<BR><BR>当然这么做是很繁琐的，幸亏我们有些工具使这个步骤变得简单。 本文的第二部分就是介绍其中的一件工具：GNU Make 工具。<BR><BR>（好家伙，现在才开始见真章。您学到点儿东西没？）<BR><BR></UL>2) GNU Make 工具<BR>~~~~~~~~~~~~~~~~<BR>
<UL>2.1 基本 makefile 结构<BR><BR>GNU Make 的主要工作是读进一个文本文件， makefile 。这个文 件里主要是有关哪些文件（‘target’目的文件）是从哪些别的 文件（‘dependencies’依靠文件）中产生的，用什么命令来进行 这个产生过程。有了这些信息， make 会检查磁碟上的文件，如果 目的文件的时间戳（该文件生成或被改动时的时间）比至少它的一 个依靠文件旧的话， make 就执行相应的命令，以便更新目的文件。 （目的文件不一定是最后的可执行档，它可以是任何一个文件。）<BR><BR>makefile 一般被叫做“makefile”或“Makefile”。当然你可以 在 make 的命令行指定别的文件名。如果你不特别指定，它会寻 找“makefile”或“Makefile”，因此使用这两个名字是最简单 的。<BR><BR>一个 makefile 主要含有一系列的规则，如下：<BR><BR><TARGET>: <DEPENDENCY><DEPENDENCY>...<BR>(tab)&lt;command&gt;<BR>(tab)&lt;command&gt;<BR>.<BR>.<BR>.<BR><BR>例如，考虑以下的 makefile ：<BR><BR>=== makefile 开始 ===<BR>myprog : foo.o bar.o<BR>&nbsp;&nbsp;gcc foo.o bar.o -o myprog<BR><BR>foo.o : foo.c foo.h bar.h<BR>&nbsp;&nbsp;gcc -c foo.c -o foo.o<BR><BR>bar.o : bar.c bar.h<BR>&nbsp;&nbsp;gcc -c bar.c -o bar.o<BR>=== makefile 结束 ===<BR><BR>这是一个非常基本的 makefile —— make 从最上面开始，把上 面第一个目的，‘myprog’，做为它的主要目标（一个它需要保 证其总是最新的最终目标）。给出的规则说明只要文件‘myprog’ 比文件‘foo.o’或‘bar.o’中的任何一个旧，下一行的命令将 会被执行。<BR><BR>但是，在检查文件 foo.o 和 bar.o 的时间戳之前，它会往下查 找那些把 foo.o 或 bar.o 做为目标文件的规则。它找到的关于 foo.o 的规则，该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 它从下面再找不到生成这些依靠文件的规则，它就开始检查磁碟 上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新，命令 'gcc -o foo.o foo.c' 将会执行，从而更新 文件 foo.o 。 <BR><BR>接下来对文件 bar.o 做类似的检查，依靠文件在这里是文件 bar.c 和 bar.h 。<BR><BR>现在， make 回到‘myprog’的规则。如果刚才两个规则中的任 何一个被执行，myprog 就需要重建（因为其中一个 .o 档就会比 ‘myprog’新），因此连接命令将被执行。<BR><BR>希望到此，你可以看出使用 make 工具来建立程序的好处——前 一章中所有繁琐的检查步骤都由 make 替你做了：检查时间戳。 你的源码文件里一个简单改变都会造成那个文件被重新编译（因 为 .o 文件依靠 .c 文件），进而可执行文件被重新连接（因为 .o 文件被改变了）。其实真正的得益是在当你改变一个 header 档的时候——你不再需要记住那个源码文件依靠它，因为所有的 资料都在 makefile 里。 make 会很轻松的替你重新编译所有那 些因依靠这个 header 文件而改变了的源码文件，如有需要，再 进行重新连接。<BR><BR>当然，你要确定你在 makefile 中所写的规则是正确无误的，只 列出那些在源码文件中被 #include 的 header 档……<BR><BR>2.2 编写 make 规则 (Rules)<BR><BR>最明显的（也是最简单的）编写规则的方法是一个一个的查 看源码文件，把它们的目标文件做为目的，而Ｃ源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把其它被这些 header 档 #include 的 header 档也列为依靠文件，还有那些被 包括的文件所包括的文件……然后你会发现要对越来越多的文件 进行管理，然后你的头发开始脱落，你的脾气开始变坏，你的脸 色变成菜色，你走在路上开始跟电线杆子碰撞，终于你捣毁你的 电脑显示器，停止编程。到低有没有些容易点儿的方法呢？<BR><BR>当然有！向编译器要！在编译每一个源码文件的时候，它实在应 该知道应该包括什么样的 header 档。使用 gcc 的时候，用 -M 开关，它会为每一个你给它的Ｃ文件输出一个规则，把目标文件 做为目的，而这个Ｃ文件和所有应该被 #include 的 header 文 件将做为依靠文件。注意这个规则会加入所有 header 文件，包 括被角括号(`&lt;', `&gt;')和双引号(`"')所包围的文件。其实我们可以 相当肯定系统 header 档（比如 stdio.h, stdlib.h 等等）不会 被我们更改，如果你用 -MM 来代替 -M 传递给 gcc，那些用角括 号包围的 header 档将不会被包括。（这会节省一些编译时间）<BR><BR>由 gcc 输出的规则不会含有命令部分；你可以自己写入你的命令 或者什么也不写，而让 make 使用它的隐含的规则（参考下面的 2.4 节）。<BR><BR>2.3 Makefile 变量<BR><BR>上面提到 makefiles 里主要包含一些规则。它们包含的其它的东 西是变量定义。<BR><BR>makefile 里的变量就像一个环境变量(environment variable)。 事实上，环境变量在 make 过程中被解释成 make 的变量。这些 变量是大小写敏感的，一般使用大写字母。它们可以从几乎任何 地方被引用，也可以被用来做很多事情，比如：<BR>
<UL>i) 贮存一个文件名列表。在上面的例子里，生成可执行文件的 规则包含一些目标文件名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc 做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名，加入新的目标 文件会变的简单而且较不易出错。<BR><BR>ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系 统里，或者如果你想使用一个不同的编译器，你必须将所 有使用编译器的地方改成用新的编译器名。但是如果使用一 个变量来代替编译器名，那么你只需要改变一个地方，其 它所有地方的命令名就都改变了。<BR><BR>iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项（例如 -Wall -O -g）；如果你把这组选项存 入一个变量，那么你可以把这个变量放在所有呼叫编译器 的地方。而当你要改变选项的时候，你只需在一个地方改 变这个变量的内容。<BR></UL>要设定一个变量，你只要在一行的开始写下这个变量的名字，后 面跟一个 = 号，后面跟你要设定的这个变量的值。以后你要引用 这个变量，写一个 $ 符号，后面是围在括号里的变量名。比如在 下面，我们把前面的 makefile 利用变量重写一遍：<BR><BR>=== makefile 开始 ===<BR>OBJS = foo.o bar.o<BR>CC = gcc<BR>CFLAGS = -Wall -O -g<BR><BR>myprog : $(OBJS)<BR>&nbsp;&nbsp;$(CC) $(OBJS) -o myprog<BR><BR>foo.o : foo.c foo.h bar.h<BR>&nbsp;&nbsp;$(CC) $(CFLAGS) -c foo.c -o foo.o<BR><BR>bar.o : bar.c bar.h<BR>&nbsp;&nbsp;$(CC) $(CFLAGS) -c bar.c -o bar.o<BR>=== makefile 结束 ===<BR><BR>还有一些设定好的内部变量，它们根据每一个规则内容定义。三个 比较有用的变量是 $@, $&lt; 和 $^ （这些变量不需要括号括住）。 $@ 扩展成当前规则的目的文件名， $&lt; 扩展成依靠列表中的第 一个依靠文件，而 $^ 扩展成整个依靠的列表（除掉了里面所有重 复的文件名）。利用这些变量，我们可以把上面的 makefile 写成：<BR><BR>=== makefile 开始 ===<BR>OBJS = foo.o bar.o<BR>CC = gcc<BR>CFLAGS = -Wall -O -g<BR><BR>myprog : $(OBJS)<BR>&nbsp;&nbsp;$(CC) $^ -o $@<BR><BR>foo.o : foo.c foo.h bar.h<BR>&nbsp;&nbsp;$(CC) $(CFLAGS) -c $&lt; -o $@<BR><BR>bar.o : bar.c bar.h<BR>&nbsp;&nbsp;$(CC) $(CFLAGS) -c $&lt; -o $@<BR>=== makefile 结束 ===<BR><BR>你可以用变量做许多其它的事情，特别是当你把它们和函数混合 使用的时候。如果需要更进一步的了解，请参考 GNU Make 手册。 ('man make', 'man makefile')<BR><BR>2.4 隐含规则 (Implicit Rules)<BR><BR>请注意，在上面的例子里，几个产生 .o 文件的命令都是一样的。 都是从 .c 文件和相关文件里产生 .o 文件，这是一个标准的步 骤。其实 make 已经知道怎么做——它有一些叫做隐含规则的内 置的规则，这些规则告诉它当你没有给出某些命令的时候，应该 怎么办。<BR><BR>如果你把生成 foo.o 和 bar.o 的命令从它们的规则中删除， make 将会查找它的隐含规则，然后会找到一个适当的命令。它的命令会 使用一些变量，因此你可以按照你的想法来设定它：它使用变量 CC 做为编译器（象我们在前面的例子），并且传递变量 CFLAGS （给 C 编译器，C++ 编译器用 CXXFLAGS ），CPPFLAGS （ C 预 处理器旗标）， TARGET_ARCH （现在不用考虑这个），然后它加 入旗标 '-c' ，后面跟变量 $&lt; （第一个依靠名），然后是旗 标 '-o' 跟变量 $@ （目的文件名）。一个Ｃ编译的具体命令将 会是：<BR><BR>$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $&lt; -o $@<BR><BR>当然你可以按照你自己的需要来定义这些变量。这就是为什么用 gcc 的 -M 或 -MM 开关输出的码可以直接用在一个 makefile 里。<BR><BR>2.5 假象目的 (Phony Targets)<BR><BR>假设你的一个项目最后需要产生两个可执行文件。你的主要目标 是产生两个可执行文件，但这两个文件是相互独立的——如果一 个文件需要重建，并不影响另一个。你可以使用“假象目的”来 达到这种效果。一个假象目的跟一个正常的目的几乎是一样的， 只是这个目的文件是不存在的。因此， make 总是会假设它需要 被生成，当把它的依赖文件更新后，就会执行它的规则里的命令 行。<BR><BR>如果在我们的 makefile 开始处输入：<BR><BR>all : exec1 exec2<BR><BR>其中 exec1 和 exec2 是我们做为目的的两个可执行文件。 make 把这个 'all' 做为它的主要目的，每次执行时都会尝试把 'all' 更新。但既然这行规则里没有哪个命令来作用在一个叫 'all' 的 实际文件（事实上 all 并不会在磁碟上实际产生），所以这个规 则并不真的改变 'all' 的状态。可既然这个文件并不存在，所以 make 会尝试更新 all 规则，因此就检查它的依靠 exec1, exec2 是否需要更新，如果需要，就把它们更新，从而达到我们的目的。 <BR><BR>假象目的也可以用来描述一组非预设的动作。例如，你想把所有由 make 产生的文件删除，你可以在 makefile 里设立这样一个规则：<BR><BR>veryclean :<BR>&nbsp;&nbsp;rm *.o<BR>&nbsp;&nbsp;rm myprog<BR><BR>前提是没有其它的规则依靠这个 'veryclean' 目的，它将永远 不会被执行。但是，如果你明确的使用命令 'make veryclean' ， make 会把这个目的做为它的主要目标，执行那些 rm 命令。<BR><BR>如果你的磁碟上存在一个叫 veryclean 文件，会发生什么事？这 时因为在这个规则里没有任何依靠文件，所以这个目的文件一定是 最新的了（所有的依靠文件都已经是最新的了），所以既使用户明 确命令 make 重新产生它，也不会有任何事情发生。解决方法是标 明所有的假象目的（用 .PHONY），这就告诉 make 不用检查它们 是否存在于磁碟上，也不用查找任何隐含规则，直接假设指定的目 的需要被更新。在 makefile 里加入下面这行包含上面规则的规则：<BR><BR>.PHONY : veryclean<BR><BR>就可以了。注意，这是一个特殊的 make 规则，make 知道 .PHONY 是一个特殊目的，当然你可以在它的依靠里加入你想用的任何假象 目的，而 make 知道它们都是假象目的。<BR><BR>2.6 函数 (Functions)<BR><BR>makefile 里的函数跟它的变量很相似——使用的时候，你用一个 $ 符号跟开括号，函数名，空格后跟一列由逗号分隔的参数，最后 用关括号结束。例如，在 GNU Make 里有一个叫 'wildcard' 的函 数，它有一个参数，功能是展开成一列所有符合由其参数描述的文 件名，文件间以空格间隔。你可以像下面所示使用这个命令：<BR><BR>SOURCES = $(wildcard *.c)<BR><BR>这行会产生一个所有以 '.c' 结尾的文件的列表，然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。<BR><BR>另一个有用的函数是 patsubst （ patten substitude, 匹配替 换的缩写）函数。它需要３个参数——第一个是一个需要匹配的 式样，第二个表示用什么来替换它，第三个是一个需要被处理的 由空格分隔的字列。例如，处理那个经过上面定义后的变量，<BR><BR>OBJS = $(patsubst %.c,%.o,$(SOURCES))<BR><BR>这行将处理所有在 SOURCES 字列中的字（一列文件名），如果它的 结尾是 '.c' ，就用 '.o' 把 '.c' 取代。注意这里的 % 符号将匹 配一个或多个字符，而它每次所匹配的字串叫做一个‘柄’(stem) 。 在第二个参数里， % 被解读成用第一参数所匹配的那个柄。<BR><BR>2.7 一个比较有效的 makefile<BR><BR>利用我们现在所学的，我们可以建立一个相当有效的 makefile 。 这个 makefile 可以完成大部分我们需要的依靠检查，不用做太大 的改变就可直接用在大多数的项目里。<BR><BR>首先我们需要一个基本的 makefile 来建我们的程序。我们可以让 它搜索当前目录，找到源码文件，并且假设它们都是属于我们的项 目的，放进一个叫 SOURCES 的变量。这里如果也包含所有的 *.cc 文件，也许会更保险，因为源码文件可能是 C++ 码的。<BR><BR>SOURCES = $(wildcard *.c *.cc)<BR><BR>利用 patsubst ，我们可以由源码文件名产生目标文件名，我们需 要编译出这些目标文件。如果我们的源码文件既有 .c 文件，也有 .cc 文件，我们需要使用相嵌的 patsubst 函数呼叫：<BR><BR>OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))<BR><BR>最里面一层 patsubst 的呼叫会对 .cc 文件进行后缀替代，产生的结 果被外层的 patsubst 呼叫处理，进行对 .c 文件后缀的替代。<BR><BR>现在我们可以设立一个规则来建可执行文件：<BR><BR>myprog : $(OBJS)<BR>&nbsp;&nbsp;gcc -o myprog $(OBJS)<BR><BR>进一步的规则不一定需要， gcc 已经知道怎么去生成目标文件 (object files) 。下面我们可以设定产生依靠信息的规则：<BR><BR>depends : $(SOURCES)<BR>&nbsp;&nbsp;gcc -M $(SOURCES) &gt; depends<BR><BR>在这里如果一个叫 'depends' 的文件不存在，或任何一个源码文件 比一个已存在的 depends 文件新，那么一个 depends 文件会被生 成。depends 文件将会含有由 gcc 产生的关于源码文件的规则（注 意 -M 开关）。现在我们要让 make 把这些规则当做 makefile 档 的一部分。这里使用的技巧很像 C 语言中的 #include 系统——我 们要求 make 把这个文件 include 到 makefile 里，如下：<BR><BR>include depends<BR><BR>GNU Make 看到这个，检查 'depends' 目的是否更新了，如果没有， 它用我们给它的命令重新产生 depends 档。然后它会把这组（新） 规则包含进来，继续处理最终目标 'myprog' 。当看到有关 myprog 的规则，它会检查所有的目标文件是否更新——利用 depends 文件 里的规则，当然这些规则现在已经是更新过的了。<BR><BR>这个系统其实效率很低，因为每当一个源码文件被改动，所有的源码 文件都要被预处理以产生一个新的 'depends' 文件。而且它也不是 100% 的安全，这是因为当一个 header 档被改动，依靠信息并不会 被更新。但就基本工作来说，它也算相当有用的了。<BR><BR>2.8 一个更好的 makefile<BR><BR>这是一个我为我大多数项目设计的 makefile 。它应该可以不需要修 改的用在大部分项目里。我主要把它用在 djgpp 上，那是一个 DOS 版的 gcc 编译器。因此你可以看到执行的命令名、 'alleg' 程序包、 和 RM -F 变量都反映了这一点。<BR><BR>=== makefile 开始 ===<BR><BR>######################################<BR># <BR># Generic makefile <BR># <BR># by George Foot <BR># email: george.foot@merton.ox.ac.uk <BR># <BR># Copyright (c) 1997 George Foot <BR># All rights reserved. <BR># 保留所有版权 <BR># <BR># No warranty, no liability; <BR># you use this at your own risk. <BR># 没保险，不负责 <BR># 你要用这个，你自己担风险 <BR># <BR># You are free to modify and <BR># distribute this without giving <BR># credit to the original author. <BR># 你可以随便更改和散发这个文件 <BR># 而不需要给原作者什么荣誉。 <BR># （你好意思？） <BR># <BR>######################################<BR><BR>### Customising<BR># 用户设定<BR>#<BR># Adjust the following if necessary; EXECUTABLE is the target<BR># executable's filename, and LIBS is a list of libraries to link in<BR># (e.g. alleg, stdcx, iostr, etc). You can override these on make's<BR># command line of course, if you prefer to do it that way.<BR># <BR># 如果需要，调整下面的东西。 EXECUTABLE 是目标的可执行文件名， LIBS<BR># 是一个需要连接的程序包列表（例如 alleg, stdcx, iostr 等等）。当然你<BR># 可以在 make 的命令行覆盖它们，你愿意就没问题。<BR># <BR><BR>EXECUTABLE := mushroom.exe<BR>LIBS := alleg<BR><BR># Now alter any implicit rules' variables if you like, e.g.:<BR>#<BR># 现在来改变任何你想改动的隐含规则中的变量，例如<BR><BR>CFLAGS := -g -Wall -O3 -m486<BR>CXXFLAGS := $(CFLAGS)<BR><BR># The next bit checks to see whether rm is in your djgpp bin<BR># directory; if not it uses del instead, but this can cause (harmless)<BR># `File not found' error messages. If you are not using DOS at all,<BR># set the variable to something which will unquestioningly remove<BR># files.<BR>#<BR># 下面先检查你的 djgpp 命令目录下有没有 rm 命令，如果没有，我们使用<BR># del 命令来代替，但有可能给我们 'File not found' 这个错误信息，这没<BR># 什么大碍。如果你不是用 DOS ，把它设定成一个删文件而不废话的命令。<BR># （其实这一步在 UNIX 类的系统上是多余的，只是方便 DOS 用户。 UNIX<BR># 用户可以删除这５行命令。）<BR><BR>ifneq ($(wildcard $(DJDIR)/bin/rm.exe),)<BR>RM-F := rm -f<BR>else<BR>RM-F := del<BR>endif<BR><BR># You shouldn't need to change anything below this point.<BR>#<BR># 从这里开始，你应该不需要改动任何东西。（我是不太相信，太ＮＢ了！）<BR><BR>SOURCE := $(wildcard *.c) $(wildcard *.cc)<BR>OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))<BR>DEPS := $(patsubst %.o,%.d,$(OBJS))<BR>MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))<BR>MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) \<BR>$(patsubst %.d,%.cc,$(MISSING_DEPS)))<BR>CPPFLAGS += -MD<BR><BR>.PHONY : everything deps objs clean veryclean rebuild<BR><BR>everything : $(EXECUTABLE)<BR><BR>deps : $(DEPS)<BR><BR>objs : $(OBJS)<BR><BR>clean :<BR>&nbsp;&nbsp;@$(RM-F) *.o<BR>&nbsp;&nbsp;@$(RM-F) *.d<BR><BR>veryclean: clean<BR>&nbsp;&nbsp;@$(RM-F) $(EXECUTABLE)<BR><BR>rebuild: veryclean everything<BR><BR>ifneq ($(MISSING_DEPS),)<BR>$(MISSING_DEPS) :<BR>&nbsp;&nbsp;@$(RM-F) $(patsubst %.d,%.o,$@)<BR>endif<BR><BR>-include $(DEPS)<BR><BR>$(EXECUTABLE) : $(OBJS)<BR>&nbsp;&nbsp;gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS))<BR><BR>=== makefile 结束 ===<BR><BR>有几个地方值得解释一下的。首先，我在定义大部分变量的时候使 用的是 := 而不是 = 符号。它的作用是立即把定义中参考到的函 数和变量都展开了。如果使用 = 的话，函数和变量参考会留在那 儿，就是说改变一个变量的值会导致其它变量的值也被改变。例 如：<BR><BR>A = foo<BR>B = $(A)<BR># 现在 B 是 $(A) ，而 $(A) 是 'foo' 。<BR>A = bar<BR># 现在 B 仍然是 $(A) ，但它的值已随着变成 'bar' 了。<BR>B := $(A)<BR># 现在 B 的值是 'bar' 。<BR>A = foo<BR># B 的值仍然是 'bar' 。<BR><BR>make 会忽略在 # 符号后面直到那一行结束的所有文字。<BR><BR>ifneg...else...endif 系统是 makefile 里让某一部分码有条件的 失效／有效的工具。 ifeq 使用两个参数，如果它们相同，它把直 到 else （或者 endif ，如果没有 else 的话）的一段码加进 makefile 里；如果不同，把 else 到 endif 间的一段码加入 makefile （如果有 else ）。 ifneq 的用法刚好相反。<BR><BR>'filter-out' 函数使用两个用空格分开的列表，它把第二列表中所 有的存在于第一列表中的项目删除。我用它来处理 DEPS 列表，把所 有已经存在的项目都删除，而只保留缺少的那些。<BR><BR>我前面说过， CPPFLAGS 存有用于隐含规则中传给预处理器的一些 旗标。而 -MD 开关类似 -M 开关，但是从源码文件 .c 或 .cc 中 形成的文件名是使用后缀 .d 的（这就解释了我形成 DEPS 变量的 步骤）。DEPS 里提到的文件后来用 '-include' 加进了 makefile 里，它隐藏了所有因文件不存在而产生的错误信息。<BR><BR>如果任何依靠文件不存在， makefile 会把相应的 .o 文件从磁碟 上删除，从而使得 make 重建它。因为 CPPFLAGS 指定了 -MD ， 它的 .d 文件也被重新产生。<BR><BR>最后， 'addprefix' 函数把第二个参数列表的每一项前缀上第一 个参数值。<BR><BR>这个 makefile 的那些目的是（这些目的可以传给 make 的命令行 来直接选用）：<BR><BR>everything:（预设） 更新主要的可执行程序，并且为每一个 源码文件生成或更新一个 '.d' 文件和一个 '.o' 文件。<BR><BR>deps: 只是为每一个源码程序产生或更新一个 '.d' 文件。<BR><BR>objs: 为每一个源码程序生成或更新 '.d' 文件和目标文件。<BR><BR>clean: 删除所有中介／依靠文件（ *.d 和 *.o ）。<BR><BR>veryclean: 做 `clean' 和删除可执行文件。<BR><BR>rebuild: 先做 `veryclean' 然后 `everything' ；既完全重建。<BR><BR>除了预设的 everything 以外，这里头只有 clean ， veryclean ， 和 rebuild 对用户是有意义的。<BR><BR>我还没有发现当给出一个源码文件的目录，这个 makefile 会失败的 情况，除非依靠文件被弄乱。如果这种弄乱的情况发生了，只要输入 `make clean' ，所有的目标文件和依靠文件会被删除，问题就应该 被解决了。当然，最好不要把它们弄乱。如果你发现在某种情况下这 个 makefile 文件不能完成它的工作，请告诉我，我会把它整好的。<BR><BR></UL>
<P>3 总结<BR>~~~~~~~~~~~~~~~<BR><BR>我希望这篇文章足够详细的解释了多文件项目是怎么运作的，也说明了 怎样安全而合理的使用它。到此，你应该可以轻松的利用 GNU Make 工 具来管理小型的项目，如果你完全理解了后面几个部分的话，这些对于 你来说应该没什么困难。<BR><BR>GNU Make 是一件强大的工具，虽然它主要是用来建立程序，它还有很多 别的用处。如果想要知道更多有关这个工具的知识，它的句法，函数， 和许多别的特点，你应该参看它的参考文件 (info pages, 别的 GNU 工具也一样，看它们的 info pages. )。<BR></P>
<P>from: <A href="http://www.chinalinuxpub.com/doc/pro/gmake.html">http://www.chinalinuxpub.com/doc/pro/gmake.html</A></P><img src ="http://www.blogjava.net/weidagang2046/aggbug/22739.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-12-06 17:02 <a href="http://www.blogjava.net/weidagang2046/articles/22739.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>vi中如何使用:s 命令实现字串的替换</title><link>http://www.blogjava.net/weidagang2046/articles/21662.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 28 Nov 2005 05:50:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/21662.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/21662.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/21662.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/21662.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/21662.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width=700 align=center border=0>
<TBODY>
<TR>
<TD vAlign=top><FONT color=#000000>:s/str1/str2/&nbsp;用字串&nbsp;str2&nbsp;替换行中首次出现的字串&nbsp;str1 <BR>:s/str1/str2/g&nbsp;用字串&nbsp;str2&nbsp;替换行中所有出现的字串&nbsp;str1 <BR><BR>:.,$&nbsp;s/str1/str2/g&nbsp;用字串str2替换正文当前行到末尾所有出现的字符串str1 <BR><BR>:1,$&nbsp;s/str1/str2/g&nbsp;用字串str2替换正文中所有出现的字串str1 <BR><BR>:g/str1/s//str2/g&nbsp;功能同上. <BR><BR><BR>　　由以上可知,g放在命令末尾,表示对搜索字串的每次出现进行替换;不加g,表示只对搜索字串 <BR>的首次出现进行替换,g放在命令开头,表示对正文中所有包含搜索字串的行进行替换.</FONT></TD></TR></TBODY></TABLE><BR>from: <A href="http://fanqiang.chinaunix.net/a1/b5/20010628/210400718.html">http://fanqiang.chinaunix.net/a1/b5/20010628/210400718.html</A><img src ="http://www.blogjava.net/weidagang2046/aggbug/21662.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-11-28 13:50 <a href="http://www.blogjava.net/weidagang2046/articles/21662.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用find搜索文件</title><link>http://www.blogjava.net/weidagang2046/articles/21659.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 28 Nov 2005 05:33:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/21659.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/21659.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/21659.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/21659.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/21659.html</trackback:ping><description><![CDATA[<P>可以使用 <TT class=computeroutput>find</TT> 命令在整个目录及其子目录下搜索符合特定准则的文件。然后，可以在找到的文件中执行命令。</P>
<DIV class=section lang=zh_cn>
<DIV class=titlepage>
<DIV>
<DIV>
<TABLE cellSpacing=1 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>&nbsp;</TD></TR>
<TR>
<TD vAlign=top align=left><A name=d0e4274></A>
<H3 class=bold>查找符合某种模式的文件</H3></TD></TR>
<TR class=decoration>
<TD class=theme><IMG height=4 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE></DIV></DIV>
<DIV></DIV></DIV>
<P>虽然 <TT class=computeroutput>find</TT> 的语法较为复杂，但它有助于提高使用 HP-UX 的效率。此命令功能强大而且灵活。但是运行速度较慢，搜索多个目录时尤其如此。</P>
<P>假定要显示当前目录及其子目录下所有以 d 开头的文件，请输入：</P>
<H4><A name=d0e4283></A>find(1)</H4>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><CODE><PRE class=programlisting>$ <B class=userinput><TT>find&nbsp;.&nbsp;-name&nbsp;'d*'</TT></B></PRE></CODE></TD></TR></TBODY></TABLE>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<P>命令中的点 (.) 会使 <TT class=computeroutput>find</TT> 搜索当前目录及其子目录。文件名后的 <TT class=computeroutput>-name</TT> 选项或文件名模式（在这种情况下为 <TT class=computeroutput>d*</TT>）告知 <TT class=computeroutput>find</TT> 搜索所有符合该模式的文件。在本示例中，<TT class=computeroutput>find</TT> 将搜索所有以 <TT class=computeroutput>d</TT> 开头的文件名。</P>
<P>注意，<TT class=computeroutput>d*</TT> 要用单引号括起来 ('<TT class=computeroutput>d*</TT>')。如果在 <TT class=computeroutput>find</TT> 命令中使用文件名模式，必须用单引号将其括起来，以便 shell 可以对其进行正确地解释。</P></DIV>
<DIV class=section lang=zh_cn>
<DIV class=titlepage>
<DIV>
<DIV>
<TABLE cellSpacing=1 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>&nbsp;</TD></TR>
<TR>
<TD vAlign=top align=left><A name=d0e4321></A>
<H3 class=bold>查找晚于某个文件创建日期的文件</H3></TD></TR>
<TR class=decoration>
<TD class=theme><IMG height=4 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE></DIV></DIV>
<DIV></DIV></DIV>
<P>假定您想显示所有在某个文件创建日期之后修改过的文件。要显示 <TT class=computeroutput>/home/leslie</TT> 目录及其子目录下所有晚于 <TT class=computeroutput>myfile</TT> 创建日期的文件，请输入：</P>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><CODE><PRE class=programlisting>$ <B class=userinput><TT>find&nbsp;/home/leslie&nbsp;-newer&nbsp;myfile</TT></B></PRE></CODE></TD></TR></TBODY></TABLE>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<P>本示例可以理解为：在目录 <TT class=computeroutput>/home/leslie</TT> 及其子目录下，查找所有在 <TT class=computeroutput>myfile</TT> 创建日期之后修改过的文件。（要确定文件的上次修改日期，请使用 <TT class=computeroutput>ll</TT> 命令。）</P></DIV>
<DIV class=section lang=zh_cn>
<DIV class=titlepage>
<DIV>
<DIV>
<TABLE cellSpacing=1 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>&nbsp;</TD></TR>
<TR>
<TD vAlign=top align=left><A name=d0e4347></A>
<H3 class=bold>在文件中运行命令</H3></TD></TR>
<TR class=decoration>
<TD class=theme><IMG height=4 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE></DIV></DIV>
<DIV></DIV></DIV>
<P>可以在使用 <TT class=computeroutput>find</TT> 命令查找到的文件中执行命令。假定您想删除当前目录及其子目录下所有扩展名为 <TT class=computeroutput>.tmp</TT> 的文件。请输入：</P>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><CODE><PRE class=programlisting>$ <B class=userinput><TT>find&nbsp;.&nbsp;-name&nbsp;'*.tmp'&nbsp;-exec&nbsp;rm&nbsp;{}&nbsp;\;</TT></B></PRE></CODE></TD></TR></TBODY></TABLE>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<P>本示例查找当前目录及其子目录下所有扩展名为 <TT class=computeroutput>.tmp</TT> 的文件，并将其显示在屏幕上，然后将其删除。<TT class=computeroutput>-exec</TT> 选项会导致以下命令 (<TT class=computeroutput>rm</TT>) 的执行。花括号 {} 代表使用 <TT class=computeroutput>find</TT> 命令找到的文件。结束 <TT class=computeroutput>exec</TT> 字符串的分号之前应添加一个反斜线 (\;)。</P></DIV>
<DIV class=section lang=zh_cn>
<DIV class=titlepage>
<DIV>
<DIV>
<TABLE cellSpacing=1 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>&nbsp;</TD></TR>
<TR>
<TD vAlign=top align=left><A name=d0e4379></A>
<H3 class=bold>使用逻辑运算符</H3></TD></TR>
<TR class=decoration>
<TD class=theme><IMG height=4 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE></DIV></DIV>
<DIV></DIV></DIV>
<P><TT class=computeroutput>find</TT> 的语法中包括逻辑布尔运算符：NOT、AND 和 OR。</P>
<P>要查找不符合特定模式的文件，请使用 NOT 逻辑运算符，即感叹号 (<TT class=computeroutput>!</TT>)。使用此运算符之后，必须使用选项定义文件属性，如文件名。然后，将找到<EM>不</EM>具有指定属性的文件。</P>
<P>例如，要查找 <TT class=computeroutput>/tmp</TT> 下所有<EM>不</EM>属于 <TT class=computeroutput>leslie</TT> 的文件，请使用以下命令：</P>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><CODE><PRE class=programlisting>$ <B class=userinput><TT>find&nbsp;/tmp&nbsp;\(&nbsp;!&nbsp;-user&nbsp;leslie&nbsp;\)</TT></B></PRE></CODE></TD></TR></TBODY></TABLE>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<P><TT class=computeroutput>\</TT> 避免了 shell 将圆括号视为特殊字符的可能性。</P>
<P>要查找具有两个不同属性的文件，请使用 AND 逻辑运算符（<I class=replaceable><TT>表达式</TT></I> <TT class=computeroutput>-a</TT> <I class=replaceable><TT>表达式）</TT></I>。例如，要查找 <TT class=computeroutput>/</TT> 下所有属于 <TT class=computeroutput>leslie</TT> 的目录，请使用以下命令：</P>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><CODE><PRE class=programlisting>$ <B class=userinput><TT>find&nbsp;/&nbsp;\(&nbsp;-type&nbsp;d&nbsp;-a&nbsp;-user&nbsp;leslie&nbsp;\)</TT></B></PRE></CODE></TD></TR></TBODY></TABLE>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<P>要查找具有其中一个或两个属性的文件，请使用 OR 逻辑运算符（<I class=replaceable><TT>表达式</TT></I> <TT class=computeroutput>-o</TT> <I class=replaceable><TT>表达式</TT></I>）。例如，要删除一周以来从未访问过的以 <TT class=computeroutput>.o</TT> 结尾或名为 <TT class=computeroutput>a.out</TT> 的所有文件，请使用以下命令：</P>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<TABLE width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD><CODE><PRE class=programlisting>$<B class=userinput><TT>find&nbsp;/&nbsp;\(&nbsp;-name&nbsp;a.out&nbsp;-o&nbsp;-name&nbsp;'*.o'&nbsp;\)&nbsp;-atime&nbsp;+7&nbsp;-exec&nbsp;rm&nbsp;{}&nbsp;\;</TT></B></PRE></CODE></TD></TR></TBODY></TABLE>
<TABLE>
<TBODY>
<TR>
<TD><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE>
<DIV class=section lang=zh_cn>
<DIV class=titlepage>
<DIV>
<DIV>
<TABLE cellSpacing=1 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>&nbsp;</TD></TR>
<TR>
<TD vAlign=top align=left><A name=d0e4455></A>
<H4 class=bold>更多信息</H4></TD></TR>
<TR class=decoration>
<TD class=theme><IMG height=2 alt="" src="http://docs.hp.com/img/s.gif" width=1></TD></TR></TBODY></TABLE></DIV></DIV>
<DIV></DIV></DIV>
<P>有关使用 <TT class=computeroutput>find</TT> 命令的详细信息，请参阅 <I class=citetitle>find</I>(1) 联机帮助页。<BR><BR>from: <A href="http://docs.hp.com/zh_cn/5187-2211/ch02s18.html">http://docs.hp.com/zh_cn/5187-2211/ch02s18.html</A></P></DIV></DIV><img src ="http://www.blogjava.net/weidagang2046/aggbug/21659.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-11-28 13:33 <a href="http://www.blogjava.net/weidagang2046/articles/21659.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>去掉linux输出的下特殊字符</title><link>http://www.blogjava.net/weidagang2046/articles/15679.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 16 Oct 2005 14:12:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/15679.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/15679.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/15679.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/15679.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/15679.html</trackback:ping><description><![CDATA[<FONT face=Georgia>man <EM>command | </EM><FONT color=#000000>col -b &gt; command.man</FONT></FONT><img src ="http://www.blogjava.net/weidagang2046/aggbug/15679.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-10-16 22:12 <a href="http://www.blogjava.net/weidagang2046/articles/15679.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>nohug</title><link>http://www.blogjava.net/weidagang2046/articles/12551.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 09 Sep 2005 07:03:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/12551.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/12551.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/12551.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/12551.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/12551.html</trackback:ping><description><![CDATA[<P><SPAN class=postbody><SPAN class=postbody>如果你正在运行一个进程，而且你觉得在退出帐户时该进程还不会结束，那么可以使用nohup命令。该命令可以在你退出帐户之后继续运行相应的进程。N o h u p就是不挂起的意思( nohang up)。 <BR>该命令的一般形式为： nohug command &amp;</P></SPAN></SPAN><img src ="http://www.blogjava.net/weidagang2046/aggbug/12551.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-09-09 15:03 <a href="http://www.blogjava.net/weidagang2046/articles/12551.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用find命令灵活查找</title><link>http://www.blogjava.net/weidagang2046/articles/10911.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 24 Aug 2005 05:53:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/10911.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/10911.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/10911.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/10911.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/10911.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width=700 align=center border=0>
<TBODY>
<TR>
<TD vAlign=top><FONT color=#000000>find&nbsp;指令可以根据档案名,&nbsp;文件模式,&nbsp;文件类型,&nbsp;帐号,&nbsp;文件大小,&nbsp;访问时间,&nbsp; <BR>修改时间等资讯进行查找.&nbsp;但要注意以下几点: <BR>1.&nbsp;find&nbsp;指令的查找物件中不能有空格,&nbsp;否则应加双引号; <BR><BR>2.&nbsp;进行多种可能性的查找时,&nbsp;应采用&nbsp;"-o"&nbsp;选项,&nbsp;如 <BR><BR>　　find&nbsp;/&nbsp;-name&nbsp;"*Myname*"&nbsp;-o&nbsp;-name&nbsp;"*myname"&nbsp;-print <BR><BR>　　可以查找档案名中所有含有&nbsp;"Myname"&nbsp;和&nbsp;"myname"&nbsp;的文件.&nbsp;"-o"&nbsp;表示逻辑或.&nbsp; <BR>此指令也可以由下列形式实现: <BR><BR>　　find&nbsp;/&nbsp;-name&nbsp;*\[Aa\]ug*&nbsp;-print <BR><BR>　　注意,&nbsp;其中的&nbsp;"\"&nbsp;符号不可少,&nbsp;否则&nbsp;"["&nbsp;和&nbsp;"]"&nbsp;符号将被作为档案名的一部分进行处理. </FONT></TD></TR></TBODY></TABLE><BR>from: http://fanqiang.chinaunix.net/a1/b5/20010629/100800723.html<img src ="http://www.blogjava.net/weidagang2046/aggbug/10911.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-08-24 13:53 <a href="http://www.blogjava.net/weidagang2046/articles/10911.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Bash提示符</title><link>http://www.blogjava.net/weidagang2046/articles/10834.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 23 Aug 2005 13:41:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/10834.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/10834.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/10834.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/10834.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/10834.html</trackback:ping><description><![CDATA[bash 有两级用户提示符。第一级是你经常看到的 bash 在等待命令输入时的提示符。缺省的一级提示符是字符$（如果是超级用户，则是#号）。你可以通过改变bash 的PS1变量的值来改变你的缺省提示符，例如： <BR><BR>PS1="Please enter a command" <BR><BR>把bash shell 的提示符该为指定的字符串。 <BR><BR>当bash 期待输入更多的信息以完成命令时显示第二级提示符。缺省的第二级提示符是 &gt;。 果你要改变第二级提示符，可以通过设置PS2变量的值来实现： <BR><BR>PS2="I need more information" <BR><BR>另外你还可以用特殊的字符来定义你的提示符，下面的列表列出了最常用的特殊字符。 <BR><BR>提示符特殊字符代码 <BR>字符 含义 <BR>! 显示该命令的历史记录编号。 <BR># 显示当前命令的命令编号。 <BR><BR>$ 显示$符作为提示符，如果用户是root的话，则显示#号。 <BR><BR>\ 显示反斜杠。 <BR><BR>d 显示当前日期。 <BR><BR>h 显示主机名。 <BR><BR>打印新行。 <BR><BR>nn 显示nnn的八进制值。 <BR><BR>s 显示当前运行的shell的名字。 <BR><BR>显示当前时间。 <BR><BR>u 显示当前用户的用户名。 <BR><BR>W 显示当前工作目录的名字。 <BR><BR>w 显示当前工作目录的路径。 <BR><BR><BR>这些特殊字符能组合成很多种有用的提示符方案（也可以组合为很奇异的方案），例如把 PS1 设为： <BR><BR>PS1=" " <BR><BR>这导致提示符显示当前的时间，就象下面的显示一样（提示符后面将不会有空格）： <BR><BR>02:16:15 <BR><BR><BR>而下面的设置： <BR><BR>PS1= <BR><BR>将导致提示符变成下面的样子： <BR><BR>t <BR><BR>这显示了设置中引号的重要性，下面的提示符串： <BR><BR>PS1=" \ " <BR><BR>会使提示符看起来象这个样子： <BR><BR>02:16:30 <BR><BR>这种情况下，提示符后面会有一个空格，因为引号里有一个空格。 <BR><BR><BR>from: <A href="http://study.99net.net/study/program/shell/1085454320.html">http://study.99net.net/study/program/shell/1085454320.html</A><img src ="http://www.blogjava.net/weidagang2046/aggbug/10834.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-08-23 21:41 <a href="http://www.blogjava.net/weidagang2046/articles/10834.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入Linux网络核心堆栈</title><link>http://www.blogjava.net/weidagang2046/articles/10096.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 14 Aug 2005 14:06:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/10096.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/10096.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/10096.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/10096.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/10096.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: ==Phrack&nbsp;Inc.==&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;卷标&nbsp;0x0b,&nbsp;期刊号&nbsp;0x3d,&nbsp;Phile&nbsp;#0x0d&nbsp;of&nbsp;0x0f|=---------------------...&nbsp;&nbsp;<a href='http://www.blogjava.net/weidagang2046/articles/10096.html'>阅读全文</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/10096.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-08-14 22:06 <a href="http://www.blogjava.net/weidagang2046/articles/10096.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境进程间通信（五）： 共享内存（上）</title><link>http://www.blogjava.net/weidagang2046/articles/9194.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 04 Aug 2005 05:07:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/9194.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/9194.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/9194.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/9194.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/9194.html</trackback:ping><description><![CDATA[<P>共享内存可以说是最有用的进程间通信方式，也是最快的IPC形式。两个不同进程A、B共享内存的意思是，同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新，反之亦然。由于多个进程共享同一块内存区域，必然需要某种同步机制，互斥锁和信号量都可以。</P>
<P>采用共享内存通信的一个显而易见的好处是效率高，因为进程可以直接读写内存，而不需要任何数据的拷贝。对于像管道和消息队列等通信方式，则需要在内核和用户空间进行四次的数据拷贝，而共享内存则只拷贝两次数据[1]：一次从输入文件到共享内存区，另一次从共享内存区到输出文件。实际上，进程之间在共享内存时，并不总是读写少量数据后就解除映射，有新的通信时，再重新建立共享内存区域。而是保持共享区域，直到通信完毕为止，这样，数据内容一直保存在共享内存中，并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此，采用共享内存的通信方式效率是非常高的。</P>
<P>Linux的2.2.x内核支持多种共享内存方式，如mmap()系统调用，Posix共享内存，以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存，但还没实现Posix共享内存，本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。</P>
<P><A name=1><SPAN class=atitle2>一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面</SPAN></A></P>
<P>1、page cache及swap cache中页面的区分：一个被访问文件的物理页面都驻留在page cache或swap cache中，一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ，它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。</P>
<P>2、文件与address_space结构的对应：一个具体的文件在打开后，内核会在内存中为之建立一个struct inode结构，其中的i_mapping域指向一个address_space结构。这样，一个文件就对应一个address_space结构，一个address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此，当要寻址某个数据时，很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。</P>
<P>3、进程调用mmap()时，只是在进程空间内新增了一块相应大小的缓冲区，并设置了相应的访问标识，但并没有建立进程空间到物理页面的映射。因此，第一次访问该空间时，会引发一个缺页异常。</P>
<P>4、对于共享内存映射情况，缺页异常处理程序首先在swap cache中寻找目标页（符合address_space以及偏移量的物理页），如果找到，则直接返回地址；如果没有找到，则判断该页是否在交换区(swap area)，如果在，则执行一个换入操作；如果上述两种情况都不满足，处理程序将分配新的物理页面，并把它插入到page cache中。进程最终将更新进程页表。<BR>注：对于映射普通文件情况（非共享映射），缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到，则说明文件数据还没有读入内存，处理程序会从磁盘读入相应的页面，并返回相应地址，同时，进程页表也会更新。</P>
<P>5、所有进程在映射同一个共享内存区域时，情况都一样，在建立线性地址与物理地址之间的映射之后，不论进程各自的返回地址如何，实际访问的必然是同一个共享内存区域对应的物理页面。<BR>注：一个共享内存区域可以看作是特殊文件系统shm中的一个文件，shm的安装点在交换区上。</P>
<P>上面涉及到了一些数据结构，围绕数据结构理解问题会容易一些。</P>
<P><A name=2><SPAN class=atitle2>二、mmap()及其相关系统调用</SPAN></A></P>
<P>mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后，进程可以向访问普通内存一样对文件进行访问，不必再调用read()，write（）等操作。</P>
<P>注：实际上，mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式，进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的，当然mmap()实现共享内存也是其主要应用之一。</P>
<P><SPAN class=atitle3>1、mmap()系统调用形式如下：</SPAN></P>
<P>void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )<BR>参数fd为即将映射到进程空间的文件描述字，一般由open()返回，同时，fd可以指定为-1，此时须指定flags参数中的MAP_ANON，表明进行的是匿名映射（不涉及具体的文件名，避免了文件的创建及打开，很显然只能用于具有亲缘关系的进程间通信）。len是映射到调用进程地址空间的字节数，它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或：PROT_READ（可读） , PROT_WRITE （可写）, PROT_EXEC （可执行）, PROT_NONE（不可访问）。flags由以下几个常值指定：MAP_SHARED , MAP_PRIVATE , MAP_FIXED，其中，MAP_SHARED , MAP_PRIVATE必选其一，而MAP_FIXED则不推荐使用。offset参数一般设为0，表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址，一般被指定一个空指针，此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址，进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数，读者可参考mmap()手册页获得进一步的信息。</P>
<P><SPAN class=atitle3>2、系统调用mmap()用于共享内存的两种方式：</SPAN></P>
<P>（1）使用普通文件提供的内存映射：适用于任何进程之间；此时，需要打开或创建一个文件，然后再调用mmap()；典型调用代码如下： <PRE>	fd=open(name, flag, mode);
if(fd&lt;0)
	...
	</PRE>ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方，我们将在范例中进行具体说明。 
<P></P>
<P>（2）使用特殊文件提供匿名内存映射：适用于具有亲缘关系的进程之间；由于父子进程特殊的亲缘关系，在父进程中先调用mmap()，然后调用fork()。那么在调用fork()之后，子进程继承父进程匿名映射后的地址空间，同样也继承mmap()返回的地址，这样，父子进程就可以通过映射区域进行通信了。注意，这里不是一般的继承关系。一般来说，子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址，却由父子进程共同维护。<BR>对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时，不必指定具体的文件，只要设置相应的标志即可，参见范例2。</P>
<P><SPAN class=atitle3>3、系统调用munmap()</SPAN></P>
<P>int munmap( void * addr, size_t len )<BR>该调用在进程地址空间中解除一个映射关系，addr是调用mmap()时返回的地址，len是映射区的大小。当映射关系解除后，对原来映射地址的访问将导致段错误发生。</P>
<P><SPAN class=atitle3>4、系统调用msync()</SPAN></P>
<P>int msync ( void * addr , size_t len, int flags)<BR>一般说来，进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中，往往在调用munmap（）后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。</P>
<P><A name=3><SPAN class=atitle2>三、mmap()范例</SPAN></A></P>
<P>下面将给出使用mmap()的两个范例：范例1给出两个进程通过映射普通文件实现共享内存通信；范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方，下面是通过mmap（）映射普通文件实现进程间的通信的范例，我们通过该范例来说明mmap()实现共享内存的特点及注意事项。</P>
<P><SPAN class=atitle3>范例1：两个进程通过映射普通文件实现共享内存通信</SPAN></P>
<P>范例1包含两个子程序：map_normalfile1.c及map_normalfile2.c。编译两个程序，可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件，把该文件映射到进程的地址空间，并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间，然后对映射后的地址空间执行读操作。这样，两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。</P>
<P>下面是两个程序代码：</P><PRE>/*-------------map_normalfile1.c-----------*/
#include &lt;sys/mman.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
typedef struct{
	char name[4];
	int  age;
}people;

main(int argc, char** argv) // map a normal file as shared mem:
{
	int fd,i;
	people *p_map;
	char temp;
	
	fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
	lseek(fd,sizeof(people)*5-1,SEEK_SET);
	write(fd,"",1);
	
	p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
	close( fd );
	temp = 'a';
	for(i=0; i&lt;10; i++)
	{
		temp += 1;
		memcpy( ( *(p_map+i) ).name, &amp;temp,2 );
		( *(p_map+i) ).age = 20+i;
	}
	printf(" initialize over \n ")；
	sleep(10);

	munmap( p_map, sizeof(people)*10 );
	printf( "umap ok \n" );
}

/*-------------map_normalfile2.c-----------*/
#include &lt;sys/mman.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
typedef struct{
	char name[4];
	int  age;
}people;

main(int argc, char** argv)	// map a normal file as shared mem:
{
	int fd,i;
	people *p_map;
	fd=open( argv[1],O_CREAT|O_RDWR,00777 );
	p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	for(i = 0;i&lt;10;i++)
	{
	printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );

	}
	munmap( p_map,sizeof(people)*10 );
}
</PRE>
<P>map_normalfile1.c首先定义了一个people数据结构，（在这里采用数据结构的方式是因为，共享内存区的数据往往是有固定格式的，这由通信的各个进程决定，采用结构的方式有普遍代表性）。map_normfile1首先打开或创建一个文件，并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始，设置了10个people结构。然后，进程睡眠10秒钟，等待其他进程映射同一个文件，最后解除映射。</P>
<P>map_normfile2.c只是简单的映射一个文件，并以people数据结构的格式从mmap()返回的地址处读取10个people结构，并输出读取的值，然后解除映射。</P>
<P>分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后，在一个终端上先运行./map_normalfile2 /tmp/test_shm，程序输出结果如下：</P><PRE>initialize over
umap ok
</PRE>
<P>在map_normalfile1输出initialize over 之后，输出umap ok之前，在另一个终端上运行map_normalfile2 /tmp/test_shm，将会产生如下输出(为了节省空间，输出结果为稍作整理后的结果)：</P><PRE>name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
name: g	age 25;	name: h	age 26;	name: I	age 27;	name: j	age 28;	name: k	age 29;
</PRE>
<P>在map_normalfile1 输出umap ok后，运行map_normalfile2则输出如下结果：</P><PRE>name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;
</PRE>
<P><B>从程序的运行结果中可以得出的结论</B></P>
<P>1、 最终被映射文件的内容的长度不会超过文件本身的初始大小，即映射不能改变文件的大小；</P>
<P>2、 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小，但不完全受限于文件大小。打开文件被截短为5个people结构大小，而在map_normalfile1中初始化了10个people数据结构，在恰当时候（map_normalfile1输出initialize over 之后，输出umap ok之前）调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值，后面将给出详细讨论。<BR>注：在linux中，内存的保护是以页为基本单位的，即使被映射文件只有一个字节大小，内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时，进程可以对从mmap()返回地址开始的一个页面大小进行访问，而不会出错；但是，如果对一个页面以外的地址空间进行访问，则导致错误发生，后面将进一步描述。因此，可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。</P>
<P>3、 文件一旦被映射后，调用mmap()的进程对返回地址的访问是对某一内存区域的访问，暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义，只有在调用了munmap()后或者msync()时，才把内存中的相应内容写回磁盘文件，所写内容仍然不能超过文件的大小。</P>
<P><SPAN class=atitle3>范例2：父子进程通过匿名映射实现共享内存</SPAN></P><PRE>#include &lt;sys/mman.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
typedef struct{
	char name[4];
	int  age;
}people;
main(int argc, char** argv)
{
	int i;
	people *p_map;
	char temp;
	p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
	if(fork() == 0)
	{
		sleep(2);
		for(i = 0;i&lt;5;i++)
			printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);
		(*p_map).age = 100;
		munmap(p_map,sizeof(people)*10); //实际上，进程终止时，会自动解除映射。
		exit();
	}
	temp = 'a';
	for(i = 0;i&lt;5;i++)
	{
		temp += 1;
		memcpy((*(p_map+i)).name, &amp;temp,2);
		(*(p_map+i)).age=20+i;
	}

	sleep(5);
	printf( "parent read: the first people,s age is %d\n",(*p_map).age );
	printf("umap\n");
	munmap( p_map,sizeof(people)*10 );
	printf( "umap ok\n" );
}
</PRE>
<P>考察程序的输出结果，体会父子进程匿名共享内存：</P><PRE>child read: the 1 people's age is 20
child read: the 2 people's age is 21
child read: the 3 people's age is 22
child read: the 4 people's age is 23
child read: the 5 people's age is 24

parent read: the first people,s age is 100
umap
umap ok
</PRE>
<P><A name=4><SPAN class=atitle2>四、对mmap()返回地址的访问</SPAN></A></P>
<P>前面对范例运行结构的讨论中已经提到，linux采用的是页式管理机制。对于用mmap()映射普通文件来说，进程会在自己的地址空间新增一块空间，空间大小由mmap()的len参数指定，注意，进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说，能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始，能够有效访问的地址空间大小。超过这个空间大小，内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明：</P>
<P>
<CENTER><IMG height=228 alt="" src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part5/image001.gif" width=719 border=0> <BR></CENTER>
<P></P>
<P>注意：文件被映射部分而不是整个文件决定了进程能够访问的空间大小，另外，如果指定文件的偏移部分，一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例：</P><PRE>#include &lt;sys/mman.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
typedef struct{
	char name[4];
	int  age;
}people;

main(int argc, char** argv)
{
	int fd,i;
	int pagesize,offset;
	people *p_map;
	
	pagesize = sysconf(_SC_PAGESIZE);
	printf("pagesize is %d\n",pagesize);
	fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
	lseek(fd,pagesize*2-100,SEEK_SET);
	write(fd,"",1);
	offset = 0;	//此处offset = 0编译成版本1；offset = pagesize编译成版本2
	p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
	close(fd);
	
	for(i = 1; i&lt;10; i++)
	{
		(*(p_map+pagesize/sizeof(people)*i-2)).age = 100;
		printf("access page %d over\n",i);
		(*(p_map+pagesize/sizeof(people)*i-1)).age = 100;
		printf("access page %d edge over, now begin to access page %d\n",i, i+1);
		(*(p_map+pagesize/sizeof(people)*i)).age = 100;
		printf("access page %d over\n",i+1);
	}
	munmap(p_map,sizeof(people)*10);
}
</PRE>
<P>如程序中所注释的那样，把程序编译成两个版本，两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间（大小为：pagesize*2-99），版本1的被映射部分是整个文件，版本2的文件被映射部分是文件大小减去一个页面后的剩余部分，不到一个页面大小(大小为：pagesize-99)。程序中试图访问每一个页面边界，两个版本都试图在进程空间中映射pagesize*3的字节数。</P>
<P>版本1的输出结果如下：</P><PRE>pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
access page 2 over
access page 2 over
access page 2 edge over, now begin to access page 3
Bus error		//被映射文件在进程空间中覆盖了两个页面，此时，进程试图访问第三个页面
</PRE>
<P>版本2的输出结果如下：</P><PRE>pagesize is 4096
access page 1 over
access page 1 edge over, now begin to access page 2
Bus error		//被映射文件在进程空间中覆盖了一个页面，此时，进程试图访问第二个页面
</PRE>
<P>结论：采用系统调用mmap()实现进程间通信是很方便的，在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容，可以参考一下相关重要数据结构来加深理解。在本专题的后面部分，将介绍系统v共享内存的实现。</P>
<P><A name=resources><SPAN class=atitle2>参考文献：</SPAN></A></P>
<P>[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 对各主题阐述得重点突出，脉络清晰。</P>
<P>[2] UNIX网络编程第二卷：进程间通信，作者：W.Richard Stevens，译者：杨继张，清华大学出版社。对mmap()有详细阐述。</P>
<P>[3] Linux内核源代码情景分析（上），毛德操、胡希明著，浙江大学出版社，给出了mmap()相关的源代码分析。</P>
<P>[4]mmap()手册</P>
<P><A name=author1><SPAN class=atitle2>关于作者：</SPAN></A></P>
<P>郑彦兴，国防科大攻读博士学位。联系方式： <A href="mailto:mlinux@163.com"><FONT color=#002c99>mlinux@163.com</FONT></A><BR><BR>from: <A href="http://www.ddvip.net/program/vc/index6/62.htm">http://www.ddvip.net/program/vc/index6/62.htm</A></P><img src ="http://www.blogjava.net/weidagang2046/aggbug/9194.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-08-04 13:07 <a href="http://www.blogjava.net/weidagang2046/articles/9194.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境进程间通信（四）：信号灯</title><link>http://www.blogjava.net/weidagang2046/articles/9193.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 04 Aug 2005 05:06:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/9193.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/9193.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/9193.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/9193.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/9193.html</trackback:ping><description><![CDATA[<P><A name=1><SPAN class=atitle2>一、信号灯概述</SPAN></A></P>
<P>信号灯与其他进程间通信方式不大相同，它主要提供对进程间共享资源访问控制机制。相当于内存中的标志，进程可以根据它判定是否能够访问某些共享资源，同时，进程也可以修改该标志。除了用于访问控制外，还可用于进程同步。信号灯有以下两种类型：</P>
<UL class=n01>
<LI>二值信号灯：最简单的信号灯形式，信号灯的值只能取0或1，类似于互斥锁。<BR>注：二值信号灯能够实现互斥锁的功能，但两者的关注内容不同。信号灯强调共享资源，只要共享资源可用，其他进程同样可以修改信号灯的值；互斥锁更强调进程，占用资源的进程使用完资源后，必须由进程本身来解锁。 
<LI>计算信号灯：信号灯的值可以取任意非负值（当然受内核本身的约束）。 </LI></UL>
<P><A name=2><SPAN class=atitle2>二、Linux信号灯</SPAN></A></P>
<P>linux对信号灯的支持状况与消息队列一样，在red had 8.0发行版本中支持的是系统V的信号灯。因此，本文将主要介绍系统V信号灯及其相应API。在没有声明的情况下，以下讨论中指的都是系统V信号灯。</P>
<P>注意，通常所说的系统V信号灯指的是计数信号灯集。</P>
<P><A name=3><SPAN class=atitle2>三、信号灯与内核</SPAN></A></P>
<P>1、系统V信号灯是随内核持续的，只有在内核重起或者显示删除一个信号灯集时，该信号灯集才会真正被删除。因此系统中记录信号灯的数据结构（struct ipc_ids sem_ids）位于内核中，系统中的所有信号灯都可以在结构sem_ids中找到访问入口。</P>
<P>2、下图说明了内核与信号灯是怎样建立起联系的：</P>
<P>其中：struct ipc_ids sem_ids是内核中记录信号灯的全局数据结构；描述一个具体的信号灯及其相关信息。</P>
<P>
<CENTER><IMG height=248 alt="" src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part4/1.gif" width=628 border=0> <BR></CENTER>
<P></P>
<P>其中，struct sem结构如下：</P><PRE>struct sem{
int semval;		// current value
int sempid		// pid of last operation
}
</PRE>
<P>从上图可以看出，全局数据结构struct ipc_ids sem_ids可以访问到struct kern_ipc_perm的第一个成员：struct kern_ipc_perm；而每个struct kern_ipc_perm能够与具体的信号灯对应起来是因为在该结构中，有一个key_t类型成员key，而key则唯一确定一个信号灯集；同时，结构struct kern_ipc_perm的最后一个成员sem_nsems确定了该信号灯在信号灯集中的顺序，这样内核就能够记录每个信号灯的信息了。kern_ipc_perm结构参见《Linux环境进程间通信（三）：消息队列》。struct sem_array见附录1。</P>
<P><A name=4><SPAN class=atitle2>四、操作信号灯</SPAN></A></P>
<P><SPAN class=atitle3>对消息队列的操作无非有下面三种类型：</SPAN></P>
<P>1、 打开或创建信号灯<BR>与消息队列的创建及打开基本相同，不再详述。</P>
<P>2、 信号灯值操作<BR>linux可以增加或减小信号灯的值，相应于对共享资源的释放和占有。具体参见后面的semop系统调用。</P>
<P>3、 获得或设置信号灯属性：<BR>系统中的每一个信号灯集都对应一个struct sem_array结构，该结构记录了信号灯集的各种信息，存在于系统空间。为了设置、获得该信号灯集的各种信息及属性，在用户空间有一个重要的联合结构与之对应，即union semun。</P>
<P>
<CENTER><IMG height=226 alt="" src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part4/2.gif" width=529 border=0> <BR></CENTER>
<P></P>
<P>联合semun数据结构各成员意义参见附录2</P>
<P><SPAN class=atitle3>信号灯API</SPAN></P>
<P>1、文件名到键值</P><PRE>#include &lt;sys/types.h&gt;
#include &lt;sys/ipc.h&gt;
key_t ftok (char*pathname, char proj)；
</PRE>
<P>它返回与路径pathname相对应的一个键值，具体用法请参考《Linux环境进程间通信（三）：消息队列》。</P>
<P>2、 linux特有的ipc()调用：</P>
<P>int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);</P>
<P>参数call取不同值时，对应信号灯的三个系统调用：<BR>当call为SEMOP时，对应int semop(int semid, struct sembuf *sops, unsigned nsops)调用；<BR>当call为SEMGET时，对应int semget(key_t key, int nsems, int semflg)调用；<BR>当call为SEMCTL时，对应int semctl(int semid，int semnum，int cmd，union semun arg)调用；<BR>这些调用将在后面阐述。</P>
<P>注：本人不主张采用系统调用ipc()，而更倾向于采用系统V或者POSIX进程间通信API。原因已在Linux环境进程间通信（三）：消息队列中给出。</P>
<P>3、系统V信号灯API</P>
<P>系统V消息队列API只有三个，使用时需要包括几个头文件：</P><PRE>#include &lt;sys/types.h&gt;
#include &lt;sys/ipc.h&gt;
#include &lt;sys/sem.h&gt;
</PRE>
<P>1）int semget(key_t key, int nsems, int semflg)<BR>参数key是一个键值，由ftok获得，唯一标识一个信号灯集，用法与msgget()中的key相同；参数nsems指定打开或者新创建的信号灯集中将包含信号灯的数目；semflg参数是一些标志位。参数key和semflg的取值，以及何时打开已有信号灯集或者创建一个新的信号灯集与msgget()中的对应部分相同，不再祥述。<BR>该调用返回与健值key相对应的信号灯集描述字。<BR>调用返回：成功返回信号灯集描述字，否则返回-1。<BR>注：如果key所代表的信号灯已经存在，且semget指定了IPC_CREAT|IPC_EXCL标志，那么即使参数nsems与原来信号灯的数目不等，返回的也是EEXIST错误；如果semget只指定了IPC_CREAT标志，那么参数nsems必须与原来的值一致，在后面程序实例中还要进一步说明。</P>
<P>2）int semop(int semid, struct sembuf *sops, unsigned nsops);<BR>semid是信号灯集ID，sops指向数组的每一个sembuf结构都刻画一个在特定信号灯上的操作。nsops为sops指向数组的大小。<BR>sembuf结构如下：<BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
struct sembuf {
	unsigned short  	sem_num;		/* semaphore index in array */
	short			sem_op;		/* semaphore operation */
	short			sem_flg;		/* operation flags */
};
</CODE></PRE></TD></TR></TBODY></TABLE><BR>sem_num对应信号集中的信号灯，0对应第一个信号灯。sem_flg可取IPC_NOWAIT以及SEM_UNDO两个标志。如果设置了SEM_UNDO标志，那么在进程结束时，相应的操作将被取消，这是比较重要的一个标志位。如果设置了该标志位，那么在进程没有释放共享资源就退出时，内核将代为释放。如果为一个信号灯设置了该标志，内核都要分配一个sem_undo结构来记录它，为的是确保以后资源能够安全释放。事实上，如果进程退出了，那么它所占用就释放了，但信号灯值却没有改变，此时，信号灯值反映的已经不是资源占有的实际情况，在这种情况下，问题的解决就靠内核来完成。这有点像僵尸进程，进程虽然退出了，资源也都释放了，但内核进程表中仍然有它的记录，此时就需要父进程调用waitpid来解决问题了。<BR>sem_op的值大于0，等于0以及小于0确定了对sem_num指定的信号灯进行的三种操作。具体请参考linux相应手册页。<BR>这里需要强调的是semop同时操作多个信号灯，在实际应用中，对应多种资源的申请或释放。semop保证操作的原子性，这一点尤为重要。尤其对于多种资源的申请来说，要么一次性获得所有资源，要么放弃申请，要么在不占有任何资源情况下继续等待，这样，一方面避免了资源的浪费；另一方面，避免了进程之间由于申请共享资源造成死锁。<BR>也许从实际含义上更好理解这些操作：信号灯的当前值记录相应资源目前可用数目；sem_op&gt;0对应相应进程要释放sem_op数目的共享资源；sem_op=0可以用于对共享资源是否已用完的测试；sem_op&lt;0相当于进程要申请-sem_op个共享资源。再联想操作的原子性，更不难理解该系统调用何时正常返回，何时睡眠等待。<BR>调用返回：成功返回0，否则返回-1。</P>
<P>3) int semctl(int semid，int semnum，int cmd，union semun arg)<BR>该系统调用实现对信号灯的各种控制操作，参数semid指定信号灯集，参数cmd指定具体的操作类型；参数semnum指定对哪个信号灯操作，只对几个特殊的cmd操作有意义；arg用于设置或返回信号灯信息。<BR>该系统调用详细信息请参见其手册页，这里只给出参数cmd所能指定的操作。<BR>
<TABLE width="100%" border=0>
<TBODY>
<TR>
<TD>IPC_STAT</TD>
<TD>获取信号灯信息，信息由arg.buf返回；</TD></TR>
<TR>
<TD>IPC_SET</TD>
<TD>设置信号灯信息，待设置信息保存在arg.buf中（在manpage中给出了可以设置哪些信息）；</TD></TR>
<TR>
<TD>GETALL</TD>
<TD>返回所有信号灯的值，结果保存在arg.array中，参数sennum被忽略；</TD></TR>
<TR>
<TD>GETNCNT</TD>
<TD>返回等待semnum所代表信号灯的值增加的进程数，相当于目前有多少进程在等待semnum代表的信号灯所代表的共享资源；</TD></TR>
<TR>
<TD>GETPID</TD>
<TD>返回最后一个对semnum所代表信号灯执行semop操作的进程ID；</TD></TR>
<TR>
<TD>GETVAL</TD>
<TD>返回semnum所代表信号灯的值；</TD></TR>
<TR>
<TD>GETZCNT</TD>
<TD>返回等待semnum所代表信号灯的值变成0的进程数；</TD></TR>
<TR>
<TD>SETALL</TD>
<TD>通过arg.array更新所有信号灯的值；同时，更新与本信号集相关的semid_ds结构的sem_ctime成员；</TD></TR>
<TR>
<TD>SETVAL</TD>
<TD>设置semnum所代表信号灯的值为arg.val；</TD></TR></TBODY></TABLE>
<P>调用返回：调用失败返回-1，成功返回与cmd相关：</P>
<TABLE width="60%" border=1>
<TBODY>
<TR>
<TD>Cmd</TD>
<TD>return value</TD></TR>
<TR>
<TD>GETNCNT</TD>
<TD>Semncnt</TD></TR>
<TR>
<TD>GETPID</TD>
<TD>Sempid</TD></TR>
<TR>
<TD>GETVAL</TD>
<TD>Semval</TD></TR>
<TR>
<TD>GETZCNT</TD>
<TD>Semzcnt</TD></TR></TBODY></TABLE>
<P></P>
<P><A name=5><SPAN class=atitle2>五、信号灯的限制</SPAN></A></P>
<P>1、 一次系统调用semop可同时操作的信号灯数目SEMOPM，semop中的参数nsops如果超过了这个数目，将返回E2BIG错误。SEMOPM的大小特定与系统，redhat 8.0为32。</P>
<P>2、 信号灯的最大数目：SEMVMX，当设置信号灯值超过这个限制时，会返回ERANGE错误。在redhat 8.0中该值为32767。</P>
<P>3、 系统范围内信号灯集的最大数目SEMMNI以及系统范围内信号灯的最大数目SEMMNS。超过这两个限制将返回ENOSPC错误。redhat 8.0中该值为32000。</P>
<P>4、 每个信号灯集中的最大信号灯数目SEMMSL，redhat 8.0中为250。 SEMOPM以及SEMVMX是使用semop调用时应该注意的；SEMMNI以及SEMMNS是调用semget时应该注意的。SEMVMX同时也是semctl调用应该注意的。</P>
<P><A name=6><SPAN class=atitle2>六、竞争问题</SPAN></A></P>
<P>第一个创建信号灯的进程同时也初始化信号灯，这样，系统调用semget包含了两个步骤：创建信号灯；初始化信号灯。由此可能导致一种竞争状态：第一个创建信号灯的进程在初始化信号灯时，第二个进程又调用semget，并且发现信号灯已经存在，此时，第二个进程必须具有判断是否有进程正在对信号灯进行初始化的能力。在参考文献[1]中，给出了绕过这种竞争状态的方法：当semget创建一个新的信号灯时，信号灯结构semid_ds的sem_otime成员初始化后的值为0。因此，第二个进程在成功调用semget后，可再次以IPC_STAT命令调用semctl，等待sem_otime变为非0值，此时可判断该信号灯已经初始化完毕。下图描述了竞争状态产生及解决方法：</P>
<P>
<CENTER><IMG height=193 alt="" src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part4/3.gif" width=613 border=0> <BR></CENTER>
<P></P>
<P>实际上，这种解决方法也是基于这样一个假定：第一个创建信号灯的进程必须调用semop，这样sem_otime才能变为非零值。另外，因为第一个进程可能不调用semop，或者semop操作需要很长时间，第二个进程可能无限期等待下去，或者等待很长时间。</P>
<P><A name=7><SPAN class=atitle2>七、信号灯应用实例</SPAN></A></P>
<P>本实例有两个目的：1、获取各种信号灯信息；2、利用信号灯实现共享资源的申请和释放。并在程序中给出了详细注释。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
#include &lt;linux/sem.h&gt;
#include &lt;stdio.h&gt;
#include &lt;errno.h&gt;
#define SEM_PATH "/unix/my_sem"
#define max_tries 3 

int semid;
main()
{
int flag1,flag2,key,i,init_ok,tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg; 			//union semun： 请参考附录2
struct sembuf askfor_res, free_res;
flag1=IPC_CREAT|IPC_EXCL|00666;
flag2=IPC_CREAT|00666;
key=ftok(SEM_PATH,'a');
//error handling for ftok here;
init_ok=0;
semid=semget(key,1,flag1);//create a semaphore set that only includes one semphore.
if(semid&lt;0)
{
	tmperrno=errno;
	perror("semget");
if(tmperrno==EEXIST)
//errno is undefined after a successful library call( including perror call) so it is saved //in tmperrno.
		{
		semid=semget(key,1,flag2);
//flag2 只包含了IPC_CREAT标志, 参数nsems(这里为1)必须与原来的信号灯数目一致
		arg.buf=&amp;sem_info;
		for(i=0; i&lt;max_tries; i++)
		{
			if(semctl(semid, 0, IPC_STAT, arg)==-1)
			{	perror("semctl error"); i=max_tries;}
			else
			{ 
				if(arg.buf-&gt;sem_otime!=0){ i=max_tries;  init_ok=1;}
				else	 sleep(1);	
			}
		}
		if(!init_ok)
  // do some initializing, here we assume that the first process that creates the sem will 
  // finish initialize the sem and run semop in max_tries*1 seconds. else it will not run 
  // semop any more.
		{
			arg.val=1;
			if(semctl(semid,0,SETVAL,arg)==-1) perror("semctl setval error");
		} 
	}
	else
	{perror("semget error, process exit");	exit();	}
}
else //semid&gt;=0; do some initializing 	
{
	arg.val=1;
	if(semctl(semid,0,SETVAL,arg)==-1)
		perror("semctl setval error");
}
//get some information about the semaphore and the limit of semaphore in redhat8.0
	arg.buf=&amp;sem_info;
	if(semctl(semid, 0, IPC_STAT, arg)==-1)
		perror("semctl IPC STAT");		
	printf("owner's uid is %d\n", 	arg.buf-&gt;sem_perm.uid);
	printf("owner's gid is %d\n", 	arg.buf-&gt;sem_perm.gid);
	printf("creater's uid is %d\n", 	arg.buf-&gt;sem_perm.cuid);
	printf("creater's gid is %d\n", 	arg.buf-&gt;sem_perm.cgid);

	arg.__buf=&amp;sem_info2;
	if(semctl(semid,0,IPC_INFO,arg)==-1)
		perror("semctl IPC_INFO");
	printf("the number of entries in semaphore map is %d \n",	 		arg.__buf-&gt;semmap);
	printf("max number of semaphore identifiers is %d \n", 		 	arg.__buf-&gt;semmni);
	printf("mas number of semaphores in system is %d \n",		 		arg.__buf-&gt;semmns);
	printf("the number of undo structures system wide is %d \n",	 	arg.__buf-&gt;semmnu);
	printf("max number of semaphores per semid is %d \n",		 		arg.__buf-&gt;semmsl);
	printf("max number of ops per semop call is %d \n",		 		arg.__buf-&gt;semopm);
	printf("max number of undo entries per process is %d \n", 	 		arg.__buf-&gt;semume);
	printf("the sizeof of struct sem_undo is %d \n", 	  	 			arg.__buf-&gt;semusz);
	printf("the maximum semaphore value is %d \n", 					arg.__buf-&gt;semvmx);
	
//now ask for available resource:	
	askfor_res.sem_num=0;
	askfor_res.sem_op=-1;
	askfor_res.sem_flg=SEM_UNDO;		
		
		if(semop(semid,&amp;askfor_res,1)==-1)//ask for resource
			perror("semop error");
	
	sleep(3); //do some handling on the sharing resource here, just sleep on it 3 seconds
	printf("now free the resource\n");	
	
//now free resource	
	free_res.sem_num=0;
	free_res.sem_op=1;
	free_res.sem_flg=SEM_UNDO;

	if(semop(semid,&amp;free_res,1)==-1)//free the resource.
		if(errno==EIDRM)
			printf("the semaphore set was removed\n");
//you can comment out the codes below to compile a different version:			
	if(semctl(semid, 0, IPC_RMID)==-1)
		perror("semctl IPC_RMID");
	else printf("remove sem ok\n");
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>注：读者可以尝试一下注释掉初始化步骤，进程在运行时会出现何种情况（进程在申请资源时会睡眠），同时可以像程序结尾给出的注释那样，把该程序编译成两个不同版本。下面是本程序的运行结果（操作系统redhat8.0）：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
owner's uid is 0
owner's gid is 0
creater's uid is 0
creater's gid is 0
the number of entries in semaphore map is 32000 
max number of semaphore identifiers is 128 
mas number of semaphores in system is 32000 
the number of undo structures system wide is 32000 
max number of semaphores per semid is 250 
max number of ops per semop call is 32 
max number of undo entries per process is 32 
the sizeof of struct sem_undo is 20 
the maximum semaphore value is 32767 
now free the resource
remove sem ok
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>Summary：信号灯与其它进程间通信方式有所不同，它主要用于进程间同步。通常所说的系统V信号灯实际上是一个信号灯的集合，可用于多种共享资源的进程间同步。每个信号灯都有一个值，可以用来表示当前该信号灯代表的共享资源可用（available）数量，如果一个进程要申请共享资源，那么就从信号灯值中减去要申请的数目，如果当前没有足够的可用资源，进程可以睡眠等待，也可以立即返回。当进程要申请多种共享资源时，linux可以保证操作的原子性，即要么申请到所有的共享资源，要么放弃所有资源，这样能够保证多个进程不会造成互锁。Linux对信号灯有各种各样的限制，程序中给出了输出结果。另外，如果读者想对信号灯作进一步的理解，建议阅读sem.h源代码，该文件不长，但给出了信号灯相关的重要数据结构。</P>
<P>附录1： struct sem_array如下：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
/*系统中的每个信号灯集对应一个sem_array 结构 */
struct sem_array {
	struct kern_ipc_perm	sem_perm;		/* permissions .. see ipc.h */
	time_t			sem_otime;			/* last semop time */
	time_t			sem_ctime;			/* last change time */
	struct sem		*sem_base;			/* ptr to first semaphore in array */
	struct sem_queue	*sem_pending;		/* pending operations to be processed */
	struct sem_queue	**sem_pending_last; 	/* last pending operation */
	struct sem_undo		*undo;			/* undo requests on this array */
	unsigned long		sem_nsems;		/* no. of semaphores in array */
};
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>其中，sem_queue结构如下：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
/* 系统中每个因为信号灯而睡眠的进程，都对应一个sem_queue结构*/
 struct sem_queue {
	struct sem_queue *	next;	 	/* next entry in the queue */
	struct sem_queue **	prev;	 	/* previous entry in the queue, *(q-&gt;prev) == q */
	struct task_struct*	sleeper; 	/* this process */
	struct sem_undo *	undo;	 	/* undo structure */
	int   pid;	 					/* process id of requesting process */
	int   status;	 				/* completion status of operation */
	struct sem_array *	sma;	 		/* semaphore array for operations */
	int	id;	 						/* internal sem id */
	struct sembuf *	sops;		 	/* array of pending operations */
	int	nsops;					 	/* number of operations */
	int	alter;	 					/* operation will alter semaphore */
};
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>附录2：union semun是系统调用semctl中的重要参数：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
union semun {
	int val;					/* value for SETVAL */
	struct semid_ds *buf;		/* buffer for IPC_STAT &amp; IPC_SET */
	unsigned short *array;		/* array for GETALL &amp; SETALL */
	struct seminfo *__buf;		/* buffer for IPC_INFO */   //test!!
	void *__pad;
};
struct  seminfo {
	int semmap;
	int semmni;
	int semmns;
	int semmnu;
	int semmsl;
	int semopm;
	int semume;
	int semusz;
	int semvmx;
	int semaem;
};
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=resources><SPAN class=atitle2>参考文献：</SPAN></A></P>
<P>[1] UNIX网络编程第二卷：进程间通信，作者：W.Richard Stevens，译者：杨继张，清华大学出版社。对POSIX以及系统V信号灯都有阐述，对Linux环境下的程序开发有极大的启发意义。</P>
<P>[2] linux内核源代码情景分析（上），毛德操、胡希明著，浙江大学出版社，给出了系统V信号灯相关的源代码分析，尤其在阐述保证操作原子性方面，以及阐述undo标志位时，讨论的很深刻。</P>
<P>[3]GNU/Linux编程指南，第二版，Kurt Wall等著，张辉译</P>
<P>[4]semget、semop、semctl手册</P>
<P><A name=author1><SPAN class=atitle2>关于作者：</SPAN></A></P>
<P>郑彦兴，国防科大攻读博士学位。联系方式： <A href="mailto:mlinux@163.com"><FONT color=#002c99>mlinux@163.com</FONT></A><BR><BR>from: <A href="http://www.ddvip.net/program/vc/index6/61.htm">http://www.ddvip.net/program/vc/index6/61.htm</A></P><img src ="http://www.blogjava.net/weidagang2046/aggbug/9193.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-08-04 13:06 <a href="http://www.blogjava.net/weidagang2046/articles/9193.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境进程间通信（三）：消息队列</title><link>http://www.blogjava.net/weidagang2046/articles/9192.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 04 Aug 2005 05:04:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/9192.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/9192.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/9192.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/9192.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/9192.html</trackback:ping><description><![CDATA[<BLOCKQUOTE>本系列文章中的前两部分，我们探讨<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part1/index.shtml"><FONT color=#002c99>管道</FONT></A>及<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part2/index1.shtml"><FONT color=#002c99>信号</FONT></A>两种通信机制，本文将深入第三部分，介绍系统 V 消息队列及其相应 API。</BLOCKQUOTE>
<P>消息队列（也叫做报文队列）能够克服早期unix通信机制的一些缺点。作为早期unix通信机制之一的信号能够传送的信息量有限，后来虽然POSIX 1003.1b在信号的实时性方面作了拓广，使得信号在传递信息量方面有了相当程度的改进，但是信号这种通信方式更像"即时"的通信方式，它要求接受信号的进程在某个时间范围内对信号做出反应，因此该信号最多在接受信号进程的生命周期内才有意义，信号所传递的信息是接近于随进程持续的概念（process-persistent），见<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/index.shtml#listing1"><FONT color=#002c99>附录 1</FONT></A>；管道及有名管道及有名管道则是典型的随进程持续IPC，并且，只能传送无格式的字节流无疑会给应用程序开发带来不便，另外，它的缓冲区大小也受到限制。</P>
<P>消息队列就是一个消息的链表。可以把消息看作一个记录，具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息；对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的（参见<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/index.shtml#listing1"><FONT color=#002c99>附录 1</FONT></A>）。</P>
<P>目前主要有两种类型的消息队列：POSIX消息队列以及系统V消息队列，系统V消息队列目前被大量使用。考虑到程序的可移植性，新开发的应用程序应尽量使用POSIX消息队列。</P>
<P>在本系列专题的序（深刻理解Linux进程间通信（IPC））中，提到对于消息队列、信号灯、以及共享内存区来说，有两个实现版本：POSIX的以及系统V的。Linux内核（内核2.4.18）支持POSIX信号灯、POSIX共享内存区以及POSIX消息队列，但对于主流Linux发行版本之一redhad8.0（内核2.4.18），还没有提供对POSIX进程间通信API的支持，不过应该只是时间上的事。</P>
<P>因此，本文将主要介绍系统V消息队列及其相应API。<B>在没有声明的情况下，以下讨论中指的都是系统V消息队列。</B></P>
<P><A id=1 name=1><SPAN class=atitle2>一、消息队列基本概念</SPAN></A></P>
<OL class=n01>
<LI>系统V消息队列是随内核持续的，只有在内核重起或者显示删除一个消息队列时，该消息队列才会真正被删除。因此系统中记录消息队列的数据结构（struct ipc_ids msg_ids）位于内核中，系统中的所有消息队列都可以在结构msg_ids中找到访问入口。 
<LI>消息队列就是一个消息的链表。每个消息队列都有一个队列头，用结构struct msg_queue来描述（参见<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/index.shtml#listing2"><FONT color=#002c99>附录 2</FONT></A>）。队列头中包含了该消息队列的大量信息，包括消息队列键值、用户ID、组ID、消息队列中消息数目等等，甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息，也可以设置其中的某些信息。 
<LI>下图说明了内核与消息队列是怎样建立起联系的：<BR>其中：struct ipc_ids msg_ids是内核中记录消息队列的全局数据结构；struct msg_queue是每个消息队列的队列头。 
<CENTER><IMG height=219 alt="" src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/fig1.gif" width=529 border=0></CENTER></LI></OL>
<P>从上图可以看出，全局数据结构 struct ipc_ids msg_ids 可以访问到每个消息队列头的第一个成员：struct kern_ipc_perm；而每个struct kern_ipc_perm能够与具体的消息队列对应起来是因为在该结构中，有一个key_t类型成员key，而key则唯一确定一个消息队列。kern_ipc_perm结构如下：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>struct kern_ipc_perm{   //内核中记录消息队列的全局数据结构msg_ids能够访问到该结构；
            key_t   key;    //该键值则唯一对应一个消息队列
            uid_t   uid;
            gid_t   gid;
uid_t   cuid;
gid_t   cgid;
mode_t  mode;
unsigned long seq;
}
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR>
<P><A id=2 name=2><SPAN class=atitle2>二、操作消息队列</SPAN></A></P>
<P><SPAN class=atitle3>对消息队列的操作无非有下面三种类型：</SPAN></P>
<P>1、 打开或创建消息队列<BR>消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值，所以，要获得一个消息队列的描述字，只需提供该消息队列的键值即可；</P>
<P>注：消息队列描述字是由在系统范围内唯一的键值生成的，而键值可以看作对应系统内的一条路经。</P>
<P>2、 读写操作</P>
<P>消息读写操作非常简单，对开发人员来说，每个消息都类似如下的数据结构：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>struct msgbuf{
long mtype;
char mtext[1];
};</CODE>
</PRE></TD></TR></TBODY></TABLE><BR>
<P>mtype成员代表消息类型，从消息队列中读取消息的一个重要依据就是消息的类型；mtext是消息内容，当然长度不一定为1。因此，对于发送消息来说，首先预置一个msgbuf缓冲区并写入消息类型和内容，调用相应的发送函数即可；对读取消息来说，首先分配这样一个msgbuf缓冲区，然后把消息读入该缓冲区即可。</P>
<P>3、 获得或设置消息队列属性：</P>
<P>消息队列的信息基本上都保存在消息队列头中，因此，可以分配一个类似于消息队列头的结构(struct msqid_ds，见<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/index.shtml#listing2"><FONT color=#002c99>附录 2</FONT></A>)，来返回消息队列的属性；同样可以设置该数据结构。</P><BR>
<CENTER><IMG height=219 alt="" src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/fig2.gif" width=507 border=0></CENTER><BR>
<P><SPAN class=atitle3>消息队列API</SPAN></P>
<P><B>1、文件名到键值</B></P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>#include &lt;sys/types.h&gt;
#include &lt;sys/ipc.h&gt;
key_t ftok (char*pathname, char proj)；
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR>
<P>它返回与路径pathname相对应的一个键值。该函数不直接对消息队列操作，但在调用ipc(MSGGET,…)或msgget()来获得消息队列描述字前，往往要调用该函数。典型的调用代码是：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>   key=ftok(path_ptr, 'a');
    ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0);
    …
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR>
<P><B>2、linux为操作系统V进程间通信的三种方式（消息队列、信号灯、共享内存区）提供了一个统一的用户界面：</B><BR><CODE><FONT face=新宋体><B>int ipc</B>(unsigned int <B>call</B>, int <B>first</B>, int <B>second</B>, int <B>third</B>, void *<B>ptr</B>, long <B>fifth</B>);</FONT></CODE></P>
<P>第一个参数指明对IPC对象的操作方式，对消息队列而言共有四种操作：MSGSND、MSGRCV、MSGGET以及MSGCTL，分别代表向消息队列发送消息、从消息队列读取消息、打开或创建消息队列、控制消息队列；first参数代表唯一的IPC对象；下面将介绍四种操作。</P>
<UL class=n01>
<LI><B>int ipc</B>(<B>MSGGET, int</B> first, <B>int</B> second, <B>int</B> third, <B>void</B> *ptr, <B>long</B> fifth);<BR>与该操作对应的系统V调用为：int msgget( (key_t)first，second)。 
<LI><B>int ipc</B>(<B>MSGCTL, int</B> first, <B>int</B> second, <B>int</B> third, <B>void</B> *ptr, <B>long</B> fifth)<BR>与该操作对应的系统V调用为：int msgctl( first，second, (struct msqid_ds*) ptr)。 
<LI><B>int ipc</B>(<B>MSGSND, int</B> first, <B>int</B> second, <B>int</B> third, <B>void</B> *ptr, <B>long</B> fifth);<BR>与该操作对应的系统V调用为：int msgsnd( first, (struct msgbuf*)ptr, second, third)。 
<LI><B>int ipc</B>(<B>MSGRCV, int</B> first, <B>int</B> second, <B>int</B> third, <B>void</B> *ptr, <B>long</B> fifth);<BR>与该操作对应的系统V调用为：int msgrcv( first，(struct msgbuf*)ptr, second, fifth,third)， </LI></UL><BR>
<P>注：本人不主张采用系统调用ipc()，而更倾向于采用系统V或者POSIX进程间通信API。原因如下：</P>
<UL class=n01>
<LI>虽然该系统调用提供了统一的用户界面，但正是由于这个特性，它的参数几乎不能给出特定的实际意义（如以first、second来命名参数），在一定程度上造成开发不便。 
<LI>正如ipc手册所说的：ipc()是linux所特有的，编写程序时应注意程序的移植性问题； 
<LI>该系统调用的实现不过是把系统V IPC函数进行了封装，没有任何效率上的优势； 
<LI>系统V在IPC方面的API数量不多，形式也较简洁。 </LI></UL><BR>
<P><B>3.系统V消息队列API</B><BR>系统V消息队列API共有四个，使用时需要包括几个头文件：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>#include &lt;sys/types.h&gt;
#include &lt;sys/ipc.h&gt;
#include &lt;sys/msg.h&gt;
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR>
<P><B>1）int msgget(key_t key, int msgflg)</B></P>
<P>参数key是一个键值，由ftok获得；msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。</P>
<P>在以下两种情况下，该调用将创建一个新的消息队列：</P>
<UL class=n01>
<LI>如果没有消息队列与健值key相对应，并且msgflg中包含了IPC_CREAT标志位； 
<LI>key参数为IPC_PRIVATE； </LI></UL><BR>
<P>参数msgflg可以为以下：IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。</P>
<P><B>调用返回：</B>成功返回消息队列描述字，否则返回-1。</P>
<P>注：参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列，只意味着即将创建新的消息队列。</P>
<P><B>2）int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);</B><BR>该系统调用从msgid代表的消息队列中读取一个消息，并把消息存储在msgp指向的msgbuf结构中。</P>
<P>msqid为消息队列描述字；消息返回后存储在msgp指向的地址，msgsz指定msgbuf的mtext成员的长度（即消息内容的长度），msgtyp为请求读取的消息类型；读消息标志msgflg可以为以下几个常值的或：</P>
<UL class=n01>
<LI>IPC_NOWAIT 如果没有满足条件的消息，调用立即返回，此时，errno=ENOMSG 
<LI>IPC_EXCEPT 与msgtyp&gt;0配合使用，返回队列中第一个类型不为msgtyp的消息 
<LI>IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节，则把该消息截断，截断部分将丢失。 </LI></UL><BR>
<P>msgrcv手册中详细给出了消息类型取不同值时(&gt;0; &lt;0; =0)，调用将返回消息队列中的哪个消息。</P>
<P>msgrcv()解除阻塞的条件有三个：</P>
<OL class=n01>
<LI>消息队列中有了满足条件的消息； 
<LI>msqid代表的消息队列被删除； 
<LI>调用msgrcv（）的进程被信号中断； </LI></OL><BR>
<P><B>调用返回：</B>成功返回读出消息的实际字节数，否则返回-1。</P>
<P><B>3）int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);</B><BR>向msgid代表的消息队列发送一个消息，即将发送的消息存储在msgp指向的msgbuf结构中，消息的大小由msgze指定。</P>
<P>对发送消息来说，有意义的msgflg标志为IPC_NOWAIT，指明在消息队列没有足够空间容纳要发送的消息时，msgsnd是否等待。造成msgsnd()等待的条件有两种：</P>
<UL class=n01>
<LI>当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容量； 
<LI>当前消息队列的消息数（单位"个"）不小于消息队列的总容量（单位"字节数"），此时，虽然消息队列中的消息数目很多，但基本上都只有一个字节。 </LI></UL><BR>msgsnd()解除阻塞的条件有三个： 
<OL class=n01>
<LI>不满足上述两个条件，即消息队列中有容纳该消息的空间； 
<LI>msqid代表的消息队列被删除； 
<LI>调用msgsnd（）的进程被信号中断； </LI></OL><BR>
<P><B>调用返回：</B>成功返回0，否则返回-1。</P>
<P><B>4）int msgctl(int msqid, int cmd, struct msqid_ds *buf);</B><BR>该系统调用对由msqid标识的消息队列执行cmd操作，共有三种cmd操作：IPC_STAT、IPC_SET 、IPC_RMID。</P>
<OL class=n01>
<LI>IPC_STAT：该命令用来获取消息队列信息，返回的信息存贮在buf指向的msqid结构中； 
<LI>IPC_SET：该命令用来设置消息队列的属性，要设置的属性存储在buf指向的msqid结构中；可设置属性包括：msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes，同时，也影响msg_ctime成员。 
<LI>IPC_RMID：删除msqid标识的消息队列； </LI></OL><BR>
<P><B>调用返回：</B>成功返回0，否则返回-1。</P>
<P><A id=3 name=3><SPAN class=atitle2>三、消息队列的限制</SPAN></A><BR>每个消息队列的容量（所能容纳的字节数）都有限制，该值因系统不同而不同。在后面的应用实例中，输出了redhat 8.0的限制，结果参见<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/index.shtml#listing3"><FONT color=#002c99>附录 3</FONT></A>。</P>
<P>另一个限制是每个消息队列所能容纳的最大消息数：在redhad 8.0中，该限制是受消息队列容量制约的：消息个数要小于消息队列的容量（字节数）。</P>
<P>注：上述两个限制是针对每个消息队列而言的，系统对消息队列的限制还有系统范围内的最大消息队列个数，以及整个系统范围内的最大消息数。一般来说，实际开发过程中不会超过这个限制。</P>
<P><A id=4 name=4><SPAN class=atitle2>四、消息队列应用实例</SPAN></A><BR>消息队列应用相对较简单，下面实例基本上覆盖了对消息队列的所有操作，同时，程序输出结果有助于加深对前面所讲的某些规则及消息队列限制的理解。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>#include &lt;sys/types.h&gt;
#include &lt;sys/msg.h&gt;
#include &lt;unistd.h&gt;
void msg_stat(int,struct msqid_ds );
main()
{
int gflags,sflags,rflags;
key_t key;
int msgid;
int reval;
struct msgsbuf{
        int mtype;
        char mtext[1];
    }msg_sbuf;
struct msgmbuf
    {
    int mtype;
    char mtext[10];
    }msg_rbuf;
struct msqid_ds msg_ginfo,msg_sinfo;
char* msgpath="/unix/msgqueue";
key=ftok(msgpath,'a');
gflags=IPC_CREAT|IPC_EXCL;
msgid=msgget(key,gflags|00666);
if(msgid==-1)
{
    printf("msg create error\n");
    return;
}
//创建一个消息队列后，输出消息队列缺省属性
msg_stat(msgid,msg_ginfo);
sflags=IPC_NOWAIT;
msg_sbuf.mtype=10;
msg_sbuf.mtext[0]='a';
reval=msgsnd(msgid,&amp;msg_sbuf,sizeof(msg_sbuf.mtext),sflags);
if(reval==-1)
{
    printf("message send error\n");
}
//发送一个消息后，输出消息队列属性
msg_stat(msgid,msg_ginfo);
rflags=IPC_NOWAIT|MSG_NOERROR;
reval=msgrcv(msgid,&amp;msg_rbuf,4,10,rflags);
if(reval==-1)
    printf("read msg error\n");
else
    printf("read from msg queue %d bytes\n",reval);
//从消息队列中读出消息后，输出消息队列属性
msg_stat(msgid,msg_ginfo);
msg_sinfo.msg_perm.uid=8;//just a try
msg_sinfo.msg_perm.gid=8;//
msg_sinfo.msg_qbytes=16388;
//此处验证超级用户可以更改消息队列的缺省msg_qbytes
//注意这里设置的值大于缺省值
reval=msgctl(msgid,IPC_SET,&amp;msg_sinfo);
if(reval==-1)
{
    printf("msg set info error\n");
    return;
}
msg_stat(msgid,msg_ginfo);
//验证设置消息队列属性
reval=msgctl(msgid,IPC_RMID,NULL);//删除消息队列
if(reval==-1)
{
    printf("unlink msg queue error\n");
    return;
}
}
void msg_stat(int msgid,struct msqid_ds msg_info)
{
int reval;
sleep(1);//只是为了后面输出时间的方便
reval=msgctl(msgid,IPC_STAT,&amp;msg_info);
if(reval==-1)
{
    printf("get msg info error\n");
    return;
}
printf("\n");
printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);
printf("number of messages in queue is %d\n",msg_info.msg_qnum);
printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
//每个消息队列的容量（字节数）都有限制MSGMNB，值的大小因系统而异。在创建新的消息队列时，//msg_qbytes的缺省值就是MSGMNB
printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);
printf("last msgsnd time is %s", ctime(&amp;(msg_info.msg_stime)));
printf("last msgrcv time is %s", ctime(&amp;(msg_info.msg_rtime)));
printf("last change time is %s", ctime(&amp;(msg_info.msg_ctime)));
printf("msg uid is %d\n",msg_info.msg_perm.uid);
printf("msg gid is %d\n",msg_info.msg_perm.gid);
}
</CODE>
</PRE></TD></TR></TBODY></TABLE>程序输出结果见<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/index.shtml#listing3"><FONT color=#002c99>附录 3</FONT></A>。<BR>
<P><A id=5 name=5><SPAN class=atitle2>小结：</SPAN></A><BR>消息队列与管道以及有名管道相比，具有更大的灵活性，首先，它提供有格式字节流，有利于减少开发人员的工作量；其次，消息具有类型，在实际应用中，可作为优先级使用。这两点是管道以及有名管道所不能比的。同样，消息队列可以在几个进程间复用，而不管这几个进程是否具有亲缘关系，这一点与有名管道很相似；但消息队列是随内核持续的，与有名管道（随进程持续）相比，生命力更强，应用空间更大。</P>
<P><B><A name=listing1>附录 1</A>：</B>在参考文献[1]中，给出了IPC随进程持续、随内核持续以及随文件系统持续的定义：</P>
<OL>
<LI>随进程持续：IPC一直存在到打开IPC对象的最后一个进程关闭该对象为止。如管道和有名管道； 
<LI>随内核持续：IPC一直持续到内核重新自举或者显示删除该对象为止。如消息队列、信号灯以及共享内存等； 
<LI>随文件系统持续：IPC一直持续到显示删除该对象为止。 </LI></OL><BR>
<P><B><A name=listing2>附录 2</A>：</B><BR>结构msg_queue用来描述消息队列头，存在于系统空间：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>struct msg_queue {
    struct kern_ipc_perm q_perm;
    time_t q_stime;         /* last msgsnd time */
    time_t q_rtime;         /* last msgrcv time */
    time_t q_ctime;         /* last change time */
    unsigned long q_cbytes;     /* current number of bytes on queue */
    unsigned long q_qnum;       /* number of messages in queue */
    unsigned long q_qbytes;     /* max number of bytes on queue */
    pid_t q_lspid;          /* pid of last msgsnd */
    pid_t q_lrpid;          /* last receive pid */
    struct list_head q_messages;
    struct list_head q_receivers;
    struct list_head q_senders;
};
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR>
<P>结构msqid_ds用来设置或返回消息队列的信息，存在于用户空间；</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>struct msqid_ds {
    struct ipc_perm msg_perm;
    struct msg *msg_first;      /* first message on queue,unused  */
    struct msg *msg_last;       /* last message in queue,unused */
    __kernel_time_t msg_stime;  /* last msgsnd time */
    __kernel_time_t msg_rtime;  /* last msgrcv time */
    __kernel_time_t msg_ctime;  /* last change time */
    unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
    unsigned long  msg_lqbytes; /* ditto */
    unsigned short msg_cbytes;  /* current number of bytes on queue */
    unsigned short msg_qnum;    /* number of messages in queue */
    unsigned short msg_qbytes;  /* max number of bytes on queue */
    __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};
</CODE>
</PRE></TD></TR></TBODY></TABLE>//可以看出上述两个结构很相似。<BR>
<P><B><A name=listing3>附录 3</A>：</B>消息队列实例输出结果：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384
pid of last msgsnd is 0
pid of last msgrcv is 0
last msgsnd time is Thu Jan  1 08:00:00 1970
last msgrcv time is Thu Jan  1 08:00:00 1970
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
//上面刚刚创建一个新消息队列时的输出
current number of bytes on queue is 1
number of messages in queue is 1
max number of bytes on queue is 16384
pid of last msgsnd is 2510
pid of last msgrcv is 0
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Thu Jan  1 08:00:00 1970
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
read from msg queue 1 bytes
//实际读出的字节数
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384   //每个消息队列最大容量（字节数）
pid of last msgsnd is 2510
pid of last msgrcv is 2510
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Sun Dec 29 18:28:22 2002
last change time is Sun Dec 29 18:28:20 2002
msg uid is 0
msg gid is 0
current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16388   //可看出超级用户可修改消息队列最大容量
pid of last msgsnd is 2510
pid of last msgrcv is 2510  //对操作消息队列进程的跟踪
last msgsnd time is Sun Dec 29 18:28:21 2002
last msgrcv time is Sun Dec 29 18:28:22 2002
last change time is Sun Dec 29 18:28:23 2002    //msgctl()调用对msg_ctime有影响
msg uid is 8
msg gid is 8
</CODE>
</PRE></TD></TR></TBODY></TABLE><BR><!-- RESOURCES-->
<P><A id=resources name=resources><SPAN class=atitle2>参考文献：</SPAN></A></P>
<UL>
<LI>UNIX网络编程第二卷：进程间通信，作者：W.Richard Stevens，译者：杨继张，清华大学出版社。对POSIX以及系统V消息队列都有阐述，对Linux环境下的程序开发有极大的启发意义。 
<LI>linux内核源代码情景分析（上），毛德操、胡希明著，浙江大学出版社，给出了系统V消息队列相关的源代码分析。 
<LI><A href="http://www.fanqiang.com/a4/b2/20010508/113315.html" target=_blank><FONT color=#002c99>http://www.fanqiang.com/a4/b2/20010508/113315.html</FONT></A>，主要阐述linux下对文件的操作，详细介绍了对文件的存取权限位，对IPC对象的存取权限同样具有很好的借鉴意义。 
<LI>msgget、msgsnd、msgrcv、msgctl手册 </LI></UL><BR><!-- AUTHOR BIOS--><!-- Make author heading singular or plural as needed-->
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A id=author1 name=author1><SPAN class=atitle2>关于作者：</SPAN></A> <BR>郑彦兴，国防科大攻读博士学位。联系方式： <A href="mailto:mlinux@163.com"><FONT color=#002c99>mlinux@163.com</FONT></A></TD></TR></TBODY></TABLE>from: <A href="http://www.ddvip.net/program/vc/index6/60.htm">http://www.ddvip.net/program/vc/index6/60.htm</A><img src ="http://www.blogjava.net/weidagang2046/aggbug/9192.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-08-04 13:04 <a href="http://www.blogjava.net/weidagang2046/articles/9192.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境进程间通信（二）：信号（下）</title><link>http://www.blogjava.net/weidagang2046/articles/9191.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 04 Aug 2005 05:03:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/9191.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/9191.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/9191.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/9191.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/9191.html</trackback:ping><description><![CDATA[<BLOCKQUOTE>在信号（上）中，讨论了linux信号种类、来源、如何安装一个信号以及对信号集的操作。本部分则首先讨论从信号的生命周期上认识信号，或者宏观上看似简单的信号机制（进程收到信号后，作相应的处理，看上去再简单不过了），在微观上究竟是如何实现的，也是在更深层次上理解信号。接下来还讨论了信号编程的一些注意事项，最后给出了信号编程的一些实例。</BLOCKQUOTE>
<P><A name=1><SPAN class=atitle2>一、信号生命周期</SPAN></A><BR>从信号发送到信号处理函数的执行完毕</P>
<P>对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说，可以分为三个重要的阶段，这三个阶段由四个重要事件来刻画：信号诞生；信号在进程中注册完毕；信号在进程中的注销完毕；信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。</P>
<P><IMG height=34 alt="" src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part2/3.gif" width=594></P>
<P>下面阐述四个事件的实际意义：</P>
<OL>
<LI>信号"诞生"。信号的诞生指的是触发信号的事件发生（如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等）。 
<LI>信号在目标进程中"注册"；进程的task_struct结构中有关于本进程中未决信号的数据成员： <PRE><CODE>struct sigpending pending：
struct sigpending{
	struct sigqueue *head, **tail;
	sigset_t signal;
};
</PRE></CODE><FONT face="宋体, MS Song">第三个成员是进程中所有未决信号集，第一、第二个成员分别指向一个sigqueue类型的结构链（称之为"未决信号信息链"）的首尾，信息链中的每个sigqueue结构刻画一个特定信号所携带的信息，并指向下一个sigqueue结构: </FONT><PRE><CODE>struct sigqueue{
	struct sigqueue *next;
	siginfo_t info;
}
</PRE></CODE><FONT face="宋体, MS Song">信号在进程中注册指的就是信号值加入到进程的未决信号集中（sigpending结构的第二个成员sigset_t signal），并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中，表明进程已经知道这些信号的存在，但还没来得及处理，或者该信号被进程阻塞。 </FONT>
<P><B>注：</B><BR>当一个实时信号发送给一个进程时，不管该信号是否已经在进程中注册，都会被再注册一次，因此，信号不会丢失，因此，实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构（进程每收到一个实时信号，都会为它分配一个结构来登记该信号信息，并把该结构添加在未决信号链尾，即所有诞生的实时信号都会在目标进程中注册）；<BR>当一个非实时信号发送给一个进程时，如果该信号已经在进程中注册，则该信号将被丢弃，造成信号丢失。因此，非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中，至多占有一个sigqueue结构（一个非实时信号诞生后，（1）、如果发现相同的信号已经在目标结构中注册，则不再注册，对于进程来说，相当于不知道本次信号发生，信号丢失；（2）、如果进程的未决信号中没有相同信号，则在进程中注册自己）。</P>
<LI>信号在进程中的注销。在目标进程执行过程中，会检测是否有信号等待处理（每次从系统空间返回到用户空间时都做这样的检查）。如果存在未决信号等待处理且该信号没有被进程阻塞，则在运行相应的信号处理函数前，进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中删除对于实时与非实时信号是不同的。对于非实时信号来说，由于在未决信号信息链中最多只占用一个sigqueue结构，因此该结构被释放后，应该把信号在进程未决信号集中删除（信号注销完毕）；而对于实时信号来说，可能在未决信号信息链中占用多个sigqueue结构，因此应该针对占用sigqueue结构的数目区别对待：如果只占用一个sigqueue结构（进程只收到该信号一次），则应该把信号在进程的未决信号集中删除（信号注销完毕）。否则，不应该在进程的未决信号集中删除该信号（信号注销完毕）。<BR>进程在执行信号相应处理函数之前，首先要把信号在进程中注销。 
<LI>信号生命终止。进程注销信号后，立即执行相应的信号处理函数，执行完毕后，信号的本次发送对进程的影响彻底结束。 
<P><B>注：</B><BR>1）信号注册与否，与发送信号的函数（如kill()或sigqueue()等）以及信号安装函数（signal()及sigaction()）无关，只与信号值有关（信号值小于SIGRTMIN的信号最多只注册一次，信号值在SIGRTMIN及SIGRTMAX之间的信号，只要被进程接收到就被注册）。<BR>2）在信号被注销到相应的信号处理函数执行完毕这段时间内，如果进程又收到同一信号多次，则对实时信号来说，每一次都会在进程中注册；而对于非实时信号来说，无论收到多少次信号，都会视为只收到一个信号，只在进程中注册一次。</P></LI></OL>
<P><A name=2><SPAN class=atitle2>二、信号编程注意事项</SPAN></A></P>
<OL>
<LI>防止不该丢失的信号丢失。如果对八中所提到的信号生命周期理解深刻的话，很容易知道信号会不会丢失，以及在哪里丢失。 
<LI><B>程序的可移植性</B><BR>考虑到程序的可移植性，应该尽量采用POSIX信号函数，POSIX信号函数主要分为两类： 
<UL>
<LI>POSIX 1003.1信号函数： Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、sigpending()、sigprocmask()、sigsuspend()。 
<LI>POSIX 1003.1b信号函数。POSIX 1003.1b在信号的实时性方面对POSIX 1003.1做了扩展，包括以下三个函数： sigqueue()、sigtimedwait()、sigwaitinfo()。其中，sigqueue主要针对信号发送，而sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函数，后面有相应实例。 <PRE><CODE>#include &lt;signal.h&gt;
int sigwaitinfo(sigset_t *set, siginfo_t *info).
</PRE></CODE><FONT face="宋体, MS Song">该函数与sigsuspend()类似，阻塞一个进程直到特定信号发生，但信号到来时不执行信号处理函数，而是返回信号值。因此为了避免执行相应的信号处理函数，必须在调用该函数前，使进程屏蔽掉set指向的信号，因此调用该函数的典型代码是： </FONT><PRE><CODE>sigset_t newmask;
int rcvd_sig; 
siginfo_t info;

sigemptyset(&amp;newmask);
sigaddset(&amp;newmask, SIGRTMIN);
sigprocmask(SIG_BLOCK, &amp;newmask, NULL);
rcvd_sig = sigwaitinfo(&amp;newmask, &amp;info) 
if (rcvd_sig == -1) {
	..
}
</PRE></CODE><FONT face="宋体, MS Song">调用成功返回信号值，否则返回-1。sigtimedwait()功能相似，只不过增加了一个进程等待的时间。 </FONT></LI></UL>
<LI><B>程序的稳定性。</B><BR>为了增强程序的稳定性，在信号处理函数中应使用可重入函数。 
<P>信号处理程序中应当使用可再入（可重入）函数（注：所谓可重入函数是指一个可以被多个任务调用的过程，任务在调用时不必担心数据是否会出错）。因为进程在收到信号后，就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数，那么信号处理函数可能会修改原来进程中不应该被修改的数据，这样进程从信号处理函数中返回接着执行时，可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。</P>
<P>满足下列条件的函数多数是不可再入的：（1）使用静态的数据结构，如getlogin()，gmtime()，getgrgid()，getgrnam()，getpwuid()以及getpwnam()等等；（2）函数实现时，调用了malloc（）或者free()函数；（3）实现时使用了标准I/O函数的。The Open Group视下列函数为可再入的： 
<P><CODE><FONT face=新宋体>_exit（）、access（）、alarm（）、cfgetispeed（）、cfgetospeed（）、cfsetispeed（）、cfsetospeed（）、chdir（）、chmod（）、chown（）、close（）、creat（）、dup（）、dup2（）、execle（）、execve（）、fcntl（）、fork（）、fpathconf（）、fstat（）、fsync（）、getegid（）、 geteuid（）、getgid（）、getgroups（）、getpgrp（）、getpid（）、getppid（）、getuid（）、kill（）、link（）、lseek（）、mkdir（）、mkfifo（）、 open（）、pathconf（）、pause（）、pipe（）、raise（）、read（）、rename（）、rmdir（）、setgid（）、setpgid（）、setsid（）、setuid（）、 sigaction（）、sigaddset（）、sigdelset（）、sigemptyset（）、sigfillset（）、sigismember（）、signal（）、sigpending（）、sigprocmask（）、sigsuspend（）、sleep（）、stat（）、sysconf（）、tcdrain（）、tcflow（）、tcflush（）、tcgetattr（）、tcgetpgrp（）、tcsendbreak（）、tcsetattr（）、tcsetpgrp（）、time（）、times（）、 umask（）、uname（）、unlink（）、utime（）、wait（）、waitpid（）、write（）。 </FONT></CODE>
<P>即使信号处理函数使用的都是"安全函数"，同样要注意进入处理函数时，首先要保存errno的值，结束时，再恢复原值。因为，信号处理过程中，errno值随时可能被改变。另外，longjmp()以及siglongjmp()没有被列为可再入函数，因为不能保证紧接着两个函数的其它调用是安全的。</P></LI></OL>
<P><A name=3><SPAN class=atitle2>三、深入浅出：信号应用实例</SPAN></A></P>
<P>linux下的信号应用并没有想象的那么恐怖，程序员所要做的最多只有三件事情：</P>
<OL>
<LI>安装信号（推荐使用sigaction()）； 
<LI>实现三参数信号处理函数，handler(int signal,struct siginfo *info, void *)； 
<LI>发送信号，推荐使用sigqueue()。 </LI></OL>
<P>实际上，对有些信号来说，只要安装信号就足够了（信号处理方式采用缺省或忽略）。其他可能要做的无非是与信号集相关的几种操作。</P>
<P><B>实例一：信号发送及处理</B><BR>实现一个信号接收程序sigreceive（其中信号安装由sigaction（））。</P><PRE><CODE>#include &lt;signal.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
	struct sigaction act;	
	int sig;
	sig=atoi(argv[1]);
	
	sigemptyset(&amp;act.sa_mask);
	act.sa_flags=SA_SIGINFO;
	act.sa_sigaction=new_op;
	
	if(sigaction(sig,&amp;act,NULL) &lt; 0)
	{
		printf("install sigal error\n");
	}
	
	while(1)
	{
		sleep(2);
		printf("wait for the signal\n");
	}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
	printf("receive signal %d", signum);
	sleep(5);
}
</PRE></CODE>
<P>说明，命令行参数为信号值，后台运行sigreceive signo &amp;，可获得该进程的ID，假设为pid，然后再另一终端上运行kill -s signo pid验证信号的发送接收及处理。同时，可验证信号的排队问题。<BR><B>注：</B>可以用sigqueue实现一个命令行信号发送程序sigqueuesend，见<A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part2/index2.shtml#5"><FONT color=#002c99>附录1</FONT></A>。 </P>
<P><B>实例二：信号传递附加信息</B><BR>主要包括两个实例：</P>
<OL>
<LI>向进程本身发送信号，并传递指针参数； <PRE><CODE>#include &lt;signal.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
	struct sigaction act;	
	union sigval mysigval;
	int i;
	int sig;
	pid_t pid;		
	char data[10];
	memset(data,0,sizeof(data));
	for(i=0;i &lt; 5;i++)
		data[i]='2';
	mysigval.sival_ptr=data;
	
	sig=atoi(argv[1]);
	pid=getpid();
	
	sigemptyset(&amp;act.sa_mask);
	act.sa_sigaction=new_op;//三参数信号处理函数
	act.sa_flags=SA_SIGINFO;//信息传递开关
	if(sigaction(sig,&amp;act,NULL) &lt; 0)
	{
		printf("install sigal error\n");
	}
	while(1)
	{
		sleep(2);
		printf("wait for the signal\n");
		sigqueue(pid,sig,mysigval);//向本进程发送信号，并传递附加信息
	}

}

void new_op(int signum,siginfo_t *info,void *myact)//三参数信号处理函数的实现
{
	int i;
	for(i=0;i&lt;10;i++)
	{
		printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
	}
	printf("handle signal %d over;",signum);
}

</PRE></CODE>
<P>这个例子中，信号实现了附加信息的传递，信号究竟如何对这些信息进行处理则取决于具体的应用。</P>
<LI>2、 不同进程间传递整型参数：把1中的信号发送和接收放在两个程序中，并且在发送过程中传递整型参数。<BR>信号接收程序： <PRE><CODE>#include &lt;signal.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
	struct sigaction act;
	int sig;
	pid_t pid;		
	
	pid=getpid();
	sig=atoi(argv[1]);	
	
	sigemptyset(&amp;act.sa_mask);
	act.sa_sigaction=new_op;
	act.sa_flags=SA_SIGINFO;
	if(sigaction(sig,&amp;act,NULL)&lt;0)
	{
		printf("install sigal error\n");
	}
	while(1)
	{
		sleep(2);
		printf("wait for the signal\n");
	}

}
void new_op(int signum,siginfo_t *info,void *myact)
{
	printf("the int value is %d \n",info-&gt;si_int);
}

</PRE></CODE>
<P>信号发送程序：命令行第二个参数为信号值，第三个参数为接收进程ID。</P><PRE><CODE>#include &lt;signal.h&gt;
#include &lt;sys/time.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/types.h&gt;
main(int argc,char**argv)
{
	pid_t pid;
	int signum;
	union sigval mysigval;

	signum=atoi(argv[1]);
	pid=(pid_t)atoi(argv[2]);
	mysigval.sival_int=8;//不代表具体含义，只用于说明问题

	if(sigqueue(pid,signum,mysigval)==-1)
		printf("send error\n");
	sleep(2);
}

</PRE></CODE>
<P><B>注：</B>实例2的两个例子侧重点在于用信号来传递信息，目前关于在linux下通过信号传递信息的实例非常少，倒是Unix下有一些，但传递的基本上都是关于传递一个整数，传递指针的我还没看到。我一直没有实现不同进程间的指针传递（实际上更有意义），也许在实现方法上存在问题吧，请实现者email我。</P></LI></OL><!--example3-->
<P><B>实例三：信号阻塞及信号集操作</B></P><PRE><CODE>#include "signal.h"
#include "unistd.h"
static void my_op(int);
main()
{
	sigset_t new_mask,old_mask,pending_mask;
	struct sigaction act;

	sigemptyset(&amp;act.sa_mask);
	act.sa_flags=SA_SIGINFO;
	act.sa_sigaction=(void*)my_op;
	if(sigaction(SIGRTMIN+10,&amp;act,NULL))
		printf("install signal SIGRTMIN+10 error\n");

	sigemptyset(&amp;new_mask);
	sigaddset(&amp;new_mask,SIGRTMIN+10);
	if(sigprocmask(SIG_BLOCK, &amp;new_mask,&amp;old_mask))
		printf("block signal SIGRTMIN+10 error\n");

	sleep(10);	
	printf("now begin to get pending mask and unblock SIGRTMIN+10\n");
	if(sigpending(&amp;pending_mask)&lt;0)
		printf("get pending mask error\n");
	if(sigismember(&amp;pending_mask,SIGRTMIN+10))
		printf("signal SIGRTMIN+10 is pending\n");

	if(sigprocmask(SIG_SETMASK,&amp;old_mask,NULL)&lt;0)
		printf("unblock signal error\n");
	printf("signal unblocked\n");

	sleep(10);
}
static void my_op(int signum)
{
	printf("receive signal %d \n",signum);
}

</PRE></CODE>
<P>编译该程序，并以后台方式运行。在另一终端向该进程发送信号(运行kill -s 42 pid，SIGRTMIN+10为42)，查看结果可以看出几个关键函数的运行机制，信号集相关操作比较简单。</P>
<P><B>注：</B>在上面几个实例中，使用了printf()函数，只是作为诊断工具，pringf()函数是不可重入的，不应在信号处理函数中使用。</P>
<P><A name=4><SPAN class=atitle2>结束语：</SPAN></A></P>
<P>系统地对linux信号机制进行分析、总结使我受益匪浅！感谢王小乐等网友的支持！<BR>Comments and suggestions are greatly welcome! </P>
<P><A name=5><SPAN class=atitle2>附录1：</SPAN></A></P>
<P>用sigqueue实现的命令行信号发送程序sigqueuesend，命令行第二个参数是发送的信号值，第三个参数是接收该信号的进程ID，可以配合实例一使用：</P><PRE><CODE>#include &lt;signal.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
int main(int argc,char**argv)
{
	pid_t pid;
	int sig;
	sig=atoi(argv[1]);
	pid=atoi(argv[2]);
	sigqueue(pid,sig,NULL);
	sleep(2);
}

</PRE></CODE><!--
<p>[Next paragraph goes here]</p>
<p><b>Figure x.牋 Figure example (Note: figure # separated from caption by two hard-coded blanks)</b><br /><img alt="" height="200" src="xxx.jpg" width="550" /></p>
<p>[Next paragraph goes here]</p>
--><!-- RESOURCES-->
<P><A name=resources><SPAN class=atitle2>参考文献：</SPAN></A> 
<UL><!-- Comment out list item below if there is no forum for this article-->
<LI>linux内核源代码情景分析（上），毛德操、胡希明著，浙江大学出版社，当要验证某个结论、想法时，最好的参考资料； 
<LI>UNIX环境高级编程，作者：W.Richard Stevens，译者：尤晋元等，机械工业出版社。对信号机制的发展过程阐述的比较详细。 
<LI>signal、sigaction、kill等手册，最直接而可靠的参考资料。 
<LI><A href="http://www.linuxjournal.com/modules.php?op=modload&amp;name=NS-help&amp;file=man"><FONT color=#002c99>http://www.linuxjournal.com/modules.php?op=modload&amp;name=NS-help&amp;file=man</FONT></A>提供了许多系统调用、库函数等的在线指南。 
<LI><A href="http://www.opengroup.org/onlinepubs/007904975/"><FONT color=#002c99>http://www.opengroup.org/onlinepubs/007904975/</FONT></A>可以在这里对许多关键函数（包括系统调用）进行查询，非常好的一个网址。 
<LI><A href="http://unix.org/whitepapers/reentrant.html"><FONT color=#002c99>http://unix.org/whitepapers/reentrant.html</FONT></A>对函数可重入进行了阐述。 
<LI><A href="http://www.uccs.edu/~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM"><FONT color=#002c99>http://www.uccs.edu/~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM</FONT></A>对实时信号给出了相当好的描述。 </LI></UL>
<P></P><!-- AUTHOR BIOS--><!-- Make author heading singular or plural as needed-->
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1><SPAN class=atitle2>关于作者</SPAN></A><BR>郑彦兴，国防科大攻读博士学位。联系方式： <A href="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part2/mlinux@163.com"><FONT color=#002c99>mlinux@163.com</FONT></A>. </TD></TR></TBODY></TABLE>from:http://www.ddvip.net/program/vc/index6/59.htm<img src ="http://www.blogjava.net/weidagang2046/aggbug/9191.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-08-04 13:03 <a href="http://www.blogjava.net/weidagang2046/articles/9191.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>