﻿<?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>C++博客-yeqing-文章分类-linux</title><link>http://www.cppblog.com/yeqing/category/2366.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 12:55:01 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 12:55:01 GMT</pubDate><ttl>60</ttl><item><title>Shell脚本调试技术</title><link>http://www.cppblog.com/yeqing/articles/30919.html</link><dc:creator>夜沁</dc:creator><author>夜沁</author><pubDate>Mon, 27 Aug 2007 03:40:00 GMT</pubDate><guid>http://www.cppblog.com/yeqing/articles/30919.html</guid><wfw:comment>http://www.cppblog.com/yeqing/comments/30919.html</wfw:comment><comments>http://www.cppblog.com/yeqing/articles/30919.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yeqing/comments/commentRss/30919.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yeqing/services/trackbacks/30919.html</trackback:ping><description><![CDATA[ 本文全面系统地介绍了shell脚本调试技术，包括使用echo, tee, trap等命令输出关键信息，跟踪变量的值，在脚本中植入调试钩子，使用&#8220;-n&#8221;选项进行shell脚本的语法检查， 使用&#8220;-x&#8221;选项实现shell脚本逐条语句的跟踪，巧妙地利用shell的内置变量增强&#8220;-x&#8221;选项的输出信息等。 

一. 前言

shell编程在unix/linux世界中使用得非常广泛，熟练掌握shell编程也是成为一名优秀的unix/linux开发者和系统管理员的必经之路。脚本调试的主要工作就是发现引发脚本错误的原因以及在脚本源代码中定位发生错误的行，常用的手段包括分析输出的错误信息，通过在脚本中加入调试语句，输出调试信息来辅助诊断错误，利用调试工具等。但与其它高级语言相比，shell解释器缺乏相应的调试机制和调试工具的支持，其输出的错误信息又往往很不明确，初学者在调试脚本时，除了知道用echo语句输出一些信息外，别无它法，而仅仅依赖于大量的加入echo语句来诊断错误，确实令人不胜其繁，故常见初学者抱怨shell脚本太难调试了。本文将系统地介绍一些重要的shell脚本调试技术，希望能对shell的初学者有所裨益。

本文的目标读者是unix/linux环境下的开发人员，测试人员和系统管理员，要求读者具有基本的shell编程知识。本文所使用范例在Bash3.1+Redhat Enterprise Server 4.0下测试通过，但所述调试技巧应也同样适用于其它shell。 

二. 在shell脚本中输出调试信息 

通过在程序中加入调试语句把一些关键地方或出错的地方的相关信息显示出来是最常见的调试手段。Shell程序员通常使用echo(ksh程序员常使用print)语句输出信息，但仅仅依赖echo语句的输出跟踪信息很麻烦，调试阶段在脚本中加入的大量的echo语句在产品交付时还得再费力一一删除。针对这个问题，本节主要介绍一些如何方便有效的输出调试信息的方法。

1. 使用trap命令

trap命令用于捕获指定的信号并执行预定义的命令。
其基本的语法是:
trap 'command' signal
其中signal是要捕获的信号，command是捕获到指定的信号之后，所要执行的命令。可以用kill &#8211;l命令看到系统中全部可用的信号名，捕获信号后所执行的命令可以是任何一条或多条合法的shell语句，也可以是一个函数名。
shell脚本在执行时，会产生三个所谓的&#8220;伪信号&#8221;，(之所以称之为&#8220;伪信号&#8221;是因为这三个信号是由shell产生的，而其它的信号是由操作系统产生的)，通过使用trap命令捕获这三个&#8220;伪信号&#8221;并输出相关信息对调试非常有帮助。

表 1. shell伪信号信号名	何时产生
EXIT	从一个函数中退出或整个脚本执行完毕
ERR	当一条命令返回非零状态时(代表命令执行不成功)
DEBUG	脚本中每一条命令执行之前


通过捕获EXIT信号,我们可以在shell脚本中止执行或从函数中退出时，输出某些想要跟踪的变量的值，并由此来判断脚本的执行状态以及出错原因,其使用方法是：
trap 'command' EXIT　或　trap 'command' 0

通过捕获ERR信号,我们可以方便的追踪执行不成功的命令或函数，并输出相关的调试信息，以下是一个捕获ERR信号的示例程序，其中的$LINENO是一个shell的内置变量，代表shell脚本的当前行号。 $ cat -n exp1.sh
     1  ERRTRAP()
     2  {
     3    echo "[LINE:$1] Error: Command or function exited with status $?"
     4  }
     5  foo()
     6  {
     7    return 1;
     8  }
     9  trap 'ERRTRAP $LINENO' ERR
    10  abc
    11  foo
      



其输出结果如下：$ sh exp1.sh
exp1.sh: line 10: abc: command not found
[LINE:10] Error: Command or function exited with status 127
[LINE:11] Error: Command or function exited with status 1
      



在调试过程中，为了跟踪某些变量的值，我们常常需要在shell脚本的许多地方插入相同的echo语句来打印相关变量的值，这种做法显得烦琐而笨拙。而通过捕获DEBUG信号，我们只需要一条trap语句就可以完成对相关变量的全程跟踪。 

以下是一个通过捕获DEBUG信号来跟踪变量的示例程序:$ cat &#8211;n exp2.sh
     1  #!/bin/bash
     2  trap 'echo &#8220;before execute line:$LINENO, a=$a,b=$b,c=$c&#8221;' DEBUG
     3  a=1
     4  if [ "$a" -eq 1 ]
     5  then
     6     b=2
     7  else
     8     b=1
     9  fi
    10  c=3
    11  echo "end"



其输出结果如下：$ sh exp2.sh
before execute line:3, a=,b=,c=
before execute line:4, a=1,b=,c=
before execute line:6, a=1,b=,c=
before execute line:10, a=1,b=2,c=
before execute line:11, a=1,b=2,c=3
end



从运行结果中可以清晰的看到每执行一条命令之后，相关变量的值的变化。同时，从运行结果中打印出来的行号来分析，可以看到整个脚本的执行轨迹，能够判断出哪些条件分支执行了，哪些条件分支没有执行。 

2. 使用tee命令 

在shell脚本中管道以及输入输出重定向使用得非常多，在管道的作用下，一些命令的执行结果直接成为了下一条命令的输入。如果我们发现由管道连接起来的一批命令的执行结果并非如预期的那样，就需要逐步检查各条命令的执行结果来判断问题出在哪儿，但因为使用了管道，这些中间结果并不会显示在屏幕上，给调试带来了困难，此时我们就可以借助于tee命令了。 

tee命令会从标准输入读取数据，将其内容输出到标准输出设备,同时又可将内容保存成文件。例如有如下的脚本片段，其作用是获取本机的ip地址： ipaddr=`/sbin/ifconfig | grep 'inet addr:' | grep -v '127.0.0.1'
| cut -d : -f3 | awk '{print $1}'` 
#注意=号后面的整句是用反引号(数字1键的左边那个键)括起来的。
echo $ipaddr



运行这个脚本，实际输出的却不是本机的ip地址，而是广播地址,这时我们可以借助tee命令，输出某些中间结果，将上述脚本片段修改为： ipaddr=`/sbin/ifconfig | grep 'inet addr:' | grep -v '127.0.0.1'
| tee temp.txt | cut -d : -f3 | awk '{print $1}'`
echo $ipaddr



之后，将这段脚本再执行一遍，然后查看temp.txt文件的内容： $ cat temp.txt
inet addr:192.168.0.1  Bcast:192.168.0.255  Mask:255.255.255.0



我们可以发现中间结果的第二列(列之间以:号分隔)才包含了IP地址，而在上面的脚本中使用cut命令截取了第三列，故我们只需将脚本中的cut -d : -f3改为cut -d : -f2即可得到正确的结果。 

具体到上述的script例子，我们也许并不需要tee命令的帮助，比如我们可以分段执行由管道连接起来的各条命令并查看各命令的输出结果来诊断错误，但在一些复杂的shell脚本中，这些由管道连接起来的命令可能又依赖于脚本中定义的一些其它变量，这时我们想要在提示符下来分段运行各条命令就会非常麻烦了，简单地在管道之间插入一条tee命令来查看中间结果会更方便一些。 

3. 使用"调试钩子" 

在C语言程序中，我们经常使用DEBUG宏来控制是否要输出调试信息，在shell脚本中我们同样可以使用这样的机制，如下列代码所示： if [ &#8220;$DEBUG&#8221; = &#8220;true&#8221; ]; then
echo &#8220;debugging&#8221;  #此处可以输出调试信息
fi



这样的代码块通常称之为&#8220;调试钩子&#8221;或&#8220;调试块&#8221;。在调试钩子内部可以输出任何您想输出的调试信息，使用调试钩子的好处是它是可以通过DEBUG变量来控制的，在脚本的开发调试阶段，可以先执行export DEBUG=true命令打开调试钩子，使其输出调试信息，而在把脚本交付使用时，也无需再费事把脚本中的调试语句一一删除。 

如果在每一处需要输出调试信息的地方均使用if语句来判断DEBUG变量的值，还是显得比较繁琐，通过定义一个DEBUG函数可以使植入调试钩子的过程更简洁方便，如下面代码所示: $ cat &#8211;n exp3.sh
     1  DEBUG()
     2  {
     3  if [ "$DEBUG" = "true" ]; then
     4      $@　　
     5  fi
     6  }
     7  a=1
     8  DEBUG echo "a=$a"
     9  if [ "$a" -eq 1 ]
    10  then
    11       b=2
    12  else
    13       b=1
    14  fi
    15  DEBUG echo "b=$b"
    16  c=3
    17  DEBUG echo "c=$c"



在上面所示的DEBUG函数中，会执行任何传给它的命令，并且这个执行过程是可以通过DEBUG变量的值来控制的，我们可以把所有跟调试有关的命令都作为DEBUG函数的参数来调用，非常的方便。 


	回页首





三. 使用shell的执行选项 

上一节所述的调试手段是通过修改shell脚本的源代码，令其输出相关的调试信息来定位错误的，那有没有不修改源代码来调试shell脚本的方法呢？答案就是使用shell的执行选项，本节将介绍一些常用选项的用法： 

-n 只读取shell脚本，但不实际执行
-x 进入跟踪方式，显示所执行的每一条命令
-c "string" 从strings中读取命令

&#8220;-n&#8221;可用于测试shell脚本是否存在语法错误，但不会实际执行命令。在shell脚本编写完成之后，实际执行之前，首先使用&#8220;-n&#8221;选项来测试脚本是否存在语法错误是一个很好的习惯。因为某些shell脚本在执行时会对系统环境产生影响，比如生成或移动文件等，如果在实际执行才发现语法错误，您不得不手工做一些系统环境的恢复工作才能继续测试这个脚本。 

&#8220;-c&#8221;选项使shell解释器从一个字符串中而不是从一个文件中读取并执行shell命令。当需要临时测试一小段脚本的执行结果时，可以使用这个选项，如下所示：
sh -c 'a=1;b=2;let c=$a+$b;echo "c=$c"' 

"-x"选项可用来跟踪脚本的执行，是调试shell脚本的强有力工具。&#8220;-x&#8221;选项使shell在执行脚本的过程中把它实际执行的每一个命令行显示出来，并且在行首显示一个"+"号。 "+"号后面显示的是经过了变量替换之后的命令行的内容，有助于分析实际执行的是什么命令。 &#8220;-x&#8221;选项使用起来简单方便，可以轻松对付大多数的shell调试任务,应把其当作首选的调试手段。 

如果把本文前面所述的trap &#8216;command&#8217; DEBUG机制与&#8220;-x&#8221;选项结合起来，我们 就可以既输出实际执行的每一条命令，又逐行跟踪相关变量的值，对调试相当有帮助。 

仍以前面所述的exp2.sh为例，现在加上&#8220;-x&#8221;选项来执行它： $ sh &#8211;x exp2.sh
+ trap 'echo "before execute line:$LINENO, a=$a,b=$b,c=$c"' DEBUG
++ echo 'before execute line:3, a=,b=,c='
before execute line:3, a=,b=,c=
+ a=1
++ echo 'before execute line:4, a=1,b=,c='
before execute line:4, a=1,b=,c=
+ '[' 1 -eq 1 ']'
++ echo 'before execute line:6, a=1,b=,c='
before execute line:6, a=1,b=,c=
+ b=2
++ echo 'before execute line:10, a=1,b=2,c='
before execute line:10, a=1,b=2,c=
+ c=3
++ echo 'before execute line:11, a=1,b=2,c=3'
before execute line:11, a=1,b=2,c=3
+ echo end
end



在上面的结果中，前面有&#8220;+&#8221;号的行是shell脚本实际执行的命令，前面有&#8220;++&#8221;号的行是执行trap机制中指定的命令，其它的行则是输出信息。 

shell的执行选项除了可以在启动shell时指定外，亦可在脚本中用set命令来指定。 "set -参数"表示启用某选项，"set +参数"表示关闭某选项。有时候我们并不需要在启动时用"-x"选项来跟踪所有的命令行，这时我们可以在脚本中使用set命令，如以下脚本片段所示： set -x　　　 #启动"-x"选项 
要跟踪的程序段 
set +x　　　　 #关闭"-x"选项



set命令同样可以使用上一节中介绍的调试钩子—DEBUG函数来调用，这样可以避免脚本交付使用时删除这些调试语句的麻烦，如以下脚本片段所示： DEBUG set -x　　　 #启动"-x"选项 
要跟踪的程序段 
DEBUG set +x　　　 #关闭"-x"选项




	回页首





四. 对"-x"选项的增强 

"-x"执行选项是目前最常用的跟踪和调试shell脚本的手段，但其输出的调试信息仅限于进行变量替换之后的每一条实际执行的命令以及行首的一个"+"号提示符，居然连行号这样的重要信息都没有，对于复杂的shell脚本的调试来说，还是非常的不方便。幸运的是，我们可以巧妙地利用shell内置的一些环境变量来增强"-x"选项的输出信息，下面先介绍几个shell内置的环境变量： 

$LINENO
代表shell脚本的当前行号，类似于C语言中的内置宏__LINE__ 

$FUNCNAME
函数的名字，类似于C语言中的内置宏__func__,但宏__func__只能代表当前所在的函数名，而$FUNCNAME的功能更强大，它是一个数组变量，其中包含了整个调用链上所有的函数的名字，故变量${FUNCNAME[0]}代表shell脚本当前正在执行的函数的名字，而变量${FUNCNAME[1]}则代表调用函数${FUNCNAME[0]}的函数的名字，余者可以依此类推。 

$PS4
主提示符变量$PS1和第二级提示符变量$PS2比较常见，但很少有人注意到第四级提示符变量$PS4的作用。我们知道使用&#8220;-x&#8221;执行选项将会显示shell脚本中每一条实际执行过的命令，而$PS4的值将被显示在&#8220;-x&#8221;选项输出的每一条命令的前面。在Bash Shell中，缺省的$PS4的值是"+"号。(现在知道为什么使用"-x"选项时，输出的命令前面有一个"+"号了吧？)。 

利用$PS4这一特性，通过使用一些内置变量来重定义$PS4的值，我们就可以增强"-x"选项的输出信息。例如先执行export PS4='+{$LINENO:${FUNCNAME[0]}} ', 然后再使用&#8220;-x&#8221;选项来执行脚本，就能在每一条实际执行的命令前面显示其行号以及所属的函数名。 

以下是一个存在bug的shell脚本的示例，本文将用此脚本来示范如何用&#8220;-n&#8221;以及增强的&#8220;-x&#8221;执行选项来调试shell脚本。这个脚本中定义了一个函数isRoot(),用于判断当前用户是不是root用户，如果不是，则中止脚本的执行 $ cat &#8211;n exp4.sh
     1  #!/bin/bash
     2  isRoot()
     3  {
     4          if [ "$UID" -ne 0 ]
     5                  return 1
     6          else
     7                  return 0
     8          fi
     9  }
    10  isRoot
    11  if ["$?" -ne 0 ]
    12  then
    13          echo "Must be root to run this script"
    14          exit 1
    15  else
    16          echo "welcome root user"
    17          #do something
    18  fi



首先执行sh &#8211;n exp4.sh来进行语法检查，输出如下： $ sh &#8211;n exp4.sh
exp4.sh: line 6: syntax error near unexpected token `else'
exp4.sh: line 6: `      else'



发现了一个语法错误，通过仔细检查第6行前后的命令，我们发现是第4行的if语句缺少then关键字引起的(写惯了C程序的人很容易犯这个错误)。我们可以把第4行修改为if [ "$UID" -ne 0 ]; then来修正这个错误。再次运行sh &#8211;n exp4.sh来进行语法检查，没有再报告错误。接下来就可以实际执行这个脚本了，执行结果如下： $ sh exp4.sh
exp2.sh: line 11: [1: command not found
welcome root user



尽管脚本没有语法错误了，在执行时却又报告了错误。错误信息还非常奇怪&#8220;[1: command not found&#8221;。现在我们可以试试定制$PS4的值，并使用&#8220;-x&#8221;选项来跟踪： $ export PS4='+{$LINENO:${FUNCNAME[0]}} '
$ sh &#8211;x exp4.sh
+{10:} isRoot
+{4:isRoot} '[' 503 -ne 0 ']'
+{5:isRoot} return 1
+{11:} '[1' -ne 0 ']'
exp4.sh: line 11: [1: command not found
+{16:} echo 'welcome root user'
welcome root user



从输出结果中，我们可以看到脚本实际被执行的语句，该语句的行号以及所属的函数名也被打印出来，从中可以清楚的分析出脚本的执行轨迹以及所调用的函数的内部执行情况。由于执行时是第11行报错，这是一个if语句，我们对比分析一下同为if语句的第4行的跟踪结果： +{4:isRoot} '[' 503 -ne 0 ']'
+{11:} '[1' -ne 0 ']'



可知由于第11行的[号后面缺少了一个空格，导致[号与紧挨它的变量$?的值1被shell解释器看作了一个整体，并试着把这个整体视为一个命令来执行，故有&#8220;[1: command not found&#8221;这样的错误提示。只需在[号后面插入一个空格就一切正常了。 

shell中还有其它一些对调试有帮助的内置变量，比如在Bash Shell中还有BASH_SOURCE, BASH_SUBSHELL等一批对调试有帮助的内置变量，您可以通过man sh或man bash来查看，然后根据您的调试目的,使用这些内置变量来定制$PS4，从而达到增强&#8220;-x&#8221;选项的输出信息的目的。 

五. 总结 

现在让我们来总结一下调试shell脚本的过程：
首先使用&#8220;-n&#8221;选项检查语法错误，然后使用&#8220;-x&#8221;选项跟踪脚本的执行，使用&#8220;-x&#8221;选项之前，别忘了先定制PS4变量的值来增强&#8220;-x&#8221;选项的输出信息，至少应该令其输出行号信息(先执行export PS4='+[$LINENO]'，更一劳永逸的办法是将这条语句加到您用户主目录的.bash_profile文件中去)，这将使你的调试之旅更轻松。也可以利用trap,调试钩子等手段输出关键调试信息，快速缩小排查错误的范围，并在脚本中使用&#8220;set -x&#8221;及&#8220;set +x&#8221;对某些代码块进行重点跟踪。这样多种手段齐下，相信您已经可以比较轻松地抓出您的shell脚本中的臭虫了。如果您的脚本足够复杂，还需要更强的调试能力，可以使用shell调试器bashdb，这是一个类似于GDB的调试工具，可以完成对shell脚本的断点设置，单步执行，变量观察等许多功能，使用bashdb对阅读和理解复杂的shell脚本也会大有裨益。关于bashdb的安装和使用，不属于本文范围，您可参阅http://bashdb.sourceforge.net/上的文档并下载试用。 



参考资料 
请访问： GNU 的 bash 主页 

请下载和试用 Shell调试器bashdb<img src ="http://www.cppblog.com/yeqing/aggbug/30919.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yeqing/" target="_blank">夜沁</a> 2007-08-27 11:40 <a href="http://www.cppblog.com/yeqing/articles/30919.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux命令行程序设计</title><link>http://www.cppblog.com/yeqing/articles/15768.html</link><dc:creator>夜沁</dc:creator><author>夜沁</author><pubDate>Wed, 29 Nov 2006 01:29:00 GMT</pubDate><guid>http://www.cppblog.com/yeqing/articles/15768.html</guid><wfw:comment>http://www.cppblog.com/yeqing/comments/15768.html</wfw:comment><comments>http://www.cppblog.com/yeqing/articles/15768.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yeqing/comments/commentRss/15768.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yeqing/services/trackbacks/15768.html</trackback:ping><description><![CDATA[
		<p>
				<font color="#808080" size="2">Linux下很多程序甚至那些具有图形用户界面（graphical user interface，GUI）的程序，都能接受和处理命令行选项。对于某些程序，这是与其他程序或用户进行交互的主要手段。具有可靠的复杂命令行参数处理机制，会使得您的应用程序更好、更有用。不过很多开发人员都将其宝贵的时间花在了编写自己的命令行解析器，却不使用 getopt()，而后者是一个专门设计来减轻命令行处理负担的库函数。</font>
		</p>
		<p>
				<font color="#808080" size="2">
				</font> </p>
		<p>
				<font color="#808080" size="2">1、命令行参数<br />命令行程序设计的首要任务是解析命令行参数，GUI派的程序员很少关心这个。这里，我们对参数（argument）采用了一种比较通俗的定义：命令行上除命令名之外的字符串。参数由多项构成，项与项之间用空白符彼此隔开。<br />参数进一步分为选项和操作数。选项用于修改程序的默认行为或为程序提供信息，比较老的约定是以短划线开头。选项后可以跟随一些参数，称为选项参数。剩下的就是操作数了。<br />2、POSIX约定<br />POSIX表示可移植操作系统接口：Portable Operating System Interface，电气和电子工程师协会（Institute of Electrical and Electronics Engineers，IEEE）最初开发 POSIX 标准，是为了提高 UNIX 环境下应用程序的可移植性。然而，POSIX 并不局限于 UNIX。许多其它的操作系统，例如 DEC OpenVMS 和 Microsoft Windows NT，都支持 POSIX 标准。<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">下面是POSIX标准中关于程序名、参数的约定：</font>
		</p>
		<p>
				<font color="#808080" size="2">程序名不宜少于2个字符且不多于9个字符； <br />程序名应只包含小写字母和阿拉伯数字； <br />选项名应该是单字符活单数字，且以短横‘-‘为前綴； <br />多个不需要选项参数的选项，可以合并。（譬如：foo -a -b -c ----&gt;foo -abc） <br />选项与其参数之间用空白符隔开； <br />选项参数不可选。 <br />若选项参数有多值，要将其并未一个字串传进来。譬如：myprog -u "arnold,joe,jane"。这种情况下，需要自己解决这些参数的分离问题。 <br />选项应该在操作数出现之前出现。 <br />特殊参数‘--'指明所有参数都结束了，其后任何参数都认为是操作数。 <br />选项如何排列没有什么关系，但对互相排斥的选项，如果一个选项的操作结果覆盖其他选项的操作结果时，最后一个选项起作用；如果选项重复，则顺序处理。 <br />允许操作数的顺序影响程序行为，但需要作文档说明。 <br />读写指定文件的程序应该将单个参数'-'作为有意义的标准输入或输出来对待。<br />当然许多标准从恒许未遵守以上约定，主要是历史兼容问题，因为标准出现之前，就已经存在N多程序了。</font>
		</p>
		<p>
				<font color="#808080" size="2">3、GNU长选项<br />GNU鼓励程序员使用--help、--verbose等形式的长选项。这些选项不仅不与POSIX约定冲突，而且容易记忆，另外也提供了在所有GNU工具之间保持一致性的机会。GNU长选项有自己的约定：</font>
		</p>
		<p>
				<font color="#808080" size="2">对于已经遵循POSIX约定的GNU程序，每个短选项都有一个对应的长选项。 <br />额外针对GNU的长选项不需要对应的短选项，仅仅推荐要有。 <br />长选项可以缩写成保持惟一性的最短的字串。 <br />选项参数与长选项之间或通过空白字符活通过一个'='来分隔。 <br />选项参数是可选的（只对短选项有效）。 <br />长选项允许以一个短横线为前缀。</font>
		</p>
		<p>
				<font color="#808080" size="2">4、基本的命令行处理技术<br />C程序通过argc和argv参数访问它的命令行参数。argc是整型数，表示参数的个数（包括命令名）。main()函数的定义方式有两种，区别仅在于argv如何定义：</font>
		</p>
		<p>
				<font color="#808080" size="2">int main(int argc, char *argv[])<br />{<br />   ……<br />} int main(int argc, char **argv)<br />{<br />   ……<br />}  </font>
		</p>
		<p>
				<br />
				<font color="#808080" size="2">当 C 运行时库的程序启动代码调用您的 main() 时，已经对命令行进行了处理。argc 参数包含参数的计数值，而 argv 包含指向这些参数的指针数组。argv[0]是程序名。</font>
		</p>
		<p>
				<font color="#808080" size="2">一个很简单的命令行处理技术的例子是echo程序，它可以将参数输出到标准设备上，用空格符隔开，最后换行。若命令行第一个参数为-n，那么就不会换行。</font>
		</p>
		<p>
				<font color="#808080" size="2">清单1：</font>
		</p>
		<p>
				<font color="#808080" size="2">#include &lt;stdio.h&gt;</font>
		</p>
		<p>
				<font color="#808080" size="2">int main(int argc, char **argv)<br />{<br />    int i, nflg;</font>
		</p>
		<p>
				<font color="#808080" size="2">    nflg = 0;<br />    if(argc &gt; 1 &amp;&amp; argv[1][0] == '-' &amp;&amp; argv[1][1] == 'n'){<br />        nflg++;<br />        argc--;<br />        argv++;<br />    }<br />    for(i=1; i&lt;argc; i++){<br />        fputs(argv[ i ], stdout);<br />        if(i &lt; argc-1)<br />            putchar(' ');<br />    }<br />    if(nflg == 0)<br />        putchar('\n');</font>
		</p>
		<p>
				<font color="#808080" size="2">    return 0;<br />}<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">上面代码中，加亮区域仔细研究一下，会发现很有趣。</font>
		</p>
		<p>
				<font color="#808080" size="2">echo程序中，对于命令行参数的解析是手动实现的。很久以前，Unix支持小组为了简化对于命令行参数的解析，开发了getopt()函数，同时提供了几个外部变量，使得编写遵守POSIX的代码变得更加容易了。</font>
		</p>
		<p>
				<font color="#808080" size="2">5、命令行参数解析函数 —— getopt()<br />getopt()函数声明如下：</font>
		</p>
		<p>
				<font color="#808080" size="2">#include &lt;unistd.h&gt;</font>
		</p>
		<p>
				<font color="#808080" size="2">int getopt(int argc, char * const argv[], const char *optstring);</font>
		</p>
		<p>
				<font color="#808080" size="2">extern char *optarg;<br />extern int optind, opterr, optopt;<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">该函数的argc和argv参数通常直接从main()的参数直接传递而来。optstring是选项字母组成的字串。如果该字串里的任一字符后面有冒号，那么这个选项就要求有选项参数。</font>
		</p>
		<p>
				<font color="#808080" size="2">当给定getopt()命令参数的数量 (argc)、指向这些参数的数组 (argv) 和选项字串 (optstring) 后，getopt() 将返回第一个选项，并设置一些全局变量。使用相同的参数再次调用该函数时，它将返回下一个选项，并设置相应的全局变量。如果不再有可识别的选项，将返回 -1，此任务就完成了。</font>
		</p>
		<p>
				<font color="#808080" size="2">getopt() 所设置的全局变量包括：</font>
		</p>
		<p>
				<font color="#808080" size="2">char *optarg——当前选项参数字串（如果有）。 <br />int optind——argv的当前索引值。当getopt()在while循环中使用时，循环结束后，剩下的字串视为操作数，在argv[optind]至argv[argc-1]中可以找到。 <br />int opterr——这个变量非零时，getopt()函数为“无效选项”和“缺少参数选项，并输出其错误信息。</font>
		</p>
		<p>
				<font color="#808080" size="2">int optopt——当发现无效选项字符之时，getopt()函数或返回'?'字符，或返回':'字符，并且optopt包含了所发现的无效选项字符。<br />下面就用getopt()来写个小程序，体验一下命令行解析的快乐。</font>
		</p>
		<p>
				<font color="#808080" size="2">程序描述：</font>
		</p>
		<p>
				<font color="#808080" size="2">程序名：opt_parse_demo</font>
		</p>
		<p>
				<font color="#808080" size="2">选项：</font>
		</p>
		<p>
				<font color="#808080" size="2">-n —— 显示我的名字。 <br />-g —— 显示我女朋友的名字。 <br />-l —— 带参数的选项.<br />清单2：<br />#include &lt;stdio.h&gt;<br />#include &lt;unistd.h&gt;</font>
		</p>
		<p>
				<font color="#808080" size="2">int main (int argc, char **argv)<br />{<br />    int oc;                     /*选项字符 */<br />    char *b_opt_arg;            /*选项参数字串 */</font>
		</p>
		<p>
				<font color="#808080" size="2">    while((oc = getopt(argc, argv, "ngl:")) != -1)<br />    {<br />        switch(oc)<br />        {<br />            case 'n':<br />                printf("My name is Lyong.\n");<br />                break;<br />            case 'g':<br />                printf("Her name is Xxiong.\n");<br />                break;<br />            case 'l':<br />                b_opt_arg = optarg;<br />                printf("Our love is %s\n", optarg);<br />                break;<br />        }<br />    }<br />   return 0;<br />}<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">运行结果：</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./opt_parse_demo -n<br />My name is Lyong.<br />$ ./opt_parse_demo -g<br />Her name is Xxiong.<br />$ ./opt_parse_demo -l forever<br />Our love is forever<br />$ ./opt_parse_demo -ngl forever<br />My name is Lyong.<br />Her name is Xxiong.<br />Our love is forever<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">6、改变getopt()对错误命令行参数信息的输出行为<br />不正确的调用程序在所难免，这种错误要么是命令行选项无效，要么是缺少选项参数。正常情况下，getopt()会为这两种情况输出自己的出错信息，并且返回'?'。为了验证此事，可以修改一下上面的清单2中的代码。</font>
		</p>
		<p>
				<font color="#808080" size="2">清单3：</font>
		</p>
		<p>
				<font color="#808080" size="2">#include &lt;stdio.h&gt;<br />#include &lt;unistd.h&gt;</font>
		</p>
		<p>
				<font color="#808080" size="2">int main (int argc, char **argv)<br />{<br />    int oc;                     /*选项字符 */<br />    char *b_opt_arg;            /*选项参数字串 */</font>
		</p>
		<p>
				<font color="#808080" size="2">    while((oc = getopt(argc, argv, "ngl:")) != -1)<br />    {<br />        switch(oc)<br />        {<br />            case 'n':<br />                printf("My name is Lyong.\n");<br />                break;<br />             case 'g':<br />                printf("Her name is Xxiong.\n");<br />                break;<br />            case 'l':<br />                b_opt_arg = optarg;<br />                printf("Our love is %s\n", optarg);<br />                break;<br />            case '?':<br />                printf("arguments error!\n");<br />                break;<br />        }<br />    }<br />    return 0;<br />}<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">输入一个错误的命令行，结果如下：</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./opt_parse_demo -l<br />./opt_parse_demo: option requires an argument -- l<br />arguments error!<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">很多时候，我们不希望输出任何错误信息，或更希望输出自己定义的错误信息。可以采用以下两种方法来更改getopt()函数的出错信息输出行为：</font>
		</p>
		<p>
				<br />
				<font color="#808080" size="2">在调用getopt()之前，将opterr设置为0，这样就可以在getopt()函数发现错误的时候强制它不输出任何消息。 <br />如果optstring参数的第一个字符是冒号，那么getopt()函数就会保持沉默，并根据错误情况返回不同字符，如下： <br />“无效选项” —— getopt()返回'?'，并且optopt包含了无效选项字符（这是正常的行为）。 <br />“缺少选项参数” —— getopt()返回':'，如果optstring的第一个字符不是冒号，那么getopt()返回'?'，这会使得这种情况不能与无效选项的情况区分开。<br />多说无益，动手测试一下。</font>
		</p>
		<p>
				<font color="#808080" size="2">清单4：<br />#include &lt;stdio.h&gt;<br />#include &lt;unistd.h&gt;</font>
		</p>
		<p>
				<font color="#808080" size="2">int main (int argc, char **argv)<br />{<br />    int oc;                     /*选项字符 */<br />    char ec;                             /*无效的选项字符*/<br />    char *b_opt_arg;            /*选项参数字串 */</font>
		</p>
		<p>
				<font color="#808080" size="2">    while((oc = getopt(argc, argv, ":ngl:")) != -1)<br />    {<br />        switch(oc)<br />        {<br />            case 'n':<br />                printf("My name is Lyong.\n");<br />                break;<br />             case 'g':<br />                printf("Her name is Xxiong.\n");<br />                break;<br />            case 'l':<br />                b_opt_arg = optarg;<br />                printf("Our love is %s\n", optarg);<br />                break;<br />            case '?':<br />                ec = (char)optopt;<br />                printf("无效的选项字符 \' %c \'!\n", ec);<br />                break;<br />            case ':':<br />                printf("缺少选项参数！\n");<br />                break;<br />        }<br />    }<br />    return 0;<br />} </font>
		</p>
		<p>
				<font color="#808080" size="2">测试结果：</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./opt_parse_demo -a<br />无效的选项字符 ' a '!<br />$ ./opt_parse_demo -l<br />缺少选项参数！ </font>
		</p>
		<p>
				<font color="#808080" size="2">7、GNU提供的getopt()函数的特点<br />上面所设计的getopt()函数是UNIX支持小组提供的，其执行时一碰到不以'-'开始的命令行参数就停止寻找选项。而GNU提供的getopt()函数与之不同，它会扫描整个命令行来寻找选项。当调用GNU getopt()函数并处理命令行参数的时候，它重新排列argv中的元素，这样当重排结束时，所有选项都被移动到前面并且那些继续检查argv[optind]至argv[argc-1]中剩余参数的代码仍正常工作，但在任何情况下，碰到特殊参数'--'就结束对选项的扫描。</font>
		</p>
		<p>
				<font color="#808080" size="2">可以输入一个乱序的命令行，查看opt_parse_demo的输出：</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./opt_parse_demo -l forever a b c d -g -n<br />Our love is forever<br />Her name is Xxiong.<br />My name is Lyong.<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">GNU getopt()第二个特点是可以在optstring中使用特殊的首字符改变getopt()的默认行为：</font>
		</p>
		<p>
				<font color="#808080" size="2">optstring[0] = '+'，这样就与UNIX支持小组提供的getopt()很相近了。 <br />optstring[0] = '-'，会在optarg中得到命令行中的每个参数。 <br />以上两种情况下，':'可以作为第二个字符使用。<br />GNU getopt()第三个特点是optstring中的选项字符后面接两个冒号，就允许该选项有可选的选项参数。在选项参数不存在的情况下，GNU getopt()返回选项字符并将optarg设置为NULL。</font>
		</p>
		<p>
				<font color="#808080" size="2">8、GNU长选项命令行解析<br />20 世纪 90 年代，UNIX 应用程序开始支持长选项，即一对短横线、一个描述性选项名称，还可以包含一个使用等号连接到选项的参数。</font>
		</p>
		<p>
				<font color="#808080" size="2">GNU提供了getopt-long()和getopt-long-only()函数支持长选项的命令行解析，其中，后者的长选项字串是以一个短横线开始的，而非一对短横线。</font>
		</p>
		<p>
				<br />
				<font color="#808080" size="2">getopt_long() 是同时支持长选项和短选项的 getopt() 版本。下面是它们的声明：</font>
		</p>
		<p>
				<font color="#808080" size="2">#include &lt;getopt.h&gt;</font>
		</p>
		<p>
				<font color="#808080" size="2">int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);</font>
		</p>
		<p>
				<font color="#808080" size="2">int getopt_long_only(int argc, char * const argv[],const char *optstring,const struct option *longopts, int *longindex);</font>
		</p>
		<p>
				<font color="#808080" size="2"> </font>
		</p>
		<p>
				<font color="#808080" size="2">getopt_long()的前三个参数与上面的getopt()相同，第4个参数是指向option结构的数组，option结构被称为“长选项表”。longindex参数如果没有设置为NULL，那么它就指向一个变量，这个变量会被赋值为寻找到的长选项在longopts中的索引值，这可以用于错误诊断。</font>
		</p>
		<p>
				<font color="#808080" size="2">option结构在getopt.h中的声明如下：</font>
		</p>
		<p>
				<font color="#808080" size="2">struct option{<br />    const char *name;<br />    int has_arg;<br />    int *flag;<br />    int val;<br />};<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">对结构中的各元素解释如下：</font>
		</p>
		<p>
				<font color="#808080" size="2">const char *name<br />这是选项名，前面没有短横线。譬如"help"、"verbose"之类。</font>
		</p>
		<p>
				<font color="#808080" size="2">int has_arg<br />描述了选项是否有选项参数。如果有，是哪种类型的参数，此时，它的值一定是下表中的一个。</font>
		</p>
		<p>
				<font color="#808080" size="2">符号常量  数值  含义  <br />no_argument  0 选项没有参数  <br />required_argument 1 选项需要参数 <br /> <br />optional_argument 2 选项参数可选  </font>
		</p>
		<p>
				<font color="#808080" size="2">int *flag<br />如果这个指针为NULL，那么getopt_long()返回该结构val字段中的数值。如果该指针不为NULL，getopt_long()会使得它所指向的变量中填入val字段中的数值，并且getopt_long()返回0。如果flag不是NULL，但未发现长选项，那么它所指向的变量的数值不变。</font>
		</p>
		<p>
				<font color="#808080" size="2">int val<br />这个值是发现了长选项时的返回值，或者flag不是NULL时载入*flag中的值。典型情况下，若flag不是NULL，那么val是个真／假值，譬如1或0；另一方面，如果flag是NULL，那么val通常是字符常量，若长选项与短选项一致，那么该字符常量应该与optstring中出现的这个选项的参数相同。</font>
		</p>
		<p>
				<font color="#808080" size="2">每个长选项在长选项表中都有一个单独条目，该条目里需要填入正确的数值。数组中最后的元素的值应该全是0。数组不需要排序，getopt_long()会进行线性搜索。但是，根据长名字来排序会使程序员读起来更容易。</font>
		</p>
		<p>
				<font color="#808080" size="2">以上所说的flag和val的用法看上去有点混乱，但它们很有实用价值，因此有必要搞透彻了。</font>
		</p>
		<p>
				<font color="#808080" size="2">大部分时候，程序员会根据getopt_long()发现的选项，在选项处理过程中要设置一些标记变量，譬如在使用getopt()时，经常做出如下的程序格式：</font>
		</p>
		<p>
				<font color="#808080" size="2">int do_name, do_gf_name, do_love; /*标记变量*/<br />char *b_opt_arg;</font>
		</p>
		<p>
				<font color="#808080" size="2">while((c = getopt(argc, argv, ":ngl:")) != -1)<br />{<br />    switch (c){<br />    case 'n':<br />        do_name = 1;<br />    case 'g':<br />        do_gf_name = 1;<br />        break;<br />        break;<br />    case 'l':<br />        b_opt_arg = optarg;<br />    ……<br />    }<br />}<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">当flag不为NULL时，getopt_long*()会为你设置标记变量。也就是说上面的代码中，关于选项'n'、'l'的处理，只是设置一些标记，如果flag不为NULL,时，getopt_long()可以自动为各选项所对应的标记变量设置标记，这样就能够将上面的switch语句中的两种种情况减少到了一种。下面给出一个长选项表以及相应处理代码的例子。</font>
		</p>
		<p>
				<font color="#808080" size="2">清单5：</font>
		</p>
		<p>
				<font color="#808080" size="2">#include &lt;stdio.h&gt;<br />#include &lt;getopt.h&gt;</font>
		</p>
		<p>
				<font color="#808080" size="2">int do_name, do_gf_name;<br />char *l_opt_arg;</font>
		</p>
		<p>
				<font color="#808080" size="2">struct option longopts[] = {<br />    { "name",        no_argument,            &amp;do_name,        1    },<br />    { "gf_name",    no_argument,            &amp;do_gf_name,    1    },<br />    { "love",        required_argument,    NULL,                'l'    },<br />    {     0,    0,    0,    0},<br />};</font>
		</p>
		<p>
				<font color="#808080" size="2">int main(int argc, char *argv[])<br />{<br />    int c;<br />    <br />    while((c = getopt_long(argc, argv, ":l:", longopts, NULL)) != -1){<br />        switch (c){<br />        case 'l':<br />            l_opt_arg = optarg;<br />            printf("Our love is %s!\n", l_opt_arg);<br />            break;<br />        case 0:<br />            printf("getopt_long()设置变量 : do_name = %d\n", do_name);<br />            printf("getopt_long()设置变量 : do_gf_name = %d\n", do_gf_name);<br />            break;<br />        }<br />    }<br />    return 0;<br />}<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">在进行测试之前，再来回顾一下有关option结构中的指针flag的说明吧。</font>
		</p>
		<p>
				<font color="#808080" size="2">如果这个指针为NULL，那么getopt_long()返回该结构val字段中的数值。如果该指针不为NULL，getopt_long()会使得它所指向的变量中填入val字段中的数值，并且getopt_long()返回0。如果flag不是NULL，但未发现长选项，那么它所指向的变量的数值不变。 </font>
		</p>
		<p>
				<font color="#808080" size="2">下面测试一下：</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./long_opt_demo --name<br />getopt_long()设置变量 : do_name = 1<br />getopt_long()设置变量 : do_gf_name = 0</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./long_opt_demo --gf_name<br />getopt_long()设置变量 : do_name = 0<br />getopt_long()设置变量 : do_gf_name = 1</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./long_opt_demo --love forever<br />Our love is forever!</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./long_opt_demo -l forever<br />Our love is forever!</font>
		</p>
		<p>
				<font color="#808080" size="2"> </font>
		</p>
		<p>
				<font color="#808080" size="2">测试过后，应该有所感触了。关于flag和val的讨论到此为止。下面总结一下get_long()的各种返回值的含义：</font>
		</p>
		<p>
				<font color="#808080" size="2">返回值    <br /> 含 义<br /> <br />0      <br /> getopt_long()设置一个标志，它的值与option结构中的val字段的值一样<br /> <br />1<br /> 每碰到一个命令行参数，optarg都会记录它<br /> <br />'?'<br /> 无效选项<br /> <br />':'<br /> 缺少选项参数<br /> <br />'x'<br /> 选项字符'x'<br /> <br />-1<br /> 选项解析结束<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">从实用的角度来说，我们更期望每个长选项都对应一个短选项，这种情况下，在option结构中，只要将flag设置为NULL，并将val设置为长选项所对应的短选项字符即可。譬如上面清单5中的程序，我们修改如下。</font>
		</p>
		<p>
				<font color="#808080" size="2">清单6：<br />#include &lt;stdio.h&gt;<br />#include &lt;getopt.h&gt;</font>
		</p>
		<p>
				<font color="#808080" size="2">int do_name, do_gf_name;<br />char *l_opt_arg;</font>
		</p>
		<p>
				<font color="#808080" size="2">struct option longopts[] = {<br />    { "name",        no_argument,            NULL,                'n'    },<br />    { "gf_name",    no_argument,            NULL,                'g'    },<br />    { "love",        required_argument,    NULL,                'l'    },<br />    {     0,    0,    0,    0},<br />};</font>
		</p>
		<p>
				<font color="#808080" size="2">int main(int argc, char *argv[])<br />{<br />    int c;<br />    <br />    while((c = getopt_long(argc, argv, ":l:", longopts, NULL)) != -1){<br />        switch (c){<br />        case 'n':<br />            printf("My name is LYR.\n");<br />            break;<br />        case 'g':<br />            printf("Her name is BX.\n");<br />            break;<br />        case 'l':<br />            l_opt_arg = optarg;<br />            printf("Our love is %s!\n", l_opt_arg);<br />            break;<br />        }<br />    }<br />    return 0;<br />} </font>
		</p>
		<p>
				<font color="#808080" size="2">测试结果如下：</font>
		</p>
		<p>
				<br />
				<font color="#808080" size="2">$ ./long_opt_demo --name --gf_name --love forever<br />My name is LYR.<br />Her name is BX.<br />Our love is forever!</font>
		</p>
		<p>
				<font color="#808080" size="2">$ ./long_opt_demo -ng -l forever<br />My name is LYR.<br />Her name is BX.<br />Our love is forever!<br /> </font>
		</p>
		<p>
				<font color="#808080" size="2">9、在LINUX之外的系统平台上使用GNU getopt()或getopt_long()<br />只要从GNU程序或GNU C Library(GLIBC)的CVS档案文件中copy源文件即可（</font>
				<a href="http://sourceware.org/glibc/">
						<font color="#808080" size="2">http://sourceware.org/glibc/</font>
				</a>
				<font color="#808080" size="2">）。所需源文件是getopt.h、getopt.c和getoptl.c，将这些文件包含在你的项目中。另外，你的项目中最好也将COPYING.LIB文件包含进去，因为GNU LGPL（GNU 程序库公共许可证）的内容全部包括在命名为COPYING.LIB 的文件中。没事时，可以阅读一下自由软件的许可协议，感受一下雷锋精神。</font>
		</p>
		<p>
				<font color="#808080" size="2">注意，在包含所需文件之后，在调用getopt_long()系列函数的源代码中，应该使用#include "getopt.h"，而不是#include &lt;getopt.h&gt;，前者会首先在当前目录中寻找getopt.h。</font>
		</p>
		<p>
				<font color="#808080" size="2">使用Windows平台的兄弟可以试用一下，很希望能在这里反馈一下你的使用情况，谢谢。</font>
		</p>
		<p>
				<br />
				<font color="#808080" size="2">10、结论<br />程序需要能够快速处理各个选项和参数，且要求不会浪费开发人员的太多时间。在这一点上，无论是GUI(图形用户交互）程序还是CUI（命令行交互）程序，都是其首要任务，其区别仅在于实现方式的不同。GUI通过菜单、对话框之类的图形控件来完成交互，而CUI使用了纯文本的交互方式。各有利弊吧。在程序开发中，许多测试程序用CUI来完成是首选方案。</font>
		</p>
		<p>
				<font color="#808080" size="2">getopt() 函数是一个标准库调用，可允许您使用直接的 while/switch 语句方便地逐个处理命令行参数和检测选项（带或不带附加的参数）。与其类似的 getopt_long() 允许在几乎不进行额外工作的情况下处理更具描述性的长选项，这非常受开发人员的欢迎。 </font>
		</p>
		<p>
				<font color="#808080" size="2">既然已经知道了如何方便地处理命令行选项，现在就可以集中精力改进您的程序的命令行，可以添加长选项支持，或添加之前由于不想向程序添加额外的命令行选项处理而搁置的任何其他选项。但不要忘记在某处记录您所有的选项和参数，并提供某种类型的内置帮助函数来为健忘的用户提供帮助。</font>
		</p>
<img src ="http://www.cppblog.com/yeqing/aggbug/15768.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yeqing/" target="_blank">夜沁</a> 2006-11-29 09:29 <a href="http://www.cppblog.com/yeqing/articles/15768.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>gSOAP学习体会</title><link>http://www.cppblog.com/yeqing/articles/12762.html</link><dc:creator>夜沁</dc:creator><author>夜沁</author><pubDate>Wed, 20 Sep 2006 08:21:00 GMT</pubDate><guid>http://www.cppblog.com/yeqing/articles/12762.html</guid><wfw:comment>http://www.cppblog.com/yeqing/comments/12762.html</wfw:comment><comments>http://www.cppblog.com/yeqing/articles/12762.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/yeqing/comments/commentRss/12762.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yeqing/services/trackbacks/12762.html</trackback:ping><description><![CDATA[
		<div style="FONT-SIZE: 12px">
				<font color="#808080">前一阶段写gSOAP 的文章没保存好，后来想写的，越学越没有写的勇气了，感觉自己很菜，但是现在感觉还是写点就算给入门者一点提示吧。另外虽说这篇文章是自己写的，但是却感觉是东拼西凑的，有很多别人的东西了。<br />   看了我转载的关于soap 的文章，大家想必对soap有所了解了吧，那么gSOAP是什么那？<br />gSOAP是一个开源的项目，用它可以方便的使用c/c++地进行SOAP客户端和服务器端编程，而不必了解xml和SOAP协议的细节。这样使用者就可以专注于自己的web service 客户端或服务器端的编写，而不用纠缠与其它细节。我第一次接触这些东西，我对SOAP的理解是这样的：以http协议为基本的通信协议，以xml文件形式请求远程服务，再以xml文件的形式返回执行结果，我理解的就这么简单了，有啥不妥处，还请指教阿。<br />实践一下才有理性认识，下面是我自己在windows下，具体说来就是用vc 6.0下编写的一个很简单的客户端程序调用远程的服务，来发送电子邮件，感觉很爽吧。<br />首先我们到</font>
				<a href="http://sourceforge.net/project/showfiles.php?group_id=52781" target="_blank">
						<font color="#808080">http://sourceforge.net/project/showfiles.php?group_id=52781</font>
				</a>
				<font color="#808080">下载gSOAP下载工具集吧，不同的系统下用的gSOAP是不一样的，根据需要下载了windows下的和linux下的。<br />gSOAP工具集不需要安装，直接解压就可以了。在/bin目录下我们可以看到两个可执行文件：<br />soapcpp2.exe: gSOAP编译器，编译头文件生成服务器和客户端都需要的 c/c++文件。 <br />wsdl2h.exe: 编译wsdl文件生成c/c++头文件。<br />工具就算准备好了。<br />其次，我们到</font>
				<a href="http://www.abysal.com/soap/AbysalEmail.wsdl" target="_blank">
						<font color="#808080">http://www.abysal.com/soap/AbysalEmail.wsdl</font>
				</a>
				<font color="#808080">下载<br />wsdl文件，假设保存文件名为：AbysalEmail.wsdl。所谓的wsdl文件翻译成中<br />文就是网络服务描述文件了。我们用wsdl2h.exe工具来根据wsdl文件生成<br />c/c++头文件，可以用-c选项是生成纯c的头文件，另外用-s选项是说明我们在<br />程序中不使用stl，注意了默认我们是适用stl的。<br />用如下命令：<br />wsdl2h  -o AbysalEmail.h AbysalEmail.wsdl<br />既可以生成我们需要的AbysalEmail.h头文件了。这里文件名可以随便起了。<br />将下载的gsoap的import里的stlvector.h中文件拷贝到当前的文件夹下，因为默认是使用stl的，所以需要它。<br />然后执行soapcpp2 命令来生成存根程序，用如下命令：<br />soapcpp2  -C AbysalEmail.h<br />-C  选项是只生成客户端的，默认是生成客户端和服务器端的，如果你在程序中使用了vector还要加上 –limport选项。<br />即可以生存客户端存根程序和框架了。<br />soapClient.cpp：编译客户端的需要的存根例程。<br />soapC.cpp，soapH.h:用来序列化和反序列化c/c++不同数据类型。<br />soapServer.cpp: 编译服务器端的需要的存根例程。<br />soapXXXProxy.h: 生成的代理类的头文件,使用代理类时需要此文件。<br />本程序为soapSendEmailBindingProxy.h。<br /><br />第三步，就是在vc中建个工程，设置如下：<br />在vc6中建立工程，其源文件为：sendMailClient.cpp soapC.cpp <br />                                    soapClient.cpp   stdsoap2.cpp<br />头文件为：      AbysalEmail.h soapH.h soapStub.h stdsoap2.h      <br />其他依赖文件为：basetsd.h  sendemailbinding.nsmp<br />stdsoap2.cpp stdsoap2.h是下载的gSOAP中包含的。<br />另外在所需要的库中把wsock32.lib加上，gSOAP也是采用socket方式连接的。<br />其中sendMailClient.cpp为我写的客户端程序，程序如下：<br /><br />#include "soapH.h"                          // 得到存根程序<br />#include "SendEmailBinding.nsmap"            //得到名称空间映射表<br />#include &lt;iostream&gt;<br />#include  &lt;string&gt;<br />#include "soapSendEmailBindingProxy.h"<br /><br />using namespace std;<br /><br />int main(int argc, char **argv) <br />{<br />        struct soap email_soap;<br />        int result = -1;<br />   SendEmailBinding  EmailBind;              //生成代理类对象<br />    _ns1__SendEmail  sendEmail;              //web服务发送电子邮件对象<br />   _ns1__SendEmailResponse  Email_Response;  //web 服务返回发送结果对象<br />   string from = "mseaspring";<br />   string to   = "David";<br />   string sub = "Hello test!";<br />   sendEmail.From = &amp;from;<br />   sendEmail.FromAddress = "</font>
				<a href="mailto:mseaspring@hotmail.com">
						<font color="#808080">mseaspring@hotmail.com</font>
				</a>
				<font color="#808080">";<br />   sendEmail.MsgBody = "I want to test a web service!";<br />   sendEmail.To = &amp;to;<br />   sendEmail.ToAddress = "</font>
				<a href="mailto:mseaspring@gmail.com">
						<font color="#808080">mseaspring@gmail.com</font>
				</a>
				<font color="#808080">";<br />   sendEmail.Subject = ⊂<br /><br />        result = EmailBind.__ns1__SendEmail(&amp;sendEmail,  &amp;Email_Response);<br />        if (result != 0)<br />        {<br />                printf("soap error ,errcode = %d\n", result);<br />        }<br />        else<br />        {<br />            cout&lt;&lt;"The result is :"&lt;&lt;Email_Response.ReturnCode&lt;&lt;endl;<br />                cout&lt;&lt;"恭喜你，邮件发送成功！"&lt;&lt;endl;<br />        }<br />        return 0;<br />}我程序中是采用代理类的方式编写的程序，不用代理类的代码如下：<br />#include "soapH.h"                          //  得到存根程序<br />#include "SendEmailBinding.nsmap"            // 得到名称空间映射表<br />#include &lt;iostream&gt;<br />#include  &lt;string&gt;<br />using namespace std;<br /><font size="3"></font><br />int main(int argc, char **argv) <br />{<br />        struct soap email_soap;<br />        //初始化gSoap运行时环境变量，只需初始化一次<br />        soap_init(&amp;email_soap);<br />        int result = -1;<br />        //远程web服务的endpoint URL<br />   const char* server="http://www.abysal.com/soap/soapmail.wdtp";<br />   string from = "mseaspring";<br />   string to   = "David";<br />   string sub = "Hello test!";<br />   sendEmail.From = &amp;from;<br />   sendEmail.FromAddress = "</font>
				<a href="mailto:mseaspring@hotmail.com">
						<font color="#808080">mseaspring@hotmail.com</font>
				</a>
				<font color="#808080">";<br />   sendEmail.MsgBody = "I want to test a web service!";<br />   sendEmail.To = &amp;to;<br />   sendEmail.ToAddress = "</font>
				<a href="mailto:mseaspring@gmail.com">
						<font color="#808080">mseaspring@gmail.com</font>
				</a>
				<font color="#808080">";<br />   sendEmail.Subject = ⊂<br />    //调用根据远程服务产生函数的接口<br />result = soap_call___ns1__SendEmail(&amp;email_soap, server, "", &amp;sendEmail,  &amp;Email_Response);<br />        if(email_soap.error)<br />        {<br />                //在stderr流中打印soap的错误信息<br />                soap_print_fault(&amp;email_soap,stderr);<br />                result = email_soap.error;<br />        }<br />    soap_destroy(&amp;email_soap);// 删除反序列化类的实例,仅用于c++<br />        soap_end(&amp;email_soap);    // 清空已经并行化的数据<br />        soap_done(&amp;email_soap);   // 与gSOAP 环境相分离,关闭连接<br />        if (result != 0)<br />        {<br />                printf("soap error ,errcode = %d\n", result);<br />        }<br />        else<br />        {<br />            cout&lt;&lt;"The result is :"&lt;&lt;Email_Response.ReturnCode&lt;&lt;endl;<br />                cout&lt;&lt;"恭喜你，邮件发送成功！"&lt;&lt;endl;<br />        }<br />        return 0;<br />}<br />你可能会问我怎么知道远程服务的接口阿？ 到soapStub.h中去找就可以了，至于代理类的使用，到代理类头文件中一看便知。<br />好了，终于要写完了，当然我们不仅可以编写客户端也可以编写服务器端程序，至于服务器端，有兴趣的可以自己看看gSOAP里面的文档，也很简单的，不过也要花点时间学习的了，呵呵。<br />如果对于上面程序，有谁没调试成功联系我，邮箱都写在程序里那。呵呵。</font>
		</div>
<img src ="http://www.cppblog.com/yeqing/aggbug/12762.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yeqing/" target="_blank">夜沁</a> 2006-09-20 16:21 <a href="http://www.cppblog.com/yeqing/articles/12762.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SOAP协议规范</title><link>http://www.cppblog.com/yeqing/articles/12759.html</link><dc:creator>夜沁</dc:creator><author>夜沁</author><pubDate>Wed, 20 Sep 2006 07:15:00 GMT</pubDate><guid>http://www.cppblog.com/yeqing/articles/12759.html</guid><wfw:comment>http://www.cppblog.com/yeqing/comments/12759.html</wfw:comment><comments>http://www.cppblog.com/yeqing/articles/12759.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yeqing/comments/commentRss/12759.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yeqing/services/trackbacks/12759.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 本文来自网络搜集，感谢原作者。1. 简介  SOAP以XML形式提供了一个简单、轻量的用于在分散或分布环境中交换结构化和类型信息的机制。SOAP本身并没有定义任何应用程序语义，如编程模型或特定语义的实现；实际上它通过提供一个有标准组件的包模型和在模块中编码数据的机制，定义了一个简单的表示应用程序语义的机制。这使SOAP能够被用于从消息传递到RPC的各种系统。  SOAP包括三个部分  * SOAP...&nbsp;&nbsp;<a href='http://www.cppblog.com/yeqing/articles/12759.html'>阅读全文</a><img src ="http://www.cppblog.com/yeqing/aggbug/12759.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yeqing/" target="_blank">夜沁</a> 2006-09-20 15:15 <a href="http://www.cppblog.com/yeqing/articles/12759.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>