﻿<?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++博客-futual -文章分类-unix</title><link>http://www.cppblog.com/strawberry/category/18871.html</link><description>  </description><language>zh-cn</language><lastBuildDate>Tue, 04 Sep 2012 00:09:09 GMT</lastBuildDate><pubDate>Tue, 04 Sep 2012 00:09:09 GMT</pubDate><ttl>60</ttl><item><title>shell编程小知识总结</title><link>http://www.cppblog.com/strawberry/articles/183151.html</link><dc:creator>futual</dc:creator><author>futual</author><pubDate>Fri, 13 Jul 2012 03:44:00 GMT</pubDate><guid>http://www.cppblog.com/strawberry/articles/183151.html</guid><wfw:comment>http://www.cppblog.com/strawberry/comments/183151.html</wfw:comment><comments>http://www.cppblog.com/strawberry/articles/183151.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/strawberry/comments/commentRss/183151.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/strawberry/services/trackbacks/183151.html</trackback:ping><description><![CDATA[本文结合大量实例阐述如何编写一个shell脚本。<br /> <br /> 　　为什么要进行shell编程<br /> <br /> 　　在Linux系统中，虽然有各种各样的图形化接口工具，但是sell仍然是一个非常灵活的工具。Shell不仅仅是命令的收集，而且是一门非常棒的编 程语言。您可以通过使用shell使大量的任务自动化，shell特别擅长系统管理任务，尤其适合那些易用性、可维护性和便携性比效率更重要的任务。<br /> 　　下面，让我们一起来看看shell是如何工作的：<br /> <br /> 　　建立一个脚本<br /> <br /> 　　Linux中有好多中不同的shell，但是通常我们使用bash (bourne again shell)  进行shell编程，因为bash是免费的并且很容易使用。所以在本文中笔者所提供的脚本都是使用bash（但是在大多数情况下，这些脚本同样可以在 bash的大姐，bourne shell中运行）。<br /> <br /> 　　如同其他语言一样，通过我们使用任意一种文字编辑器，比如nedit、kedit、emacs、vi<br /> 　　等来编写我们的shell程序。<br /> 　　程序必须以下面的行开始（必须方在文件的第一行）：<br /> 　　#!/bin/sh<br /> <br /> 　　符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。<br /> 　　当编辑好脚本时，如果要执行该脚本，还必须使其可执行。<br /> 　　要使脚本可执行：<br /> 　　chmod +x filename <br /> 　　然后，您可以通过输入： ./filename 来执行您的脚本。<br /> <br /> 　　注释<br /> <br /> 　　在进行shell编程时，以#开头的句子表示注释，直到这一行的结束。我们真诚地建议您在程序中使用注释。如果您使用了注释，那么即使相当长的时间内没有使用该脚本，您也能在很短的时间内明白该脚本的作用及工作原理。<br /> <br /> 　　变量<br /> <br /> 　　在其他编程语言中您必须使用变量。在shell编程中，所有的变量都由字符串组成，并且您不需要对变量进行声明。要赋值给一个变量，您可以这样写： <br /> <br /> 　　变量名=值<br /> <br /> 　　取出变量值可以加一个美元符号（$）在变量前面： <br /> <br /> 　　#!/bin/sh<br /> 　　#对变量赋值：<br /> 　　a="hello world"<br /> 　　# 现在打印变量a的内容：<br /> 　　echo "A is:"<br /> 　　echo $a<br /> <br /> 　　在您的编辑器中输入以上内容，然后将其保存为一个文件first。之后执行chmod +x first <br /> 　　使其可执行，最后输入./first执行该脚本。<br /> 　　这个脚本将会输出：<br /> 　　A is:<br /> 　　hello world<br /> <br /> 　　有时候变量名很容易与其他文字混淆，比如：<br /> 　　num=2<br /> 　　echo "this is the $numnd"<br /> 　　这并不会打印出"this is the 2nd"，而仅仅打印"this is the "，因为shell会去搜索变量numnd的值，但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量： <br /> 　　num=2<br /> 　　echo "this is the ${num}nd"<br /> 　　这将打印： this is the 2nd <br /> <br /> 　　有许多变量是系统自动设定的，这将在后面使用这些变量时进行讨论。<br /> <br /> 　　如果您需要处理数学表达式，那么您需要使用诸如expr等程序（见下面）。<br /> 　　除了一般的仅在程序内有效的shell变量以外，还有环境变量。由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论，因为通常情况下仅仅在登录脚本中使用环境变量。 <br /> <br /> 　　Shell命令和流程控制<br /> <br /> 　　在shell脚本中可以使用三类命令：<br /> <br /> 　　1)Unix 命令:<br /> <br /> 　　虽然在shell脚本中可以使用任意的unix命令，但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。<br /> <br /> <br /> 　　常用命令语法及功能<br /> <br /> 　　echo "some text": 将文字内容打印在屏幕上<br /> <br /> 　　ls: 文件列表<br /> <br /> 　　wc &#8211;l filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数<br /> <br /> 　　cp sourcefile destfile: 文件拷贝<br /> <br /> 　　mv oldname newname : 重命名文件或移动文件<br /> <br /> 　　rm file: 删除文件<br /> <br /> 　　grep 'pattern' file: 在文件内搜索字符串比如：grep 'searchstring' file.txt<br /> <br /> 　　cut -b colnum file: 指定欲显示的文件内容范围，并将它们输出到标准输出设备比如：输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆，这是两个完全不同的命令<br /> <br /> 　　cat file.txt: 输出文件内容到标准输出设备（屏幕）上<br /> <br /> 　　file somefile: 得到文件类型<br /> <br /> 　　read var: 提示用户输入，并将输入赋值给变量<br /> <br /> 　　sort file.txt: 对file.txt文件中的行进行排序<br /> <br /> 　　uniq: 删除文本文件中出现的行列比如： sort file.txt | uniq<br /> <br /> 　　expr: 进行数学运算Example: add 2 and 3expr 2 "+" 3<br /> <br /> 　　find: 搜索文件比如：根据文件名搜索find . -name filename -print<br /> <br /> 　　tee: 将数据输出到标准输出设备(屏幕) 和文件比如：somecommand | tee outfile<br /> <br /> 　　basename file: 返回不包含路径的文件名比如： basename /bin/tux将返回 tux<br /> <br /> 　　dirname file: 返回文件所在路径比如：dirname /bin/tux将返回 /bin<br /> <br /> 　　head file: 打印文本文件开头几行<br /> <br /> 　　tail file : 打印文本文件末尾几行<br /> <br /> 　　sed:  Sed是一个基本的查找替换程序。可以从标准输入（比如命令管道）读入文本，并将结果输出到标准输出（屏幕）。该命令采用正则表达式（见参考）进行搜索。 不要和shell中的通配符相混淆。比如：将linuxfocus 替换为 LinuxFocus ：cat text.file | sed  's/linuxfocus/LinuxFocus/' &gt; newtext.file<br /> <br /> 　　awk: awk 用来从文本文件中提取字段。缺省地，字段分割符是空格，可以使用-F指定其他分割符。cat file.txt | awk  -F, '{print $1 "," $3 }'这里我们使用，作为字段分割符，同时打印第一个和第三个字段。如果该文件内容如下： Adam  Bor, 34, IndiaKerry Miller, 22, USA命令输出结果为：Adam Bor, IndiaKerry Miller,  USA<br /> <br /> <br /> 　　2) 概念: 管道, 重定向和 backtick<br /> <br /> 　　这些不是系统命令，但是他们真的很重要。<br /> <br /> 　　管道 (|) 将一个命令的输出作为另外一个命令的输入。<br /> 　　grep "hello" file.txt | wc -l<br /> 　　在file.txt中搜索包含有&#8221;hello&#8221;的行并计算其行数。<br /> 　　在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。<br /> <br /> 　　重定向：将命令的结果输出到文件，而不是标准输出（屏幕）。<br /> 　　&gt; 写入文件并覆盖旧文件<br /> 　　&gt;&gt; 加到文件的尾部，保留旧文件内容。<br /> <br /> 　　反短斜线<br /> 　　使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。<br /> 　　命令： <br /> 　　find . -mtime -1 -type f -print<br /> 　　用来查找过去24小时（-mtime &#8211;2则表示过去48小时）内修改过的文件。如果您想将所有查找到的文件打一个包，则可以使用以下脚本： <br /> 　　#!/bin/sh<br /> 　　# The ticks are backticks (`) not normal quotes ('):<br /> 　　tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`<br /> <br /> 　　3) 流程控制<br /> <br /> 　　"if" 表达式 如果条件为真则执行then后面的部分： <br /> 　　if ....; then<br /> 　　　 ....<br /> 　　elif ....; then<br /> 　　　 ....<br /> 　　else<br /> 　　　 ....<br /> 　　fi<br /> 　　大多数情况下，可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等&#8230;<br /> 　　通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。 <br /> 　　[ -f "somefile" ] ：判断是否是一个文件<br /> 　　[ -x "/bin/ls" ] ：判断/bin/ls是否存在并有可执行权限<br /> 　　[ -n "$var" ] ：判断$var变量是否有值<br /> 　　[ "$a" = "$b" ] ：判断$a和$b是否相等<br /> <br /> 　　执行man test可以查看所有测试表达式可以比较和判断的类型。<br /> 　　直接执行以下脚本： <br /> 　　#!/bin/sh<br /> 　　if [ "$SHELL" = "/bin/bash" ]; then<br /> 　　　 echo "your login shell is the bash (bourne again shell)"<br /> 　　else<br /> 　　　 echo "your login shell is not bash but $SHELL"<br /> 　　fi<br /> 　　变量$SHELL包含了登录shell的名称，我们和/bin/bash进行了比较。<br /> <br /> 　　快捷操作符<br /> <br /> 　　熟悉C语言的朋友可能会很喜欢下面的表达式：<br /> 　　[ -f "/etc/shadow" ] &amp;&amp; echo "This computer uses shadow passwors"<br /> 　　这里 &amp;&amp;  就是一个快捷操作符，如果左边的表达式为真则执行右边的语句。您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在则打印&#8221;  This computer uses shadow passwors&#8221;。同样或操作(||)在shell编程中也是可用的。这里有个例子： <br /> #!/bin/sh<br /> mailfolder=/var/spool/mail/james<br /> [ -r "$mailfolder" ]' '{ echo "Can not read $mailfolder" ; exit 1; }<br /> echo "$mailfolder has mail from:"<br /> grep "^From " $mailfolder<br /> 　　该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效，打印错误信息后脚本退出。这里有个问题，那就是我们必须有两个命令： <br /> -打印错误信息<br /> -退出程序 <br /> 　　我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。<br /> 　　不用与和或操作符，我们也可以用if表达式作任何事情，但是使用与或操作符会更便利很多。<br /> <br /> 　　case表达式可以用来匹配一个给定的字符串，而不是数字。 <br /> case ... in<br /> ...) do something here ;;<br /> esac<br /> 　　让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型，比如： <br /> file lf.gz<br /> 　　这将返回：<br /> lf.gz: gzip compressed data, deflated, original filename,<br /> last modified: Mon Aug 27 23:09:18 2001, os: Unix<br /> 我们利用这一点写了一个叫做smartzip的脚本，该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件： <br /> #!/bin/sh<br /> ftype=`file "$1"`<br /> case "$ftype" in<br /> "$1: Zip archive"*)<br /> 　　unzip "$1" ;;<br /> "$1: gzip compressed"*)<br /> 　　gunzip "$1" ;;<br /> "$1: bzip2 compressed"*)<br /> 　　bunzip2 "$1" ;;<br /> *) error "File $1 can not be uncompressed with smartzip";;<br /> esac<br /> <br /> 　　您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。也就是说，当我们运行：<br /> smartzip articles.zip <br /> $1 就是字符串 articles.zip <br /> <br /> 　　select 表达式是一种bash的扩展应用，尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。 <br /> select var in ... ; do<br /> 　break<br /> done<br /> .... now $var can be used ....<br /> 下面是一个例子：<br /> #!/bin/sh<br /> echo "What is your favourite OS?"<br /> select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do<br /> 　　　　break<br /> done<br /> echo "You have selected $var"<br /> 下面是该脚本运行的结果： <br /> What is your favourite OS?<br /> 1) Linux<br /> 2) Gnu Hurd<br /> 3) Free BSD<br /> 4) Other<br /> #? 1<br /> You have selected Linux<br /> <br /> 您也可以在shell中使用如下的loop表达式：<br /> while ...; do<br /> ....<br /> done<br /> <br /> while-loop 将运行直到表达式测试为真。will run while the expression that we test for  is true. 关键字"break" 用来跳出循环。而关键字&#8221;continue&#8221;用来不执行余下的部分而直接跳到下一个循环。<br /> <br /> for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量： <br /> for var in ....; do<br /> 　....<br /> done<br /> <br /> 在下面的例子中，将分别打印ABC到屏幕上： <br /> #!/bin/sh<br /> for var in A B C ; do<br /> 　echo "var is $var"<br /> done<br /> <br /> 下面是一个更为有用的脚本showrpm，其功能是打印一些RPM包的统计信息：<br /> #!/bin/sh<br /> # list a content summary of a number of RPM packages<br /> # USAGE: showrpm rpmfile1 rpmfile2 ...<br /> # EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm<br /> for rpmpackage in $*; do<br /> 　if [ -r "$rpmpackage" ];then<br /> 　　echo "=============== $rpmpackage =============="<br /> 　　rpm -qi -p $rpmpackage<br /> 　else<br /> 　　echo "ERROR: cannot read file $rpmpackage"<br /> 　fi<br /> done<br /> <br /> 这里出现了第二个特殊的变量$*，该变量包含了所有输入的命令行参数值。如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm <br /> 此时 $* 包含了 3 个字符串，即openssh.rpm, w3m.rpm and webgrep.rpm. <br /> <br /> <br /> 引号<br /> 在向程序传递任何参数之前，程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符（比如*）替换成合适的文件名，它变量替换成变量值。为了防止程 序作这种替换，您可以使用引号：让我们来看一个例子，假设在当前目录下有一些文件，两个jpg文件， mail.jpg 和tux.jpg。<br /> <br /> #!/bin/sh<br /> echo *.jpg<br /> 这将打印出"mail.jpg tux.jpg"的结果。<br /> 引号 (单引号和双引号) 将防止这种通配符扩展： <br /> #!/bin/sh<br /> echo "*.jpg"<br /> echo '*.jpg'<br /> 这将打印"*.jpg" 两次。 <br /> 单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。 <br /> #!/bin/sh<br /> echo $SHELL<br /> echo "$SHELL"<br /> echo '$SHELL'<br /> <br /> 运行结果为： <br /> /bin/bash<br /> /bin/bash<br /> $SHELL<br /> <br /> 最后，还有一种防止这种扩展的方法，那就是使用转义字符&#8212;&#8212;反斜杆： <br /> echo *.jpg<br /> echo $SHELL<br /> 这将输出：<br /> *.jpg<br /> $SHELL<br /> Here documents<br /> <br /> 当要将几行文字传递给一个命令时，here  documents（译者注：目前还没有见到过对该词适合的翻译）一种不错的方法。对每个脚本写一段帮助性的文字是很有用的，此时如果我们四有那个 here documents就不必用echo函数一行行输出。 一个 "Here document" 以 &lt;&lt;  开头，后面接上一个字符串，这个字符串还必须出现在here  document的末尾。下面是一个例子，在该例子中，我们对多个文件进行重命名，并且使用here documents打印帮助： <br /> <br /> #!/bin/sh<br /> # we have less than 3 arguments. Print the help text:<br /> if [ $# -lt 3 ] ; then<br /> cat &lt;&lt;HELP<br /> ren -- renames a number of files using sed regular expressions<br /> <br /> USAGE: ren 'regexp' 'replacement' files...<br /> <br /> EXAMPLE: rename all *.HTM files in *.html:<br /> 　ren 'HTM$' 'html' *.HTM<br /> <br /> HELP<br /> 　exit 0<br /> fi<br /> OLD="$1"<br /> NEW="$2"<br /> # The shift command removes one argument from the list of<br /> # command line arguments.<br /> shift<br /> shift<br /> # $* contains now all the files:<br /> for file in $*; do<br /> 　　if [ -f "$file" ] ; then<br /> 　　　newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`<br /> 　　　if [ -f "$newfile" ]; then<br /> 　　　　echo "ERROR: $newfile exists already"<br /> 　　　else<br /> 　　　　echo "renaming $file to $newfile ..."<br /> 　　　　mv "$file" "$newfile"<br /> 　　　fi<br /> 　　fi<br /> done<br /> <br /> 这是一个复杂一些的例子。让我们详细讨论一下。第一个if表达式判断输入命令行参数是否小于3个 (特殊变量$# 表示包含参数的个数)  。如果输入参数小于3个，则将帮助文字传递给cat命令，然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。  如果输入参数等于或大于3个，我们就将第一个参数赋值给变量OLD，第二个参数赋值给变量NEW。下一步，我们使用shift命令将第一个和第二个参数从 参数列表中删除，这样原来的第三个参数就成为参数列表$*的第一个参数。然后我们开始循环，命令行参数列表被一个接一个地被赋值给变量$file。接着我 们判断该文件是否存在，如果存在则通过sed命令搜索和替换来产生新的文件名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的目 的：得到了旧文件名和新文件名。然后使用mv命令进行重命名。<br /> <br /> 函数<br /> <br /> 如果您写了一些稍微复杂一些的程序，您就会发现在程序中可能在几个地方使用了相同的代码，并且您也会发现，如果我们使用了函数，会方便很多。一个函数是这个样子的： <br /> functionname()<br /> {<br /> # inside the body $1 is the first argument given to the function<br /> # $2 the second ...<br /> body<br /> }<br /> 您需要在每个程序的开始对函数进行声明。<br /> <br /> 下面是一个叫做xtitlebar的脚本，使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help的函数。正如您可以看到的那样，这个定义的函数被使用了两次。 <br /> #!/bin/sh<br /> # vim: set sw=4 ts=4 et:<br /> <br /> help()<br /> {<br /> 　　cat &lt;&lt;HELP<br /> xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole<br /> <br /> USAGE: xtitlebar [-h] "string_for_titelbar"<br /> <br /> OPTIONS: -h help text<br /> <br /> EXAMPLE: xtitlebar "cvs"<br /> <br /> HELP<br /> 　　exit 0<br /> }<br /> <br /> # in case of error or if -h is given we call the function help:<br /> [ -z "$1" ] &amp;&amp; help<br /> [ "$1" = "-h" ] &amp;&amp; help<br /> <br /> # send the escape sequence to change the xterm titelbar:<br /> echo -e "33]0;$107" <br /> #<br /> <br /> 　　在脚本中提供帮助是一种很好的编程习惯，这样方便其他用户（和您）使用和理解脚本。<br /> <br /> 　　命令行参数<br /> <br /> 　　我们已经见过$* 和 $1, $2 ... $9  等特殊变量，这些特殊变量包含了用户从命令行输入的参数。迄今为止，我们仅仅了解了一些简单的命令行语法（比如一些强制性的参数和查看帮助的-h选项）。 但是在编写更复杂的程序时，您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号，后面再加上参数值 (比如文件名)。 <br /> <br /> 　　有好多方法可以实现对输入参数的分析，但是下面的使用case表达式的例子无遗是一个不错的方法。 <br /> #!/bin/sh<br /> help()<br /> {<br /> 　cat &lt;&lt;HELP<br /> This is a generic command line parser demo.<br /> USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2<br /> HELP<br /> 　exit 0<br /> }<br /> <br /> while [ -n "$1" ]; do<br /> case $1 in<br /> 　　-h) help;shift 1;; # function help is called<br /> 　　-f) opt_f=1;shift 1;; # variable opt_f is set<br /> 　　-l) opt_l=$2;shift 2;; # -l takes an argument -&gt; shift by 2<br /> 　　--) shift;break;; # end of options<br /> 　　-*) echo "error: no such option $1. -h for help";exit 1;;<br /> 　　*) break;;<br /> esac<br /> done<br /> <br /> echo "opt_f is $opt_f"<br /> echo "opt_l is $opt_l"<br /> echo "first arg is $1"<br /> echo "2nd arg is $2"<br /> <br /> 您可以这样运行该脚本：<br /> cmdparser -l hello -f -- -somefile1 somefile2<br /> <br /> 返回的结果是：<br /> opt_f is 1<br /> opt_l is hello<br /> first arg is -somefile1<br /> 2nd arg is somefile2<br /> <br /> 　　这个脚本是如何工作的呢？脚本首先在所有输入命令行参数中进行循环，将输入参数与case表达式进行比较，如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例，首先输入的应该是包含减号的参数。 <br /> <br /> <br /> 　　实例<br /> <br /> 　　一般编程步骤<br /> <br /> 　　现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本（framework.sh），该脚本包含了大多数脚本都需要的框架结构，是一个非常不错的主意。这时候，在写一个新的脚本时我们只需要执行一下copy命令： <br /> cp framework.sh myscript<br /> 然后再插入自己的函数。<br /> <br /> 　　让我们再看两个例子： <br /> <br /> 　　二进制到十进制的转换<br /> <br /> 　　脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子： <br /> #!/bin/sh<br /> # vim: set sw=4 ts=4 et:<br /> help()<br /> {<br /> 　cat &lt;&lt;HELP<br /> b2h -- convert binary to decimal<br /> <br /> USAGE: b2h [-h] binarynum<br /> <br /> OPTIONS: -h help text<br /> <br /> EXAMPLE: b2h 111010<br /> will return 58<br /> HELP<br /> 　exit 0<br /> }<br /> <br /> error()<br /> {<br /> 　　# print an error and exit<br /> 　　echo "$1"<br /> 　　exit 1<br /> }<br /> <br /> lastchar()<br /> {<br /> 　　# return the last character of a string in $rval<br /> 　　if [ -z "$1" ]; then<br /> 　　　　# empty string<br /> 　　　　rval=""<br /> 　　　　return<br /> 　　fi<br /> 　　# wc puts some space behind the output this is why we need sed:<br /> 　　numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `<br /> 　　# now cut out the last char<br /> 　　rval=`echo -n "$1" | cut -b $numofchar`<br /> }<br /> <br /> chop()<br /> {<br /> 　　# remove the last character in string and return it in $rval<br /> 　　if [ -z "$1" ]; then<br /> 　　　　# empty string<br /> 　　　　rval=""<br /> 　　　　return<br /> 　　fi<br /> 　　# wc puts some space behind the output this is why we need sed:<br /> 　　numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `<br /> 　　if [ "$numofchar" = "1" ]; then<br /> 　　　　# only one char in string<br /> 　　　　rval=""<br /> 　　　　return<br /> 　　fi<br /> 　　numofcharminus1=`expr $numofchar "-" 1`<br /> 　　# now cut all but the last char:<br /> 　　rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`<br /> }<br /> <br /> <br /> while [ -n "$1" ]; do<br /> case $1 in<br /> 　　-h) help;shift 1;; # function help is called<br /> 　　--) shift;break;; # end of options<br /> 　　-*) error "error: no such option $1. -h for help";;<br /> 　　*) break;;<br /> esac<br /> done<br /> <br /> # The main program<br /> sum=0<br /> weight=1<br /> # one arg must be given:<br /> [ -z "$1" ] &amp;&amp; help<br /> binnum="$1"<br /> binnumorig="$1"<br /> <br /> while [ -n "$binnum" ]; do<br /> 　　lastchar "$binnum"<br /> 　　if [ "$rval" = "1" ]; then<br /> 　　　　sum=`expr "$weight" "+" "$sum"`<br /> 　　fi<br /> 　　# remove the last position in $binnum<br /> 　　chop "$binnum"<br /> 　　binnum="$rval"<br /> 　　weight=`expr "$weight" "*" 2`<br /> done<br /> <br /> echo "binary $binnumorig is decimal $sum"<br /> #<br /> 　　该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,..)，比如二进制"10"可以这样转换成十进制： <br /> 　　0 * 1 + 1 * 2 = 2 <br /> 　　为了得到单个的二进制数我们是用了lastchar 函数。该函数使用wc &#8211;c计算字符个数，然后使用cut命令取出末尾一个字符。Chop函数的功能则是移除最后一个字符。<br /> <br /> 　　文件循环程序<br /> 　　或许您是想将所有发出的邮件保存到一个文件中的人们中的一员，但是在过了几个月以后，这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的 脚本rotatefile  可以解决这个问题。这个脚本可以重命名邮件保存文件（假设为outmail）为outmail.1，而对于outmail.1就变成了outmail.2  等等等等... <br /> #!/bin/sh<br /> # vim: set sw=4 ts=4 et:<br /> ver="0.1"<br /> help()<br /> {<br /> 　　cat &lt;&lt;HELP<br /> rotatefile -- rotate the file name<br /> <br /> USAGE: rotatefile [-h] filename<br /> <br /> OPTIONS: -h help text<br /> <br /> EXAMPLE: rotatefile out<br /> This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1<br /> and create an empty out-file<br /> <br /> The max number is 10<br /> <br /> version $ver<br /> HELP<br /> 　　exit 0<br /> }<br /> <br /> error()<br /> {<br /> 　　echo "$1"<br /> 　　exit 1<br /> }<br /> while [ -n "$1" ]; do<br /> case $1 in<br /> 　　-h) help;shift 1;;<br /> 　　--) break;;<br /> 　　-*) echo "error: no such option $1. -h for help";exit 1;;<br /> 　　*) break;;<br /> esac<br /> done<br /> <br /> # input check:<br /> if [ -z "$1" ] ; then<br /> error "ERROR: you must specify a file, use -h for help"<br /> fi<br /> filen="$1"<br /> # rename any .1 , .2 etc file:<br /> for n in 9 8 7 6 5 4 3 2 1; do<br /> 　　if [ -f "$filen.$n" ]; then<br /> 　　　　p=`expr $n + 1`<br /> 　　　　echo "mv $filen.$n $filen.$p"<br /> 　　　　mv $filen.$n $filen.$p<br /> 　　fi<br /> done<br /> # rename the original file:<br /> if [ -f "$filen" ]; then<br /> 　　echo "mv $filen $filen.1"<br /> 　　mv $filen $filen.1<br /> fi<br /> echo touch $filen<br /> touch $filen<br /> <br /> 　　这个脚本是如何工作的呢？在检测用户提供了一个文件名以后，我们进行一个9到1的循环。文件9被命名为10，文件8重命名为9等等。循环完成之后，我们将原始文件命名为文件1同时建立一个与原始文件同名的空文件。 <br /> 　　调试<br /> 　　最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印任何变量值。这也是绝大多数的shell程序员要花费80%的时间来调试程序的原因。Shell程序的好处在于不需要重新编译，插入一个echo命令也不需要多少时间。<br /> <br /> 　　shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误，您可以这样来进行调试： <br /> 　　sh -x strangescript<br /> 　　这将执行该脚本并显示所有变量的值。<br /> 　　shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用： <br /> 　　sh -n your_script<br /> 　　这将返回所有语法错误。<br /> 　　我们希望您现在可以开始写您自己的shell脚本，希望您玩得开心。<img src ="http://www.cppblog.com/strawberry/aggbug/183151.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/strawberry/" target="_blank">futual</a> 2012-07-13 11:44 <a href="http://www.cppblog.com/strawberry/articles/183151.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux 标准文件编程库</title><link>http://www.cppblog.com/strawberry/articles/170271.html</link><dc:creator>futual</dc:creator><author>futual</author><pubDate>Fri, 06 Apr 2012 06:59:00 GMT</pubDate><guid>http://www.cppblog.com/strawberry/articles/170271.html</guid><wfw:comment>http://www.cppblog.com/strawberry/comments/170271.html</wfw:comment><comments>http://www.cppblog.com/strawberry/articles/170271.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/strawberry/comments/commentRss/170271.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/strawberry/services/trackbacks/170271.html</trackback:ping><description><![CDATA[在标准库中，结构FILE是指向文件的指针，所有对文件的操作都是通过FILE完成的，FILE指针也称为文件流，它定义在头文件&lt;stdio.h&gt;，<br />相对于整形的低级文件I/O描述符，它提供了I/O缓冲功能。<br />1，创建、打开、关闭与删除文件的函数族<br />#include&lt;stdio.h&gt;<br />FILE *fopen(const char *filename,const char *type);<br />FILE *freopen(const char *filename,const char *type,FIlE *strem);<br />int fclose(FILE * stream);<br />int remove(const char *filename);<br />int rename(const char *oldname,const char *newname);<br />/*filename&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 打开文件的名称(带路径)*/<br />/*Type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 打开文件的方式，由权限和类型两部分组成，前者可以是r、w、a、r+、w+、a+，后者默认表示文本文件、使用b表示二进制文件*/<br />/*stream&nbsp; 已经打开的文件指针*/<br />函数fopen打开或创建文件；fclose关闭文件；函数freopen重新打开文件；函数remove删除磁盘文件；函数rename更改文件名称。<br />ex1:以只读方式打开文本文件/etc/passwd:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FILE *fp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fp=fopen("/etc/passwd","r");<br />ex2:以二进制方式创建文件rr.txt：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FILE *fp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fp=fopen("rr.txt","wb");<br />(只读打开'r',只写打开'w',追加'a'，增强功能读写打开'+')<br />2)freopen函数<br />&nbsp;&nbsp;&nbsp;&nbsp; 本函数实现文件流的替换。它首先关闭原文件流stream，然后再以freopen的方式打开一个新的文件流，此后对原文件流的任意操作都自动转换为对新文件流的操作。成功时返回指向新文件的FILE型指针，否则返回NULL。<br />&nbsp; Unix进程默认打开三个文件：标准输出、标准输入、标准错误输出，它们的FILE标识符号分别是stdout、stdin、stderr。函数freopen常用于将以上三个文件流重定向，实现方法如下：<br />/*-----------open.c------------*/<br />#include&lt;stdio.h&gt;<br />void main()<br />{<br />&nbsp; FILE *fp;<br />&nbsp; char *szBuf[100];<br />&nbsp; /*将屏幕标准输出的内容重定向到文件"/tmp/1" */<br />&nbsp; if((fp=freopen("/tmp/1","w",stderr))==NULL)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp; printf("stderr--/tmp/1 failed./n");<br />&nbsp;&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }<br />&nbsp; /*stderr已经输出重定向，所有错误输出内容都将写到"/tmp/1"*/<br />&nbsp; fputs("T Like Unix./n",stderr);<br />&nbsp; /*关闭文件*/<br />&nbsp; fclose(fp);<br />&nbsp; /*将标准输入由键盘输入更改为从文件"/tmp/1"中读入*/<br />&nbsp; if((fp=freopen("/tmp/1","r",stdin))==NULL)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp; printf("stdin --/tmp/1 failed./n");<br />&nbsp;&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }<br />&nbsp; memset(szBuf,0,sizeof(szBuf));<br />&nbsp;/*stdin 已经输入重定向，所有内容都将写入文件"/tmp/1"*/<br />&nbsp; fgets(szBuf,sizeof(szBuf),stdin);<br />&nbsp; printf("szBuf=[%s]",szBuf);<br />&nbsp; fclose(fp);<br />}<br />&nbsp;编译与运行：<br />gcc -o open.c&nbsp; open<br />./open<br />szBuf = [I Like UNIX.]<br />3)fclose函数<br />&nbsp;&nbsp; 为了减少系统资源消耗、避免误改文件内容和更新文件缓冲，应该及时关闭在将来一段时间内不需要使用的文件。函数fclose关闭文件流stream，成功时返回0，否则返回EOF；<br />4)remove<br />&nbsp;&nbsp; 函数remove删除字符串filename指定的文件或目录，当filename指定文件时，remove相当于unlinke函数，当filename指定目录时，相当于rmdir;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 文件的无格式读写<br />1)字符读写<br />&nbsp;&nbsp;&nbsp; 字符读写函数每次只操作一个字符，为了提高磁盘读写效率，标准文件<a class="keylink" href="http://www.2cto.com/kf" target="_blank">编程</a>中提供了缓冲处理。<br />&nbsp;&nbsp;&nbsp; (1)字符输入函数<br />#include&lt;stdio.h&gt;<br />int getc(FILE *stream);<br />int getchar(void);<br />int fgetc(FILE *stream);<br />&nbsp;&nbsp; 函数getc以unsigned char类型读取文件输入流stream中的一个字符，并将该无符号字符转化为整数返回，同时移动文件指针到下一个字符处。函数getchar实际上是关于getc的一个宏定义"getc(stdin)".<br />&nbsp;&nbsp;&nbsp;&nbsp; 函数fgetc的功能类似于getc，不同的是，它的执行速度远低于getc，因此getc常常被定义在宏中使用。<br />&nbsp;&nbsp;&nbsp;&nbsp; 当文件结束或错误时，这三个函数都将返回EOF，EOF为常数。特别注意，他们的返回值都是整形。&nbsp; EOF一般都是定义为int型-1,在某些UNIX中，将EOF强行转换为字符型后的数值不再与原值相等，从而程序不能正确执行。<br />&nbsp;&nbsp;&nbsp;&nbsp; 因此错误的文件结束判断代码如下：<br />char&nbsp; c;/*---char c是错误的---*/<br />.....<br />c=getc(FILENAME);<br />if(c==EOF)<br />....<br />&nbsp;&nbsp;&nbsp; 而正确的文件结束判断代码是：<br />int c;/*---char c是错误的---*/<br />.....<br />c=getc(FILENAME);<br />if(c==EOF)<br />....<br />&nbsp;&nbsp; (2)字符输出函数族<br />#include&lt;stdio.h&gt;<br />int putc(int c,FILE *stream);<br />int putchar(int c);<br />int fputc(int c,FILE *stream);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数putc首先先将int型参数c自动转换为unsigned char类型，然后写入文件流stream中，同时移动文件指针到下一个字符处。函数putchar 实际上是关于putc的宏定义"putc(stdout)"。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3)实例<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 报文解析是Unix应用的一个重要内容。双方把约定的几个域通过某种排序和分割方式组合在一起，就成了报文。报文解析就是从报文中分解出各个域的数据，比如从银行的代收代付报文查找出账号域和资金域的内容。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 字符串报文是报文的重要分支，它以字符串为载体记录了域的数据。在字符串报文中，域与域之间最常见的2中分隔方式是固定长度分割和特殊字符(串)分割。前 者每个域占用固定宽度，解析时只是读取特定位置的数据即可。后者的域与域之间由固定的字符(串)连接，解析时需要计算固定字符(串)出现的次数，以决定域 的序号和内容。Unix中passwd文件就是由":"分割的字符串报文组合而成的。<br />&nbsp;&nbsp;&nbsp;&nbsp; 例子：一个使用字符读写函数解析报文的，程序读取文件的"/etc/passwd"中每一个字符，并将"用户名称"域(报文的第一个域)单独提出，存入文件"copyname.txt"中，源程序如下：<br />#include&lt;stdio.h&gt;<br />void&nbsp; main()<br />{<br />&nbsp; FILE *fpr,*fpw;<br />&nbsp; int c=0,f=0;<br />&nbsp; /*以下打开源文件*/<br />&nbsp; if((fpr=fopen("/etc/passwd","r"))==NULL)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp; printf("open file /etc/passwd failed./n");<br />&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }<br />&nbsp; /*以下打开目标文件*/<br />&nbsp; if((fpw=fopen("./copyname.txt","w"))==NULL)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp; printf("open file ./copyname.txt failed./n");<br />&nbsp;&nbsp;&nbsp;&nbsp; fclose(fpr);/*如果程序需要同时打开两个或两个以上文件，当后一个文件的打开操作发生错误，而不得不退出函数时，务必关闭前面已经打开的文件。*/<br />&nbsp;&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }<br />&nbsp; while((c=getc(fpr)!=EOF))<br />&nbsp; {&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp; /*字符已经读取到了c中*/<br />&nbsp;&nbsp;&nbsp; if(f==0)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(c!=':') putchar(putc(c,fpw));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else f=1;<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; else if(c=='/n')<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f=0;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; putchar(getc(c,fpw));<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp; }<br />&nbsp; fclose(fpr);<br />&nbsp; fclose(fpw);<br />} 
<p>2)按行读写<br />&nbsp;&nbsp; 标准函数编程库提供了行读写函数，该类函数读取一行以换行符"/n"结束的数据，写入数据时自动输出换行符。<br />&nbsp;&nbsp; (1)行输入函数族<br />#include&lt;stdio.h&gt;<br />char *gets(char *s);<br />char *fgets(char *s,int n,FILE *stream);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数gets从标准输入流(stdin)中读取一串字符存储到参数s所指向的内存空间中，文件结束或者错误发生时返回NULL，否则将返回参数s所指向的内存地址。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数fgets中键入了防溢出控制，它从文件流stream中读取一串字符到参数s所指向的内存空间，但读取数据的长度(包括换行符"/n")不能超过 n-1,。参数n代表了字符串s的最大存储空间。倘若待读入的实际数据长度包括("/n")超过了n-1，函数将截取该n个字符返回，剩余的字符将在下一 次fgets调用时读入。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 两个函数都把读取的字符信息存入字符串s中，并且自动增加字符串结束符"0",这也是fgets一次性最多只能读入n-1个字符的原因(第n个字符需要存储结束符"0")。函数调用成功时返回参数s的值，即指向输入的字符信息，否则返回空指针NULL。<br />/*不利用fgets函数的返回值*/<br />char s[1024];<br />...<br />fgets(s,sizeof(s),STREAM);<br />puts(s);<br />.....<br />&nbsp;<br />&nbsp;<br />/*利用fgets函数的返回值*/<br />char s[1024];<br />...<br />puts(fgets(s,sizof(s),STREAM);<br />....<br />(2)行输出函数<br />&nbsp;&nbsp;&nbsp;&nbsp; 标准文件编程库中用于文件行输出的函数如下：<br />#include&lt;stdio.h&gt;<br />int puts(const char *s);<br />int fputs(const char *s,FILE *steam);<br />参数s指向一串以字符串结束符"0"结尾的字符；函数puts把该字符串(不包括结束符"0")写入到标准输出流stdout中，并自动输出换行符"/n"；函数fputs字符串s(不包括结束符"0")写入文件流stream中，但不再输出换行符"/n"。<br />&nbsp;&nbsp;&nbsp;&nbsp; 两函数都不输出字符串末的结束符，输出失败时，都返EOF。<br />&nbsp;&nbsp;&nbsp;&nbsp; (3)实例：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 解析报文文件的另一种方法是先读入一行数据，再通过字符串函数分解。本处设计了一个使用行读写解析报文的例子，程序按行读取文件"/etc /passwd"，并将"用户名称"域提取出来(报文的第一个域)单独提取出来，存入文件"copyname.txt"中，源程序如下：<br />#include&lt;memory.h&gt;<br />#include&lt;string.h&gt;<br />#include&lt;stdio.h&gt;<br />void main()<br />{<br />&nbsp; FILE *fpr, *fpw;<br />&nbsp; char buf[1024], *p1,*p2;<br />&nbsp; /*以下打开源文件*/<br />&nbsp; if((fpr=fopen("/etc/passwd","r"))=NULL)<br />&nbsp; {&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp; printf("open file /etc/pwasswd failed. /n");<br />&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }<br />&nbsp; if((fpw=fopen("./copyname.txt"))==NULL)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp; printf("open file ./copyname.txt failed./n");<br />&nbsp;&nbsp;&nbsp;&nbsp; fclose(fpr);<br />&nbsp;&nbsp;&nbsp;&nbsp; return ;&nbsp;<br />&nbsp; }&nbsp;<br />&nbsp; memset(buf,0,sizeof(buf));<br />&nbsp; while(fgets(buf,sizeof(buf),fpr)!=NULL)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp; /*p1指向第一个":",p2指向第二个":"*/<br />&nbsp;&nbsp;&nbsp; if((p1=strstr(buf,":"))==NULL)break;<br />&nbsp;&nbsp;&nbsp; if((p2=strstr(p1+1,":"))==NULL)break;<br />&nbsp;&nbsp;&nbsp; p1++;p2++;<br />&nbsp;&nbsp;&nbsp; /*p1指向第二个域密码字段，p2指向第三个域用户ID字段*/<br />&nbsp;&nbsp;&nbsp; /*以下代码移动字符串内容，将ID字段的内容移动到用户名字段后*/<br />&nbsp;&nbsp;&nbsp; while(*p2!=':')<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *p1=*p2;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p1++;p2++;<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; *p1=0;<br />&nbsp;&nbsp;&nbsp; /*屏幕输出*/<br />&nbsp;&nbsp;&nbsp; puts(buf);<br />&nbsp;&nbsp;&nbsp; /*文件输出*/<br />&nbsp;&nbsp;&nbsp; fputs(buf,fpw);<br />&nbsp;&nbsp;&nbsp; fputs("/n",fpw);<br />&nbsp;&nbsp;&nbsp; memset(buf,0,sizeof(buf));<br />&nbsp;<br />&nbsp; }<br />&nbsp; fclose(fpr);<br />&nbsp; fclose(fpw);<br />}<br />&nbsp;3)按块读写<br />&nbsp;&nbsp;&nbsp; 块读写函数，能够输入输出任何数量的字符，在操作二进制文件时常常使用，标准文件编程库中用于文件块输入输出的函数如下：<br />#include&lt;stdio.h&gt;<br />size_t fread(void *ptr,size_t size,size_t nitems,FILE *stream);<br />size_t fwrite(const void *ptr,size_t size, size_t nitems,FILE *stream);<br />&nbsp;&nbsp; 函数fread从文件流stream中读入nitems个数据项存储到指针ptr所指向的内存中，每个数据项具有size字节大小，一次操作总共读入size*nitems个字节。<br />&nbsp; 函数fwrite将ptr的数据写入到stream中，每次可写入size*nitems个字符。参数nitems表示写入文件的数据项个数，参数size表示每个数据项具有的字节大小。<br />&nbsp;注：在大多数Unix中，size_t被定义为无符号整形：<br />&nbsp;typedef unsigned int size_t;<br />&nbsp; fread&amp;fwrite都不返回实际读写的字符个数，而返回的是实际读写的数据项数。成功时，返回值等于参数nitems值，否则返回值将小于nitems值。<br />&nbsp; 块I/O函数常应用于二进制文件读写中，操作比较灵活，既可以读写一个字符，也可以读写一个结构，还可以读写任意数据类型。<br />/*读写字符*/<br />char c;<br />....<br />fread(&amp;c,sizeof(char),1,INSTREAM);<br />...<br />fwrite(&amp;c,sizeof(char),1,OUTSTREAM);<br />&nbsp;<br />&nbsp;<br />/*读写结构*/<br />typedef struct<br />{<br />&nbsp; char c;<br />&nbsp; int l;<br />}MYSTRUCT;<br />...<br />MYSTRUCT my;<br />...<br />fread(&amp;my,sizeof(MYSTRUCT),1,INSTREAM);<br />...<br />fwrite(&amp;my,sizeof(MYSTRUCT),1,OUTSTREAM);<br />&nbsp;<br />/*读写数组*/<br />double f[5];<br />...<br />fread(f,sizeof(double),5,INSTREAM);<br />...<br />fwrite(f,sizeof(double),5,OUTSTREAM);<br />实例<br />&nbsp;&nbsp;&nbsp;&nbsp; 块读写函数经常操作二进制文件，保留内存信息或永久存储数据信息等。比如在软件中涉及一个文件型<a class="keylink" href="http://www.2cto.com/database/" target="_blank">数据库</a>，用以存取历史交易明细。<br />&nbsp;&nbsp;&nbsp; 本处设计了一个存取数据信息的实例，通过文件"array.dat"存储和读取一个整形数组，其中文件起始记录数组的长度，随后是数组的数据信息。源文件如下：<br />/*数据信息存储 wdata.c*/<br />#include&lt;stdio.h&gt;<br />void main()<br />{<br />&nbsp; FILE *fp;<br />&nbsp; /*data array, readers can modify the lenght&nbsp; and the context of the array*/<br />&nbsp; int narray[5]={3132,2354,45,224,566};<br />&nbsp; int all=5;<br />&nbsp; if((fp=fopen("array.dat","wb"))==NULL);<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp; printf("open file array failed./n");<br />&nbsp;&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }<br />&nbsp; fwrite(&amp;all,sizeof(int),1,fp);<br />&nbsp; /*实际写入1*sizeof(int)=4个字符*/<br />&nbsp; fwrite(narray,sizeof(int),all,fp);<br />&nbsp; /*实际写入all*sizeof(int)个字符*/<br />&nbsp; fclose(fp);<br />&nbsp;<br />}<br />/*数组信息恢复rdata.c*/<br />include&lt;stdio.h&gt;<br />void main()<br />{<br />&nbsp; FILE *fp;<br />&nbsp; int narray[5];<br />&nbsp; int all=6,i;<br />&nbsp; if((fp=fopen("array.dat","rb"))=NULL);<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp; printf(open file array.dat failed./n);<br />&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }&nbsp;<br />&nbsp; /*读取文件中存储的第一个整数，获取存储数组的元素数*/<br />&nbsp; fread(&amp;all,sizeof(int),1,fp);<br />/*读取文件存储的数组元素*/<br />&nbsp; fread(narray,sizeof(int),1,fp);<br />&nbsp; printf("all=%d/n",all);<br />&nbsp; for(i=0;i&lt;all,i++)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp; printf("[%d]=%d",i,narray[i]);<br />&nbsp; }&nbsp;<br />&nbsp; printf("/n");<br />&nbsp; fclose(fp);<br />&nbsp;<br />}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 文件的格式化读写<br />&nbsp;&nbsp;&nbsp;&nbsp; 文件的格式化读写具有数据格式自动转换和文件流输入输出两个过程。输入时，先将文件流中的字符串转化为二进制数据，再存入内存中；输出时，先将二进制数据转化为字符串数据，再输出。<br />&nbsp;&nbsp; 文件格式化输出函数族：<br />int printf(const char *format, /*[arg,]*/ ...)<br />int fprintf(FILE *stream, const char *format, /*[arg,]*/...)<br />int sprintf(char *s, const char *format, /*[arg,]*/...)<br />sprintf输出结果到字符串s中，同时在字符串末尾自动加上字符结束符'\0'。这三个函数调用成功时返回实际输出的字符数，否则返回一个负数<br />&nbsp;&nbsp;&nbsp; 常见类型：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d,i&nbsp;&nbsp; 以十进制形式输出带符号整数<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; u&nbsp;&nbsp;&nbsp;&nbsp; 以十进制形式输出无符号整数<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f&nbsp;&nbsp;&nbsp;&nbsp; 以小数形式输出单、双精度实数<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e,E&nbsp;&nbsp; 以指数形式输出单、双精度实数<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g,G&nbsp;&nbsp; 以%f%e中较短的输出宽度输出单、双精度实数<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x,X&nbsp;&nbsp; 以十六进制形式输出无符号整数,前者输出"0123456789abcdef"，后者输出"0123456789ABCDEF"<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; o&nbsp;&nbsp;&nbsp;&nbsp; 以八进制形式输出无符号整数<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c&nbsp;&nbsp;&nbsp;&nbsp; 输出单个字符<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s&nbsp;&nbsp;&nbsp;&nbsp; 输出字符串<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p&nbsp;&nbsp;&nbsp;&nbsp; 以指针形式输出<br />&nbsp;&nbsp;&nbsp; 标志：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&nbsp;&nbsp;&nbsp;&nbsp; 左对齐输出，右边填空格<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +&nbsp;&nbsp;&nbsp;&nbsp; 带符号(正号或负号)输出<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 空格&nbsp; 输出正数时加上空格，输出负数时加上负号<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #&nbsp;&nbsp;&nbsp;&nbsp; 整型的八进制或十六进制转换时分别输出前缀"0"或"0x"；浮点数转换时，省去多余小数<br />&nbsp;&nbsp;&nbsp; 文件格式化输入函数族：<br />int scanf(const char *format, /*[pointer,]*/...)<br />int fscanf(FILE *stream, const char *format, /*[pointer,]*/...)<br />int sscanf(const char *s, const char *format, /*[pointer,]*/...)<br />&nbsp; 输入函数能自动过滤输入流中的空格、制表等符号。函数调用成功时返回读入值的参数个数，否则返回EOF。<br />3:函数的变长参数<br />&nbsp; 文件的格式化参数都支持变长参数。定义时，变长参数列表通过省略号"..."表示，因此，具有变长参数列表的函数定义格式如下：<br />&nbsp; type 函数名(参数1,参数2,参数n,...);<br />其中type为函数的返回值类型，参数1~n为定长参数，...代表变长参数，...必须定义在参数的最右端。如下例：<br />&nbsp; int printf(const char * format,...);<br />&nbsp; int mysum(...);<br />1)变长参数的使用<br />&nbsp; Unix的变长参数通过va_list对象实现，定义在文件"stdarg.h"中，变长参数的应用模板代码如下：<br />#include&lt;stdarg.h&gt;<br />function(parmN,...)<br />va_list pvar;<br />.........<br />va_start(pvar,parmN);<br />while()<br />{<br />&nbsp; ........<br />&nbsp; f=va_arg(pvar,type);<br />&nbsp; ........<br />}<br />va_end(pvar);<br />1 step. va_list pvar<br />申明va_list数据类型变量pvar，该变量访问变长参数列表中的参数<br />2 step. va_start(pvar,parmN)<br />宏va_start初始化变长参数列表。pvar是va_list型变量，在step1定义，记载列表中的参数信息。parmN是省略号"..."前的一个参数名，va_start根据此参数，判断参数列表的起始位置。<br />3 step: va_arg(pvar,type)<br />获取变长参数列表中参数的值。pvar是step定义的va_list型变量，type为参数值的类型，也是红va_arg返回数值的类型，如：<br />va_arg(pvar,int);<br />va_arg(pvar,float);<br />宏va_arg执行完毕后自动更改对象pvar，将其指向下一个参数。<br />4 step：va_end(pvar)<br />关闭本次对变长参数列表的访问。<br />设计函数mysum，计算输入参数的和并返回结果，源程序如下：<br />#include&lt;stdarg.h&gt;<br />int mysum(int i,...)<br />{<br />&nbsp; int r=0,j=0;<br />&nbsp; va_list pvar;<br />&nbsp; va_start(pvar,i);<br />&nbsp; for(j=0;j&lt;i;++j)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp; r +=va_arg(pvar,int);<br />&nbsp; }<br />&nbsp; va_end(pvar);<br />&nbsp; return(r);<br />}<br />void main()<br />{<br />&nbsp; printf("sum(1,4)=%d/n",mysum(1,4));<br />&nbsp; printf("sum(2,4,8)=%d/n",mysum(2,4,8));<br />}<br />compile &amp; run<br />$make mysum<br />&nbsp;&nbsp;&nbsp;&nbsp; cc -O -o mysum mysum.c<br />$./mysum<br />sum(1,4)=4;<br />sum(2,4,8)=12;<br />4)变长参数的传递<br />变长参数传递的函数族如下：<br />#include&lt;stdarg.h&gt;<br />int vprintf(const char *format,va_list ap);<br />int vfprintf(FILE *stream,const char *format,va_list ap);<br />int vsprintf(char *str,const char *format,va_list ap);<br />&nbsp;&nbsp;&nbsp;&nbsp; 这些函数完全等价于格式化函数，只是在形式上采用固定参数替代变长参数列表，这样描述的函数更加紧凑，这些函数长应用于变长参数内部的功能实现。<br />&nbsp;&nbsp;&nbsp; 实现：设计函数"int PrintLog(FILE *stream,const&nbsp; char *pformat,...)"，它按照字符串format的内容，控制后即参数的数量和格式，并在文件流stream中输出。源程序代码如下：<br />#include&lt;stdio.h&gt;<br />#include&lt;stdarg.h&gt;<br />int PrintLog(FILE *pfile,const char * pformat,...)<br />{<br />&nbsp; va_list _va_list;<br />&nbsp; char szbuf[1024];<br />&nbsp; if(pformat==null || pfile==null)return -1;/*判断指针是否正确*/<br />&nbsp; va_start(_va_list,pformat);/*初始化变长参数列表*/<br />&nbsp; vsprintf(szbuf,pformat,_va_list);/*传递变长参数*/<br />&nbsp; va_end(_va_list);<br />&nbsp; fputs(szbuf,pfile);<br />&nbsp; return 0;<br />}<br />&nbsp;<br />void main()<br />{<br />&nbsp; PrintLog(stderr,"[%s][%s][%d][%c]/n","This","is",5,'a');<br />}<br />4：文件读写位置的定位<br />&nbsp; 在实际应用中，我们常常只需要读取文件中的一小段内容，或者写入一小段呢日哦难怪到文件中，使用文件的读写定位功能可以避免些不必要的的数据段。例如某文 件由一系列固定大小记录块组成，当访问其第n块记录时，可以先将文件指针移动到第n块记录的起始位置，再访问1个记录快大小的数据，并不需要从文件头开始 读取n个记录块。<br />&nbsp; 标准文件变成库中用于定位文件读写为止的函数如下：<br />#include&lt;stdio.h&gt;<br />int fseek(FILE *stream,long int offset,int whence);<br />void rewind(FILE *stream);<br />long int ftell(FILE *stream);<br />其中fseek改变文件流stream中的访问位置，参数whence表示文件定位的方式，fseek中提供了三种定位方式，参数offset表明了定位的偏移量，其值可正可负，与whence一起确定访问文件的最终定位。<br />whence&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 含义&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 文件定位于<br />SEEK_SET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 从文件头开始定位&nbsp;&nbsp; 0+offset<br />SEEK_CUR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 从当前位置开始定位&nbsp; 当前位置+offset<br />SEEK_END&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 从文件尾开始定位&nbsp;&nbsp;&nbsp;&nbsp; 文件末+offset<br />rewind重置流stream，将文件流定位于文件开始处，相当于执行了以下操作：<br />void fseek(stream,0L,SEEK_SET);<br />函数ftell获取文件流的当前位置，调用成功时将该值返回，否则返回-1。<br />注：<br />&nbsp;&nbsp;&nbsp; 大多数系统中，可以通过以下代码获取文件的长度(len)<br />long len;<br />fseek(stream,0,SEEK_END);<br />len=ftell(stream);<br />实例:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 本处设计了一个文件读写定位的例子，代码如下，其中seekwrite将整数值narray写入文件第"rec*sizeof(int)"处；函数seekread从文件的第"rec*sizeof(int)"位置中读取整数值存入narray.<br />&nbsp;&nbsp;&nbsp;&nbsp; 如果定位超过了文件的最大长度且执行了写入操作，文件长度将延长到当前位置，中间部分自动补0；<br />#include&lt;stdio.h&gt;<br />void seekwrite(FILE　*fp,int narray,int rec)/*写入文件第rec个整数*/<br />{<br />&nbsp; fseek(fp,sizeof(int)*rec,SEEK_SET);<br />&nbsp; fwrite(&amp;narray,sizeof(int),1,fp);<br />}<br />&nbsp;<br />void seekread(FILE *fp,int *narray,int rec)<br />{<br />&nbsp; fseek(fp,sizeof(int)*rec,SEEK_SET);<br />&nbsp; fread(narray,sizeof(int),1,fp);<br />&nbsp;<br />}<br />&nbsp;<br />void main()<br />{<br />&nbsp; FILE *fp;<br />&nbsp; int narray[2]={100,200},narrayr[2];<br />&nbsp; if((fp=fopen("seek.dat","w+b"))==NULL)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp; printf("open seek.dat failed./n");<br />&nbsp;&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }<br />&nbsp; seekwrite(fp,narray[0],1);seekwrite(fp,narray[1],10);<br />&nbsp; seekread(fp,narrayr,1); seekread(fp,narrayr+1,10);<br />&nbsp; printf("now array[0]=%d,array[1]=%d",narrayr[0],narrayr[1]);<br />&nbsp; fclose(fp);<br />}<br />5:文件的状态<br />&nbsp; 每一个流对象内部都保持了两个指示状态：错误指示状态和文件结束状态，函数ferror和feof分别检查这两个状态，函数strerror显示错误的提示信息。<br />(1)文件的错误与结束状态<br />#include&lt;stdio.h&gt;<br />int ferror(FILE *stream);<br />int feof(FILE *stream);<br />void clearerr(FILE *stream);<br />当文件I/O发生错误时，调用ferror函数将返回非0值，否则返回0值。当文件结束时，调用feof函数返回非0值，否则返回0值，函数clearerr清除文件错误标志和EOF标志。<br />&nbsp;&nbsp; 实例：<br />&nbsp;&nbsp; 读取文件"/etc/passwd"，当文件结束时自动退出。<br />#include&lt;stdio.h&gt;<br />void main()<br />{<br />&nbsp; FILE *fp;<br />&nbsp; char buf[1024];<br />&nbsp; if((fp=fopen("/etc/passwd","r"))==NULL)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp; printf("open file /etc/passwd failed./n");<br />&nbsp;&nbsp;&nbsp; return ;<br />&nbsp; }<br />&nbsp; while(!feof(fp))<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp; fgets(buf,sizeof(buf),fp);<br />&nbsp;&nbsp;&nbsp; if(feof(fp)) break;<br />&nbsp;&nbsp;&nbsp; fputs(buf,stderr);<br />&nbsp; }<br />&nbsp; fclose(fp);<br />}<br />(2)文件的错误信息<br />&nbsp;&nbsp;&nbsp; 错误状态指示器仅能判断错误是否发生，不能明确错误的内容，标准文件编程库用于明确错误信息的函数如下：<br />&nbsp;&nbsp;&nbsp; extern int errno;<br />&nbsp;&nbsp;&nbsp; #include&lt;string.h&gt;<br />&nbsp;&nbsp;&nbsp; char *strerror(int errnum);<br />&nbsp; 其中外部变量errno代表了发生错误的代码，函数strerror获取第errnum号错误的详细信息。<br />6：文件的缓冲<br />&nbsp;&nbsp; 所谓文件写缓冲，是指文件流在执行输出操作时，并不立刻将数据写入文件，而是先把数据累计到缓冲区，再以块为单位批量输出到文件中，同理，文件读缓冲是指 文件流在执行输入操作时，以块为单位读取文件内容，多余的数据存储在内存中。如果下次读操作的内容刚好在同一块中，则可以直接返回结果，避免一次输入操 作。通过缓冲技术，可以减少低级I/O函数read和write函数的调用次数，从而大大提高软件执行效率。<br />&nbsp; 1)缓冲模式<br />&nbsp;&nbsp; 标准文件编程库采用FILE类型描述文件流，与低级I/O函数相比，最大的特性就是应用及增加了缓冲功能(低级I/O函数只使用了文件系统自带的缓冲功能)，文件的输入输出以"缓冲块"为单位批量完成，并且根据"缓冲块"大小，提供了三种缓冲模式。<br />&nbsp;&nbsp; (1)全缓冲(_IOFBF)：一般读写普通磁盘文件采用全缓冲模式。<br />&nbsp;&nbsp; (2)行缓冲(_IOLBF)：比如调用fgets函数从标准输入流stdin中输入字符，当且仅当客户输入回车换行时，函数才返回。<br />&nbsp;&nbsp; (3)无缓冲(_IONBF)：比如stderr采用无缓冲模式；<br />2)缓冲函数<br />#include&lt;stdio.h&gt;<br />void setbuf(FILE *stream,char *buf);<br />int setvbuf(FILE *stream,char *buf,int type,size_t size);<br />int fflush(FILE *stream);<br />&nbsp;setbuf设置文件流stream的缓冲区，参数buf指向一个大小为BUFSIZ的内存块，调用成功后，文件流stream使用该内存块作为新的缓冲区。倘若buf是空指针NULL，文件流stream的缓冲将被完全关闭。缓冲区内存块的定义一般为：<br />&nbsp;&nbsp; char buf[BUFSIZ]; ---其中BUFSIZ是stdio.h中的常数，代表缓冲区的大小，常为256的整数倍。<br />&nbsp;&nbsp; setvbuf设置了文件流stream的缓冲区和缓冲模式，缓冲模式由参数type确定.<br />&nbsp;&nbsp; 任何时候，都可以使用fflush刷新缓冲区，并将缓冲区的内容强制输出到文件中，参数stream指明了更新的 文件流，当其值为NULL时，系统将刷新全部文件流的缓冲区。<br />&nbsp; 实例：<br />&nbsp; 本处设计了一个缓冲显示的实例：<br />#include&lt;stdio.h&gt;<br />void main()<br />{<br />&nbsp; printf(1---1);<br />&nbsp; /*fflush(stdout);*/<br />&nbsp; fprintf(stderr,"2---2");<br />&nbsp; printf("3---3/n");<br />&nbsp; fprintf(stderr,"4--4/n");<br />}<br />7:项目：通用函数库之调试功能库封装<br />&nbsp;&nbsp; 1)调试库内容：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 格式化日志输出<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 十六进制日志输出<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 信息判断<br />&nbsp;&nbsp; 2)调试库设计<br />&nbsp;&nbsp;&nbsp;&nbsp; (1)PrintLog<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 格式化日志输出函数，其原型为：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int PrintLog(FILE *fp,const char *pformat,...);<br />/*-----debug.c-----*/<br />#include&lt;stdio.h&gt;<br />#include&lt;stdarg.h&gt;<br />#include&lt;comlib.h&gt;<br />int PrintLog(FILE *fp,const char *pfromat,...)<br />{<br />&nbsp; va_list _va_list;<br />&nbsp; TIMESTRU timestru;<br />&nbsp; char szBuf[1024];<br />&nbsp; int nLen;<br />&nbsp; if(pformat==NULL||pfile==NULL) return -1;/*判断指针是否yes*/<br />&nbsp; timestu=GetTime();<br />&nbsp; nLen=sprintf(szBuf,"%04d.%02d.%02d %02d:%02d:%02d. [%d]: ",timestru.nYear,timestru.nMon,timestru.nDay,timestru.nHour,timestru.nMin,timestru.nSec,getpid());<br />&nbsp; va_start(_va_list,pformat);<br />&nbsp; nLen+=vsprintf(szBuf+nLen,pformat,_va_list);<br />&nbsp; va_end(_va_list);<br />&nbsp; nLen+=sprintf(szBuf+nLen,"/n");<br />&nbsp; if(fputs(szBfu,pfile)!=EOF &amp;&amp; fflush(pfile)!=EOF)&nbsp;<br />&nbsp;&nbsp;&nbsp; return 0;/*输出并刷新文件流*/<br />&nbsp;<br />&nbsp; return -2;/*错误返回*/<br />}<br />&nbsp;&nbsp; (2)PrintTraceLog<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int PrintTraceLog(const char *pformat,...);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 核心代码<br />if((fp=open(TRACE_FILE),"a")!=NULL)<br />{<br />&nbsp; ret=PrintTraceLog(fp,szBuf);<br />&nbsp; fclose(fp);<br />&nbsp; return(set);<br />}<br />(3) Verify<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 信息判断函数，其原型为：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int Verify(int bStatus,const char *szBuf,const char *szFile,int nLine);<br />&nbsp; (5)调试库应用实例：<br />#include&lt;stdio.h&gt;<br />void main()<br />{<br />&nbsp; int i=10000;<br />&nbsp; PrintLog(stderr,"This is test[%d]/n");<br />&nbsp; PrintTraceLOg("This is test [%d]/n");<br />&nbsp; PrintTraceLog(----------------);<br />&nbsp; Verify(0,NULL,_FILE_,_LINE_);<br />&nbsp; PrintTraceLog("---------------");<br />&nbsp; Verify(0);<br />&nbsp; PrintTraceLog("---------------");<br />&nbsp; Verify(1);<br />&nbsp;&nbsp;<br />}<br /></p><img src ="http://www.cppblog.com/strawberry/aggbug/170271.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/strawberry/" target="_blank">futual</a> 2012-04-06 14:59 <a href="http://www.cppblog.com/strawberry/articles/170271.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux 下文件的相关知识</title><link>http://www.cppblog.com/strawberry/articles/170269.html</link><dc:creator>futual</dc:creator><author>futual</author><pubDate>Fri, 06 Apr 2012 06:57:00 GMT</pubDate><guid>http://www.cppblog.com/strawberry/articles/170269.html</guid><wfw:comment>http://www.cppblog.com/strawberry/comments/170269.html</wfw:comment><comments>http://www.cppblog.com/strawberry/articles/170269.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/strawberry/comments/commentRss/170269.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/strawberry/services/trackbacks/170269.html</trackback:ping><description><![CDATA[我们在这一节将要讨论linux下文件操作的各个函数. <br />1.文件的创建和读写 <br />2.文件的各个属性 <br />3.目录文件的操作 <br />4.管道文件 <br /><br /><br /><br /><strong>1：文件的创建和读写</strong> <br />我假设你已经知道了标准级的文件操作的各个函数(fopen,fread,fwrite等等).当然如果你不清楚的话也不要着急.我们讨论的系统级的文件操作实际上是为标准级文件操作服务的. <br />当我们需要打开一个文件进行读写操作的时候,我们可以使用系统调用函数open.使用完成以后我们调用另外一个close函数进行关闭操作. <br />#include <br />#include <br />#include <br />#include <br /><br />int open(const char *pathname,int flags); <br />int open(const char *pathname,int flags,mode_t mode); <br /><br />int close(int fd); <br /><br />open函数有两个形式.其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下面).flags可以去下面的一个值或者是几个值的组合. <br />O_RDONLY:以只读的方式打开文件. <br />O_WRONLY:以只写的方式打开文件. <br />O_RDWR:以读写的方式打开文件. <br />O_APPEND:以追加的方式打开文件. <br />O_CREAT:创建一个文件. <br />O_EXEC:如果使用了O_CREAT而且文件已经存在,就会发生一个错误. <br />O_NOBLOCK:以非阻塞的方式打开一个文件. <br />O_TRUNC:如果文件已经存在,则删除文件的内容. <br />前面三个标志只能使用任意的一个.如果使用了O_CREATE标志,那么我们要使用open的第二种形式.还要指定mode标志,用来表示文件的访问权限.mode可以是以下情况的组合. <br />----------------------------------------------------------------- <br />S_IRUSR 用户可以读 S_IWUSR 用户可以写 <br />S_IXUSR 用户可以执行 S_IRWXU 用户可以读写执行 <br />----------------------------------------------------------------- <br />S_IRGRP 组可以读 S_IWGRP 组可以写 <br />S_IXGRP 组可以执行 S_IRWXG 组可以读写执行 <br />----------------------------------------------------------------- <br />S_IROTH 其他人可以读 S_IWOTH 其他人可以写 <br />S_IXOTH 其他人可以执行 S_IRWXO 其他人可以读写执行 <br />----------------------------------------------------------------- <br />S_ISUID 设置用户执行ID S_ISGID 设置组的执行ID <br />----------------------------------------------------------------- <br />我们也可以用数字来代表各个位的标志.Linux总共用5个数字来表示文件的各种权限. <br />00000.第一位表示设置用户ID.第二位表示设置组ID,第三位表示用户自己的权限位,第四位表示组的权限,最后一位表示其他人的权限. <br />每个数字可以取1(执行权限),2(写权限),4(读权限),0(什么也没有)或者是这几个值的和. <br />比如我们要创建一个用户读写执行,组没有权限,其他人读执行的文件.设置用户ID位那么我们可以使用的模式是--1(设置用户ID)0(组没有设置)7(1+2+4)0(没有权限,使用缺省)5(1+4)即10705: <br />open("temp",O_CREAT,10705); <br />如果我们打开文件成功,open会返回一个文件描述符.我们以后对文件的所有操作就可以对这个文件描述符进行操作了. <br />当我们操作完成以后,我们要关闭文件了,只要调用close就可以了,其中fd是我们要关闭的文件描述符. <br />文件打开了以后,我们就要对文件进行读写了.我们可以调用函数read和write进行文件的读写. <br />#include <br /><br />ssize_t read(int fd, void *buffer,size_t count); <br />ssize_t write(int fd, const void *buffer,size_t count); <br /><br />fd是我们要进行读写操作的文件描述符,buffer是我们要写入文件内容或读出文件内容的内存地址.count是我们要读写的字节数. <br />对于普通的文件read从指定的文件(fd)中读取count字节到buffer缓冲区中(记住我们必须提供一个足够大的缓冲区),同时返回count. <br />如果read读到了文件的结尾或者被一个信号所中断,返回值会小于count.如果是由信号中断引起返回,而且没有返回数据,read会返回-1,且设置errno为EINTR.当程序读到了文件结尾的时候,read会返回0. <br />write从buffer中写count字节到文件fd中,成功时返回实际所写的字节数. <br />下面我们学习一个实例,这个实例用来拷贝文件. <br /><br />#include <br />#include <br />#include <br />#include <br />#include <br />#include <br />#include <br /><br />#define BUFFER_SIZE 1024 <br /><br />int main(int argc,char **argv) <br />{ <br /><br />int from_fd,to_fd; <br />int bytes_read,bytes_write; <br />char buffer[BUFFER_SIZE]; <br />char *ptr; <br /><br />if(argc!=3) <br />{ <br />fprintf(stderr,"Usage:%s fromfile tofile\n\a",argv[0]); <br />exit(1); <br />} <br /><br />/* 打开源文件 */ <br /><br />if((from_fd=open(argv[1],O_RDONLY))==-1) <br />{ <br />fprintf(stderr,"Open %s Error:%s\n",argv[1],strerror(errno)); <br />exit(1); <br />} <br /><br />/* 创建目的文件 */ <br /><br />if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1) <br />{ <br />fprintf(stderr,"Open %s Error:%s\n",argv[2],strerror(errno)); <br />exit(1); <br />} <br /><br />/* 以下代码是一个经典的拷贝文件的代码 */ <br /><br />while(bytes_read=read(from_fd,buffer,BUFFER_SIZE)) <br />{ <br />/* 一个致命的错误发生了 */ <br />if((bytes_read==-1)&amp;&amp;(errno!=EINTR)) break; <br />else if(bytes_read&gt;0) <br />{ <br />ptr=buffer; <br />while(bytes_write=write(to_fd,ptr,bytes_read)) <br />{ <br />/* 一个致命错误发生了 */ <br />if((bytes_write==-1)&amp;&amp;(errno!=EINTR))break; <br />/* 写完了所有读的字节 */ <br />else if(bytes_write==bytes_read) break; <br />/* 只写了一部分,继续写 */ <br />else if(bytes_write&gt;0) <br />{ <br />ptr+=bytes_write; <br />bytes_read-=bytes_write; <br />} <br />} <br />/* 写的时候发生的致命错误 */ <br />if(bytes_write==-1)break; <br /><br />} <br />} <br />close(from_fd); <br />close(to_fd); <br />exit(0); <br />} <br /><br /><br /><strong>2：文件的各个属性</strong> <br />文件具有各种各样的属性,除了我们上面所知道的文件权限以外,文件还有创建时间,大小等等属性. <br />有时侯我们要判断文件是否可以进行某种操作(读,写等等).这个时候我们可以使用access函数. <br />#include <br /><br />int access(const char *pathname,int mode); <br /><br />pathname:是文件名称,mode是我们要判断的属性.可以取以下值或者是他们的组合. <br />R_OK文件可以读,W_OK文件可以写,X_OK文件可以执行,F_OK文件存在.当我们测试成功时,函数返回0,否则如果有一个条件不符时,返回-1. <br />如果我们要获得文件的其他属性,我们可以使用函数stat或者fstat. <br />#include <br />#include <br /><br />int stat(const char *file_name,struct stat *buf); <br />int fstat(int filedes,struct stat *buf); <br /><br />struct stat { <br />dev_t st_dev; /* 设备 */ <br />ino_t st_ino; /* 节点 */ <br />mode_t st_mode; /* 模式 */ <br />nlink_t st_nlink; /* 硬连接 */ <br />uid_t st_uid; /* 用户ID */ <br />gid_t st_gid; /* 组ID */ <br />dev_t st_rdev; /* 设备类型 */ <br />off_t st_off; /* 文件字节数 */ <br />unsigned long st_blksize; /* 块大小 */ <br />unsigned long st_blocks; /* 块数 */ <br />time_t st_atime; /* 最后一次访问时间 */ <br />time_t st_mtime; /* 最后一次修改时间 */ <br />time_t st_ctime; /* 最后一次改变时间(指属性) */ <br />}; <br /><br />stat用来判断没有打开的文件,而fstat用来判断打开的文件.我们使用最多的属性是st_mode.通过着属性我们可以判断给定的文件是一个普通文件还是一个目录,连接等等.可以使用下面几个宏来判断. <br />S_ISLNK(st_mode):是否是一个连接.S_ISREG是否是一个常规文件.S_ISDIR是否是一个目录S_ISCHR是否是一个字符设备.S_ISBLK是否是一个块设备S_ISFIFO是否 是一个FIFO文件.S_ISSOCK是否是一个SOCKET文件. 我们会在下面说明如何使用这几个宏的. <br /><br /><br /><strong>3：目录文件的操作</strong> <br />在我们编写程序的时候，有时候会要得到我们当前的工作路径。C库函数提供了getcwd来解决这个问题。 <br />#include <br /><br />char *getcwd(char *buffer,size_t size); <br /><br />我们提供一个size大小的buffer,getcwd会把我们当前的路径考到buffer中.如果buffer太小,函数会返回-1和一个错误号. <br />Linux提供了大量的目录操作函数,我们学习几个比较简单和常用的函数. <br />#include <br />#include <br />#include <br />#include <br />#include <br /><br />int mkdir(const char *path,mode_t mode); <br />DIR *opendir(const char *path); <br />struct dirent *readdir(DIR *dir); <br />void rewinddir(DIR *dir); <br />off_t telldir(DIR *dir); <br />void seekdir(DIR *dir,off_t off); <br />int closedir(DIR *dir); <br /><br />struct dirent { <br />long d_ino; <br />off_t d_off; <br />unsigned short d_reclen; <br />char d_name[NAME_MAX+1]; /* 文件名称 */ <br /><br />mkdir很容易就是我们创建一个目录,opendir打开一个目录为以后读做准备.readdir读一个打开的目录.rewinddir是用来重读目录的和我们学的rewind函数一样.closedir是关闭一个目录.telldir和seekdir类似与ftee和fseek函数. <br />下面我们开发一个小程序,这个程序有一个参数.如果这个参数是一个文件名,我们输出这个文件的大小和最后修改的时间,如果是一个目录我们输出这个目录下所有文件的大小和修改时间. <br /><br />#include <br />#include <br />#include <br />#include <br />#include <br />#include <br />#include <br /><br />static int get_file_size_time(const char *filename) <br />{ <br />struct stat statbuf; <br /><br />if(stat(filename,&amp;statbuf)==-1) <br />{ <br />printf("Get stat on %s Error:%s\n", <br />filename,strerror(errno)); <br />return(-1); <br />} <br /><br />if(S_ISDIR(statbuf.st_mode))return(1); <br />if(S_ISREG(statbuf.st_mode)) <br />printf("%s size:%ld bytes\tmodified at %s", <br />filename,statbuf.st_size,ctime(&amp;statbuf.st_mtime)); <br /><br />return(0); <br />} <br /><br />int main(int argc,char **argv) <br />{ <br />DIR *dirp; <br />struct dirent *direntp; <br />int stats; <br /><br />if(argc!=2) <br />{ <br />printf("Usage:%s filename\n\a",argv[0]); <br />exit(1); <br />} <br /><br />if(((stats=get_file_size_time(argv[1]))==0)||(stats==-1))exit(1); <br /><br />if((dirp=opendir(argv[1]))==NULL) <br />{ <br />printf("Open Directory %s Error:%s\n", <br />argv[1],strerror(errno)); <br />exit(1); <br />} <br /><br />while((direntp=readdir(dirp))!=NULL) <br />if(get_file_size_time(direntp-br&gt; closedir(dirp); <br />exit(1); <br />} <br /><br /><br /><strong>4：管道文件</strong> <br />Linux提供了许多的过滤和重定向程序,比如more cat <br />等等.还提供了&lt; &gt; | &lt;&lt;等等重定向操作符.在这些过滤和重 定向程序当中,都用到了管道这种特殊的文件.系统调用pipe可以创建一个管道. <br />#include <br /><br />int pipe(int fildes[2]); <br /><br />pipe调用可以创建一个管道(通信缓冲区).当调用成功时,我们可以访问文件描述符fildes[0],fildes[1].其中fildes[0]是用来读的文件描述符,而fildes[1]是用来写的文件描述符. <br />在实际使用中我们是通过创建一个子进程,然后一个进程写,一个进程读来使用的. <br />关于进程通信的详细情况请查看进程通信 <br /><br />#include <br />#include <br />#include <br />#include <br />#include <br />#include <br />#include <br />#define BUFFER 255 <br /><br />int main(int argc,char **argv) <br />{ <br />char buffer[BUFFER+1]; <br />int fd[2]; <br /><br />if(argc!=2) <br />{ <br />fprintf(stderr,"Usage:%s string\n\a",argv[0]); <br />exit(1); <br />} <br /><br />if(pipe(fd)!=0) <br />{ <br />fprintf(stderr,"Pipe Error:%s\n\a",strerror(errno)); <br />exit(1); <br />} <br />if(fork()==0) <br />{ <br />close(fd[0]); <br />printf("Child[%d] Write to pipe\n\a",getpid()); <br />snprintf(buffer,BUFFER,"%s",argv[1]); <br />write(fd[1],buffer,strlen(buffer)); <br />printf("Child[%d] Quit\n\a",getpid()); <br />exit(0); <br />} <br />else <br />{ <br />close(fd[1]); <br />printf("Parent[%d] Read from pipe\n\a",getpid()); <br />memset(buffer,'\0',BUFFER+1); <br />read(fd[0],buffer,BUFFER); <br />printf("Parent[%d] Read:%s\n",getpid(),buffer); <br />exit(1); <br />} <br />} <br /><br />为了实现重定向操作,我们需要调用另外一个函数dup2. <br />#include <br /><br />int dup2(int oldfd,int newfd); <br /><br />dup2将用oldfd文件描述符来代替newfd文件描述符,同时关闭newfd文件描述符.也就是说, <br />所有向newfd操作都转到oldfd上面.下面我们学习一个例子,这个例子将标准输出重定向到一个文件. <br /><br />#include <br />#include <br />#include <br />#include <br />#include <br />#include <br />#include <br /><br />#define BUFFER_SIZE 1024 <br /><br />int main(int argc,char **argv) <br />{ <br />int fd; <br />char buffer[BUFFER_SIZE]; <br /><br />if(argc!=2) <br />{ <br />fprintf(stderr,"Usage:%s outfilename\n\a",argv[0]); <br />exit(1); <br />} <br /><br />if((fd=open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR))==-1) <br />{ <br />fprintf(stderr,"Open %s Error:%s\n\a",argv[1],strerror(errno)); <br />exit(1); <br />} <br /><br />if(dup2(fd,STDOUT_FILENO)==-1) <br />{ <br />fprintf(stderr,"Redirect Standard Out Error:%s\n\a",strerror(errno)); <br />exit(1); <br />} <br /><br />fprintf(stderr,"Now,please input string"); <br />fprintf(stderr,"(To quit use CTRL+D)\n"); <br />while(1) <br />{ <br />fgets(buffer,BUFFER_SIZE,stdin); <br />if(feof(stdin))break; <br />write(STDOUT_FILENO,buffer,strlen(buffer)); <br />} <br />exit(0); <br />} <br /><br /><img src ="http://www.cppblog.com/strawberry/aggbug/170269.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/strawberry/" target="_blank">futual</a> 2012-04-06 14:57 <a href="http://www.cppblog.com/strawberry/articles/170269.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux应用程序编程：用管道进行编程</title><link>http://www.cppblog.com/strawberry/articles/169559.html</link><dc:creator>futual</dc:creator><author>futual</author><pubDate>Fri, 30 Mar 2012 12:06:00 GMT</pubDate><guid>http://www.cppblog.com/strawberry/articles/169559.html</guid><wfw:comment>http://www.cppblog.com/strawberry/comments/169559.html</wfw:comment><comments>http://www.cppblog.com/strawberry/articles/169559.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/strawberry/comments/commentRss/169559.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/strawberry/services/trackbacks/169559.html</trackback:ping><description><![CDATA[<p style="text-indent: 2em"><strong>管道模型</strong> 
<p style="text-indent: 2em">
<p style="text-indent: 2em">一个形象化管道的描述为&#8212;&#8212;一个在两个实体之间的单向连接器。例如，让我们来看一看下面的这个GNU/Linux命令： 
<p style="text-indent: 2em">
<p style="text-indent: 2em">ls -1 | wc &#8211;l 
<p style="text-indent: 2em">
<p style="text-indent: 2em">这个命令创建了两个进程，一个和ls －l关联而另一个则和wc －l关联。接着它通过设置第二个进程的标准输入到第一个进程的标准输出连接了这两个进程（如图11.1）。产生的结果是&#8212;&#8212;计算了当前子目录的文件数目。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">我们的命令设置了一个在两个GNU/Linux命令之间的管道。命令ls被执行之后它产生的输出被用作第二个命令wc（word count）的输入。这是一个单向管道&#8212;&#8212;通信发生在一个方向上。两个命令之间的连接由GNU/Linux来完成。我们也可以在应用程序当中做到这一点（等一下我们将会证明这一点）。 
<p style="text-indent: 2em">
<p style="text-indent: 2em"><strong>匿名管道和有名管道</strong> 
<p style="text-indent: 2em">
<p style="text-indent: 2em">一个匿名管道或者说单向管道，为一个进程提供了和它的一个子进程（匿名的种类）进行通信的方法。这是因为没有可以在操作系统当中找到一个匿名进程的方法。它的最通常的用法是在父进程建立一个匿名管道，然后将这个管道传递给它的子进程，然后它们就可以进行通信了。注意，如果需要双向通信的话，我们考虑使用的API就应该是套接字（sockets）API了。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">管道的另一种类型是有名管道。一个有名管道其功能和匿名管道差不多，差别就在于它是可以在文件系统中存在的并且所有进程都可以找到它。这意味着没有血缘关系的进程之间可以使用它来进行通信。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">在接下来的部分当中我们将会同时学习有名管道和匿名管道。我们将对管道进行一个快速的游览，然后我们就详细地学习管道API以及支持管道编程的GNU/Linux系统级的命令。 
<p style="text-indent: 2em">旋风式的游览 
<p style="text-indent: 2em">
<p style="text-indent: 2em">让我们以一个简单的管道编程的模型的例子来开始我们的旋风式游览。在这个简单的例子当中，我们在一个进程当中创建了一个管道，然后对它写入一个消息，接下来的就是从这个管道当中读取前面写入的消息，然后将它显示出来。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">清单11.1: 一个简单的管道例子 
<p style="text-indent: 2em">
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>1:#include &lt;unistd.h&gt;
2:#include &lt;stdio.h&gt;
3:#include &lt;string.h&gt;
4:  
5:#define MAX_LINE        80
6:#define PIPE_STDIN      0
7:       #define PIPE_STDOUT     1
8:  
9:       int main()
10:       {
11:         const char *string={"A sample message."};
12:         int ret, myPipe[2];
13:         char buffer[MAX_LINE+1];
14:  
15:         /* Create the pipe */
16:         ret = pipe( myPipe );
17:  
18:         if (ret == 0) {
19:  
20:           /* Write the message into the pipe */
21:           write( myPipe[PIPE_STDOUT], string, strlen(string) );
22:  
23:           /* Read the message from the pipe */
24:           ret = read( myPipe[PIPE_STDIN], buffer, MAX_LINE );
25:  
26:           /* Null terminate the string */
27:           buffer[ ret ] = 0;
28:  
29:           printf("%s\n", buffer);
30:  
31:         }
32:  
33:         return 0;
34:       }</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">
<p style="text-indent: 2em">在清单11.1当中，我们在16行当中使用了函数pipe创建了我们的管道。我们向函数pipe传入了一个拥有两个元素的代表我们的管道的整型数组。管道被定义为一对分开的文件描述符&#8212;&#8212;一个输入和一个输出。我们可以从管道的一端写入然后从管道的另一端读取。如果管道成功建立的话，API函数pipe就会返回0。基于返回，myPipe将会包括两个新的代表管道的输入（myPipe[1]）和对管道的输出（myPipe[0]）的文件描述符。（PS： in文件描述符代表其所关联的文件或者设备相当于一个输入设备；out文件描述符则代表其所关联的文件或者设备相当于一个输出设备。） 
<p style="text-indent: 2em">
<p style="text-indent: 2em">在21行，我们使用函数write将我们的消息写入管道。我们指定了stdout（标准输出）描述符（这是从应用程序的角度来看的，而非管道）。管道现在包含了我们的消息，然后它能够在24行被函数read读取。在这里我们再次说明一下，从应用程序的角度来看，我们使用了stdin（标准输入）描述符来从管道进行读取。函数read将从管道当中读取到的消息保存到了变量buffer。为了让buffer真正地成为一个字符串，我们在其后添加了一个0 （NULL），然后我们就可以在29行使用函数printf显示它了。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">虽然这个例子很有趣，但是我们自己之间的通信的执行可以使用任意数量的机制。接下来我们将要学习提供了进程（不论是有关系的还是无关系的）间通信的更为复杂的例子。 
<p style="text-indent: 2em">
<p style="text-indent: 2em"><strong>详细的回顾</strong> 
<p style="text-indent: 2em">
<p style="text-indent: 2em">虽然管道模型的绝大部分为函数pipe，但是还有两个应该对它们的基于管道编程的可用性进行讨论的其它函数。表格11.1列举了我们在本章当中要详细地讨论的函数： 
<p style="text-indent: 2em">
<p style="text-indent: 2em"><strong>API函数 用处 </strong>
<p style="text-indent: 2em">
<p style="text-indent: 2em">pipe 创建一个匿名管道 
<p style="text-indent: 2em">dup 创建一个文件描述符的拷贝 
<p style="text-indent: 2em">mkfifo 创建一个有名管道（先进先出） 
<p style="text-indent: 2em">同时我们还将会学习一些可用于管道通信的其它函数，特别是那些能够使用管道进行通信的函数。 
<p style="text-indent: 2em">注意：我们要记住一个管道不过是一对文件描述符而已，因此任何能够在文件描述符上进行操作的函数都可以使用管道。这就包括了select、read、write、fcntl以及freopen等等，但并不是仅仅限于这些函数。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">pipe 
<p style="text-indent: 2em">
<p style="text-indent: 2em">API函数pipe创建了一个新的由一个包含两个文件描述符的数组所代表的管道。函数pipe的原型如下： 
<p style="text-indent: 2em">#include <unistd.h>
<p style="text-indent: 2em">int pipe( int fds[2] ); 
<p style="text-indent: 2em">
<p style="text-indent: 2em">如果函数pipe的操作成功的话，它将会返回0；反之则会返回-1，同时适当地设置了errno。如若成功返回，数组fds（它是按引用传递值）将会拥有两个激活的文件描述符。数组的第一个元素是一个能够被应用程序读取的文件描述符；而第二个则是一个能够被应用程序写入的文件描述符。 
<p style="text-indent: 2em">现在让我们来看一下一个应用管道于多进程应用程序的稍微更为复杂一点的例子。在这个应用程序，当中,我们将在14行当中创建一个管道，然后在16行使用函数fork在程序的父进程当中创建一个新的进程（子进程）。在子进程当中，我们尝试从我们的管道的输入文件描述符当中进行读取（18 行），但是如果没有什么可以读取的话，我们就将这个子进程挂起。当进行读取操作之后，我们就通过一个NULL将字符串终止（就是让一个数组字符成为一个字符串），然后打印我们所读取的字符串。父进程只是简单地通过使用输出（写）文件描述符（管道结构的数组的第二个元素）将一个测试字符串写入管道，然后使用函数wait等待子进程退出。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">注意：除了我们的子进程继承了父进程使用函数pipe创建的的文件描述符然后使用它们来和另一个子进程或者和父进程来通信，关于这个程序不再没有什么可以引人注目的了。回想一下：一旦函数fork的操作完成，我们的进程是独立的（除了子进程继承了父进程的特征，比如管道描述符）。由于内存是分开的， pipe方法为我们提供了一个有趣的进程间通信的模型。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">清单11.2: 举例说明两个进程间的管道模型 
<p style="text-indent: 2em">
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>1:       #include &lt;stdio.h&gt;
     2:       #include &lt;unistd.h&gt;
     3:       #include &lt;string.h&gt;
     4:       #include &lt;wait.h&gt;
     5:      
     6:       #define MAX_LINE        80
     7:  
     8:       int main()
     9:       {
     10:         int thePipe[2], ret;
     11:         char buf[MAX_LINE+1];
     12:         const char *testbuf={"a test string."};
     13:      
     14:         if ( pipe( thePipe ) == 0 ) {
     15:      
     16:           if (fork() == 0) {
     17:      
     18:             ret = read( thePipe[0], buf, MAX_LINE );
     19:             buf[ret] = 0;
     20:             printf( "Child read %s\n", buf );
     21:      
     22:           } else {
     23:      
     24:             ret = write( thePipe[1], testbuf, strlen(testbuf) );
     25:             ret = wait( NULL );
     26:      
     27:           }
     28:      
     29:         }
     30:      
     31:         return 0;
     32:       }</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">
<p style="text-indent: 2em">注意：在这些简单的程序当中我们并没有讨论管道的关闭，这是因为一个进程一旦结束，和管道相关联的资源将会被自动地释放。虽然如此，调用函数close来关闭管道的描述符是一个十分良好的编程做法，比如： 
<p style="text-indent: 2em">
<p style="text-indent: 2em">ret = pipe( myPipe ); 
<p style="text-indent: 2em">... 
<p style="text-indent: 2em">close( myPipe[0] ); 
<p style="text-indent: 2em">close( myPipe[1] ); 
<p style="text-indent: 2em">
<p style="text-indent: 2em">如果管道的写入端被关闭，而一个进程想要从管道当中进行读取的话，一个0将会被返回。这意味着管道不再被使用并且应该把它关闭。如果管道的读取端被关闭，而一个进程想要对管道写入的话，一个信号将会产生。这个信号（将会在&#8220;第十二章&#8212;&#8212;套接字编程的简介&#8221;当中讨论）被叫做SIGPIPE。要对管道进行写入操作的应用程序通常都包含一个捕获这么一个状况的信号句柄。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">dup和dup2 
<p style="text-indent: 2em">
<p style="text-indent: 2em">函数dup和dup2能够复制一个文件描述符的十分有用的函数。它们经常被用来重定向一个进程的stdin、stdout或者stderr。函数dup和dup2的原型如下： 
<p style="text-indent: 2em">
<p style="text-indent: 2em">#include <unistd.h>
<p style="text-indent: 2em">int dup( int oldfd ); 
<p style="text-indent: 2em">int dup2( int oldfd, int targetfd ); 
<p style="text-indent: 2em">
<p style="text-indent: 2em">函数dup允许我们复制一个描述符。我们向函数传递一个已经存在了的描述符，然后它返回了一个和前面那个一模一样的新的描述符。这意味着两个描述符共享着相同的结构。例如，我们用一个文件描述符执行了一个lseek（在文件当中查找），文件的当前位置（文件的内部指针）在第二个当中也是一样的。函数 dup的使用如下面的代码片段所示： 
<p style="text-indent: 2em">
<p style="text-indent: 2em">int fd1, fd2; 
<p style="text-indent: 2em">... 
<p style="text-indent: 2em">fd2 = dup( fd1 ); 
<p style="text-indent: 2em">注意：在fork的调用之前创建一个描述符产生的效果和调用函数dup是一样的。子进程接收了一个复制的描述符，就好像它模仿了dup的调用一样。 
<p style="text-indent: 2em">函数dup2和dup类似，但是它允许调用者指定一个激活的描述符已经目标描述符的id。在函数dup2成功返回之后，新的目标描述符复制了第一个描述符（targetfd ＝oldfd）。让我们来看一下下面的举例说明dup2的用法的代码片段： 
<p style="text-indent: 2em">
<p style="text-indent: 2em">int oldfd; 
<p style="text-indent: 2em">oldfd = open("app_log", (O_RDWR | O_CREATE), 0644 ); 
<p style="text-indent: 2em">dup2( oldfd, 1 ); 
<p style="text-indent: 2em">close( oldfd ); 
<p style="text-indent: 2em">
<p style="text-indent: 2em">在这个例子当中，我们打开了一个叫做&#8220;app_log&#8221;的新的文件并且接收到了一个叫做fd1的文件描述符。我们通过使用oldfd和1调用了 dup2，就这样我们用oldfd（我们新打开的文件）替换了文件描述符1（stdout标准输出）。任何写入标准输出stdout的数据现在将会被写入一个叫做&#8220;app_log&#8221;的文件当中。注意我们在复制了oldfd之后就关闭了它。然而这并没有关闭了我们新打开的文件，这是因为文件描述符1现在就是它&#8212;&#8212;我们可以这么认为。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">现在让我们来学习一个更为复杂的例子。回想本章的前面部分我们研究的让ls &#8211;l的输出成为wc &#8211;l的输入。我们现在通过一个C应用程序来探讨允许这个例子（清单11.3）。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">在清单11.3的开始部分，我们在9行创建了我们的管道然后在13-16行创建了程序的子进程接下来在20-23行创建了父进程。我们在13行关闭了 stdout（标准输出）描述符。子进程在这里提供了ls &#8211;l的功能并且不会写入stdout当中去相反它被写入了我们的管道（用了dup来作重定向）。在14行，我们使用了dup2来重定向stdout到我们的管道（pfds[1]）当中去。一旦这样的一个操作完成之后，我们关闭了我们的管道的输入端（因为它永远不会被使用）。最后，我们使用了函数 execlp来将我们的子进程的镜像替换为命令ls &#8211;l的镜像。一旦这个命令被执行了之后，任何产生的输出将会被传送到输入当中去。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">现在让我们来看一看，管道的接收端。父进程充当了这么一个角色并遵循了一个相似的模式。我们首先在20行关闭了stdin（标准输入），这是因为我们不会从它那里接收任何数据。接下来，我们再次使用函数dup2（21行）来使得stdin成为管道的输出端。这是通过使文件描述符0（一般来说为 stdin）在功能上变得和pfds[0]一样来完成的。我们关闭了管道的stdout标准输出端（pfds[1]），这是因为在这里我们不会使用到它（22行）。最后，我们使用函数execlp来执行将管道当中的内容当成它的输入的命令wc -1（23行）。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">清单11.3: 流水线命令C程序 
<p style="text-indent: 2em">
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>1:       #include &lt;stdio.h&gt;
     2:       #include &lt;stdlib.h&gt;
     3:       #include &lt;unistd.h&gt;
     4:      
     5:       int main()
     6:       {
     7:         int pfds[2];
     8:      
     9:         if ( pipe(pfds) == 0 ) {
     10:      
     11:           if ( fork() == 0 ) {
     12:      
     13:             close(1);
     14:             dup2( pfds[1], 1 );
     15:             close( pfds[0] );
     16:             execlp( "ls", "ls", "-1", NULL );
     17:      
     18:           } else {
     19:      
     20:             close(0);
     21:             dup2( pfds[0], 0 );
     22:             close( pfds[1] );
     23:             execlp( "wc", "wc", "-l", NULL );
     24:      
     25:           }
     26:      
     27:         }
     28:      
     29:         return 0;
     30:       }</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">
<p style="text-indent: 2em">在这个程序当中有一点十分重要并值得我们记下来&#8212;&#8212;这就是我们的子进程把它自己的输出重定向到管道的输入上，而父进程则将它自己的输入重定向到管道的输出&#8212;&#8212;这是一个值得我们去记的十分有用的技术。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">mkfifo 
<p style="text-indent: 2em">
<p style="text-indent: 2em">函数mkfifo被用来创建一个在文件系统当中的提供FIFO（先进先出）的功能的文件（也可以称之为有名管道）。到目前为止我们讨论的都是匿名管道。它们是专用于一个父进程和它的子进程之间的通信。有名管道在文件系统当中是可见的，因此可以被任何进程使用。函数mkfifo原型如下： 
<p style="text-indent: 2em">#include <sys types.h>
<p style="text-indent: 2em">#include <sys stat.h>
<p style="text-indent: 2em">int mkfifo( const char *pathname, mode_t mode ); 
<p style="text-indent: 2em">命令mkfifo需要两个参数。第一个（pathname）是要被创建在文件系统当中的一个特殊文件。第二个（mode）指定了FIFO的读写权限。成功的话命令mkfifo将会返回0，失败的话将会返回-1（同时errno将会被适当地写入）。让我们来看一下一个使用函数mkfifo创建了一个 fifo的例子： 
<p style="text-indent: 2em">
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>int ret;
...
ret = mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );
if (ret == 0) {
// Named pipe successfully created
} else {
// Failed to create named pipe
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">在这个例子当中，我们使用文件cmd_pipe在子目录/tmp当中创建了一个fifo（有名管道）。接下来我们就打开了这个文件来进行读写，通过它我们就可以进行通信了。一旦我们打开了一个有名管道，我们可以使用典型的I/O命令进行读写了。例如，下面是一个使用fgets从管道当中进行读取的代码段： 
<p style="text-indent: 2em">pfp = fopen( "/tmp/cmd_pipe", "r" ); 
<p style="text-indent: 2em">... 
<p style="text-indent: 2em">ret = fgets( buffer, MAX_LINE, pfp ); 
<p style="text-indent: 2em">
<p style="text-indent: 2em">我们可以使用下面的代码段来为上面的代码对管道进行写入操作： 
<p style="text-indent: 2em">pfp = fopen( "/tmp/cmd_pipe", "w+ ); 
<p style="text-indent: 2em">... 
<p style="text-indent: 2em">ret = fprintf( pfp, "Here&#8217;s a test string!\n" ); 
<p style="text-indent: 2em">
<p style="text-indent: 2em">关于有名管道的使人感兴趣的地方是它们在被看做是集合点的地方工作，我们将会在对系统命令mkfifo的讨论当中对这一点进行探讨。一个读取者是不能够打开一个有名管道的，除非一个写入者主动地打开管道的另一端。读取者将会在调用open函数时被阻塞直到一个写入者出现。尽管有这样的一个限制，有名管道仍然是进程间通信的一个有用的机制。 
<p style="text-indent: 2em">
<p style="text-indent: 2em"><strong>系统命令</strong> 
<p style="text-indent: 2em">
<p style="text-indent: 2em">让我们来看一下一个和用于IPC的管道模型相关的系统命令。命令mkfifo，就和API函数mkfifo一样，允许我们在命令行上创建一个有名管道。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">mkfifo 
<p style="text-indent: 2em">
<p style="text-indent: 2em">命令mkfifo是在命令行上创建有名管道（fifo特殊文件）的两个方法当中的一个。命令mkfifo的一般用法如下： 
<p style="text-indent: 2em">mkfifo [options] name 
<p style="text-indent: 2em">
<p style="text-indent: 2em">在这里，选项（options）-m代表mode（权限）而name则是要创建的有名管道的名字（如果需要的话还要包含路径）。如果没有指定权限，默认的是0644。下面是一个例子，它在目录/tmp当中创建了一个叫做cmd_pipe的有名管道： 
<p style="text-indent: 2em">$ mkfifo /tmp/cmd_pipe 
<p style="text-indent: 2em">
<p style="text-indent: 2em">我们可以简单地通过使用选项-m调整选项。下面是一个将权限设置为0644的例子（但是我们必须首先删除掉原先的那一个）： 
<p style="text-indent: 2em">$ rm cmd_pipe 
<p style="text-indent: 2em">$ mkfifo -m 0644 /tmp/cmd_pipe 
<p style="text-indent: 2em">
<p style="text-indent: 2em">一旦权限被创建完毕，我们可以通过这个管道来在命令行上进行通信。在另一个终端上，我们使用命令echo向有名管道cmd_pipe写入： 
<p style="text-indent: 2em">
<p style="text-indent: 2em">$ echo Hi &gt; cmd_pipe 
<p style="text-indent: 2em">
<p style="text-indent: 2em">当这个命令结束的时候，我们的读取者将会被唤醒并结束（下面清楚地完成了读取者的命令序列）： 
<p style="text-indent: 2em">$ cat cmd_pipe 
<p style="text-indent: 2em">Hi 
<p style="text-indent: 2em">$ 
<p style="text-indent: 2em">以上举例说明了有名管道不仅可以用于C程序当中还可以用在脚本当中。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">有名管道的创建还可以使用命令mknod（紧随其后的特殊文件可以是很多其它类型的文件）。下面我们使用命令mknod创建了一个有名管道； 
<p style="text-indent: 2em">$ mknod cmd_pipe p 
<p style="text-indent: 2em">在这里，有名管道cmd_pipe被创建在当前子目录当中（p是有名管道的类型）。 
<p style="text-indent: 2em">
<p style="text-indent: 2em"><strong>总结</strong> 
<p style="text-indent: 2em">
<p style="text-indent: 2em">在这一章当中，我们对有名管道和匿名管道作了一次旋风式的游览。我们回顾了创建管道的程序方法和命令行方法，同时还回顾了使用它们来进行通信的典型的 I/O机制。我们还回顾了如何使用函数dup和dup2来进行I/O的重定向。虽然管道十分有用，在其它某一特定情节当中这些命令或者函数依然十分地有用（无论在上面地方一个文件描述符被使用，比如一个套接字或者文件）。 
<p style="text-indent: 2em">
<p style="text-indent: 2em">管道编程API 
<p style="text-indent: 2em">#include <unistd.h>
<p style="text-indent: 2em">int pipe( int filedes[2] ); 
<p style="text-indent: 2em">int dup( int oldfd ); 
<p style="text-indent: 2em">int dup2( int oldfd, int targetfd ); 
<p style="text-indent: 2em">int mkfifo( const char *pathname, mode_t mode ); 
<p style="text-indent: 2em">
<p style="text-indent: 2em">（</p><img src ="http://www.cppblog.com/strawberry/aggbug/169559.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/strawberry/" target="_blank">futual</a> 2012-03-30 20:06 <a href="http://www.cppblog.com/strawberry/articles/169559.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux 函数--fstat/stat/lstat系统调用</title><link>http://www.cppblog.com/strawberry/articles/169141.html</link><dc:creator>futual</dc:creator><author>futual</author><pubDate>Tue, 27 Mar 2012 05:51:00 GMT</pubDate><guid>http://www.cppblog.com/strawberry/articles/169141.html</guid><wfw:comment>http://www.cppblog.com/strawberry/comments/169141.html</wfw:comment><comments>http://www.cppblog.com/strawberry/articles/169141.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/strawberry/comments/commentRss/169141.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/strawberry/services/trackbacks/169141.html</trackback:ping><description><![CDATA[<p><span style="font-size: 12px">表头文件:&nbsp;&nbsp;&nbsp; #include &lt;sys/stat.h&gt;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #include &lt;unistd.h&gt;&nbsp;<br />定义函数:&nbsp;&nbsp;&nbsp; int stat(const char *file_name, struct stat *buf);&nbsp;<br /><br />函数说明:&nbsp;&nbsp;&nbsp; 通过文件名filename获取文件信息，并保存在buf所指的结构体stat中&nbsp;<br />返回值:&nbsp;&nbsp;&nbsp;&nbsp; 执行成功则返回0，失败返回-1，错误代码存于errno&nbsp;<br />错误代码:&nbsp;<br />&nbsp;&nbsp;&nbsp; ENOENT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 参数file_name指定的文件不存在&nbsp;<br />&nbsp;&nbsp;&nbsp; ENOTDIR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 路径中的目录存在但却非真正的目录&nbsp;<br />&nbsp;&nbsp;&nbsp; ELOOP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 欲打开的文件有过多符号连接问题，上限为16符号连接&nbsp;<br />&nbsp;&nbsp;&nbsp; EFAULT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 参数buf为无效指针，指向无法存在的内存空间&nbsp;<br />&nbsp;&nbsp;&nbsp; EACCESS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 存取文件时被拒绝&nbsp;<br />&nbsp;&nbsp;&nbsp; ENOMEM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 核心内存不足&nbsp;<br />&nbsp;&nbsp;&nbsp; ENAMETOOLONG&nbsp;&nbsp; 参数file_name的路径名称太长&nbsp;<br />#include &lt;sys/stat.h&gt;&nbsp;<br />#include &lt;unistd.h&gt;&nbsp;<br />#include &lt;stdio.h&gt;&nbsp;<br />int main() {&nbsp;<br />&nbsp;&nbsp;&nbsp; struct stat buf;&nbsp;<br />&nbsp;&nbsp;&nbsp; stat("/etc/hosts", &amp;buf);&nbsp;<br />&nbsp;&nbsp;&nbsp; printf("/etc/hosts file size = %d\n", buf.st_size);&nbsp;<br />}&nbsp;<br />-----------------------------------------------------&nbsp;<br />struct stat {&nbsp;<br />&nbsp;&nbsp;&nbsp; dev_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_dev;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件的设备编号&nbsp;<br />&nbsp;&nbsp;&nbsp; ino_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_ino;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //节点&nbsp;<br />&nbsp;&nbsp;&nbsp; mode_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_mode;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件的类型和存取的权限&nbsp;<br />&nbsp;&nbsp;&nbsp; nlink_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_nlink;&nbsp;&nbsp;&nbsp;&nbsp; //连到该文件的硬连接数目，刚建立的文件值为1&nbsp;<br />&nbsp;&nbsp;&nbsp; uid_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_uid;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //用户ID&nbsp;<br />&nbsp;&nbsp;&nbsp; gid_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_gid;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //组ID&nbsp;<br />&nbsp;&nbsp;&nbsp; dev_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_rdev;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //(设备类型)若此文件为设备文件，则为其设备编号&nbsp;<br />&nbsp;&nbsp;&nbsp; off_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_size;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件字节数(文件大小)&nbsp;<br />&nbsp;&nbsp;&nbsp; unsigned long st_blksize;&nbsp;&nbsp; //块大小(文件系统的I/O 缓冲区大小)&nbsp;<br />&nbsp;&nbsp;&nbsp; unsigned long st_blocks;&nbsp;&nbsp;&nbsp; //块数&nbsp;<br />&nbsp;&nbsp;&nbsp; time_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_atime;&nbsp;&nbsp;&nbsp;&nbsp; //最后一次访问时间&nbsp;<br />&nbsp;&nbsp;&nbsp; time_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_mtime;&nbsp;&nbsp;&nbsp;&nbsp; //最后一次修改时间&nbsp;<br />&nbsp;&nbsp;&nbsp; time_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_ctime;&nbsp;&nbsp;&nbsp;&nbsp; //最后一次改变时间(指属性)&nbsp;<br />};&nbsp;<br />先前所描述的st_mode 则定义了下列数种情况：&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IFMT&nbsp;&nbsp; 0170000&nbsp;&nbsp;&nbsp; 文件类型的位遮罩&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IFSOCK 0140000&nbsp;&nbsp;&nbsp; scoket&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IFLNK 0120000&nbsp;&nbsp;&nbsp;&nbsp; 符号连接&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IFREG 0100000&nbsp;&nbsp;&nbsp;&nbsp; 一般文件&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IFBLK 0060000&nbsp;&nbsp;&nbsp;&nbsp; 区块装置&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IFDIR 0040000&nbsp;&nbsp;&nbsp;&nbsp; 目录&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IFCHR 0020000&nbsp;&nbsp;&nbsp;&nbsp; 字符装置&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IFIFO 0010000&nbsp;&nbsp;&nbsp;&nbsp; 先进先出&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISUID 04000&nbsp;&nbsp;&nbsp;&nbsp; 文件的(set user-id on execution)位&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISGID 02000&nbsp;&nbsp;&nbsp;&nbsp; 文件的(set group-id on execution)位&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISVTX 01000&nbsp;&nbsp;&nbsp;&nbsp; 文件的sticky位&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IRUSR(S_IREAD) 00400&nbsp;&nbsp;&nbsp;&nbsp; 文件所有者具可读取权限&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IWUSR(S_IWRITE)00200&nbsp;&nbsp;&nbsp;&nbsp; 文件所有者具可写入权限&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IXUSR(S_IEXEC) 00100&nbsp;&nbsp;&nbsp;&nbsp; 文件所有者具可执行权限&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IRGRP 00040&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 用户组具可读取权限&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IWGRP 00020&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 用户组具可写入权限&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IXGRP 00010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 用户组具可执行权限&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IROTH 00004&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其他用户具可读取权限&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IWOTH 00002&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其他用户具可写入权限&nbsp;<br />&nbsp;&nbsp;&nbsp; S_IXOTH 00001&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其他用户具可执行权限&nbsp;<br />&nbsp;&nbsp;&nbsp; 上述的文件类型在POSIX中定义了检查这些类型的宏定义：&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISLNK (st_mode)&nbsp;&nbsp;&nbsp; 判断是否为符号连接&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISREG (st_mode)&nbsp;&nbsp;&nbsp; 是否为一般文件&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISDIR (st_mode)&nbsp;&nbsp;&nbsp; 是否为目录&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISCHR (st_mode)&nbsp;&nbsp;&nbsp; 是否为字符装置文件&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISBLK (s3e)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 是否为先进先出&nbsp;<br />&nbsp;&nbsp;&nbsp; S_ISSOCK (st_mode)&nbsp;&nbsp; 是否为socket&nbsp;<br />&nbsp;&nbsp;&nbsp; 若一目录具有sticky位(S_ISVTX)，则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名。&nbsp;<br />-----------------------------------------------------&nbsp;<br />struct statfs {&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_type;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件系统类型&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_bsize;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //块大小&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_blocks;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //块多少&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_bfree;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //空闲的块&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_bavail;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //可用块&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_files;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //总文件节点&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_ffree;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //空闲文件节点&nbsp;<br />&nbsp;&nbsp;&nbsp; fsid_t f_fsid;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件系统id&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_namelen;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件名的最大长度&nbsp;<br />&nbsp;&nbsp;&nbsp; long&nbsp;&nbsp;&nbsp; f_spare[6];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //spare for later&nbsp;<br />};&nbsp;<br />stat、fstat和lstat函数(UNIX)&nbsp;<br />#include&lt;sys/types.h&gt;&nbsp;<br />#include&lt;sys/stat.h&gt;&nbsp;<br />int stat(const char *restrict pathname, struct stat *restrict buf);&nbsp;<br />提供文件名字，获取文件对应属性。感觉一般是文件没有打开的时候这样操作。&nbsp;<br />int fstat(int filedes, struct stat *buf);&nbsp;<br />通过文件描述符获取文件对应的属性。文件打开后这样操作&nbsp;<br />int lstat(const char *restrict pathname, struct stat *restrict buf);&nbsp;<br />连接文件&nbsp;<br />三个函数的返回：若成功则为0，若出错则为-1&nbsp;<br />给定一个pathname，stat函数返回一个与此命名文件有关的信息结构，fstat函数获得已在描述符filedes上打开的文件的有关信息。lstat函数类似于stat，但是当命名的文件是一个符号连接时，lstat返回该符号连接的有关信息，而不是由该符号连接引用的文件的信息。&nbsp;<br />第二个参数是个指针，它指向一个我们应提供的结构。这些函数填写由buf指向的结构。该结构的实际定义可能随实现而有所不同，但其基本形式是：&nbsp;<br />struct stat{&nbsp;<br />mode_t st_mode;&nbsp;&nbsp; /*file tpye &amp;mode (permissions)*/&nbsp;<br />ino_t st_ino;&nbsp;&nbsp;&nbsp;&nbsp; /*i=node number (serial number)*/&nbsp;<br />dev_t st_rdev;&nbsp;&nbsp; /*device number for special files*/&nbsp;<br />nlink_t st_nlink; /*number of links*/&nbsp;<br />uid_t&nbsp;&nbsp;&nbsp; st_uid; /*user id of owner*/&nbsp;<br />gid_t&nbsp;&nbsp;&nbsp; st_gid; /*group ID of owner*/&nbsp;<br />off_t&nbsp;&nbsp; st_size; /*size in bytes for regular files*/&nbsp;<br />time_t st_atime; /*time of last access*/&nbsp;<br />time_t st_mtime; /*time of last modification*/&nbsp;<br />time_t st_ctime; /*time of last file status change*/&nbsp;<br />long st_blksize; /*best I/O block size */&nbsp;<br />long st_blocks; /*number of 512-byte blocks allocated*/&nbsp;<br />};&nbsp;<br />&nbsp;&nbsp; 注意，除最后两个以外，其他各成员都为基本系统数据类型。我们将说明此结构的每个成员以了解文件属性。&nbsp;<br />&nbsp;&nbsp;&nbsp;<br />使用stat函数最多的可能是ls-l命令，用其可以获得有关一个文件的所有信息。&nbsp;<br />1 函数都是获取文件（普通文件，目录，管道，socket，字符，块（）的属性。&nbsp;<br />函数原型&nbsp;<br />#include &lt;sys/stat.h&gt;&nbsp;<br />int stat(const char *restrict pathname, struct stat *restrict buf);&nbsp;<br />提供文件名字，获取文件对应属性。&nbsp;<br />int fstat(int filedes, struct stat *buf);&nbsp;<br />通过文件描述符获取文件对应的属性。&nbsp;<br />int lstat(const char *restrict pathname, struct stat *restrict buf);&nbsp;<br />连接文件描述命，获取文件属性。&nbsp;<br />2 文件对应的属性&nbsp;<br />struct stat {&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mode_t&nbsp;&nbsp;&nbsp;&nbsp; st_mode;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件对应的模式，文件，目录等&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ino_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_ino;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //inode节点号&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dev_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_dev;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //设备号码&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dev_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_rdev;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //特殊设备号码&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nlink_t&nbsp;&nbsp;&nbsp; st_nlink;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件的连接数&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uid_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_uid;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件所有者&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gid_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_gid;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件所有者对应的组&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; off_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; st_size;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //普通文件，对应的文件字节数&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time_t&nbsp;&nbsp;&nbsp;&nbsp; st_atime;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件最后被访问的时间&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time_t&nbsp;&nbsp;&nbsp;&nbsp; st_mtime;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件内容最后被修改的时间&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time_t&nbsp;&nbsp;&nbsp;&nbsp; st_ctime;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //文件状态改变时间&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; blksize_t st_blksize;&nbsp;&nbsp;&nbsp; //文件内容对应的块大小&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; blkcnt_t&nbsp;&nbsp; st_blocks;&nbsp;&nbsp;&nbsp;&nbsp; //伟建内容对应的块数量&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };&nbsp;<br />可以通过上面提供的函数，返回一个结构体，保存着文件的信息。</span></p><!--内容关联投票--><img src ="http://www.cppblog.com/strawberry/aggbug/169141.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/strawberry/" target="_blank">futual</a> 2012-03-27 13:51 <a href="http://www.cppblog.com/strawberry/articles/169141.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux  下read（），write（），open（）等函数详解</title><link>http://www.cppblog.com/strawberry/articles/169055.html</link><dc:creator>futual</dc:creator><author>futual</author><pubDate>Mon, 26 Mar 2012 13:43:00 GMT</pubDate><guid>http://www.cppblog.com/strawberry/articles/169055.html</guid><wfw:comment>http://www.cppblog.com/strawberry/comments/169055.html</wfw:comment><comments>http://www.cppblog.com/strawberry/articles/169055.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/strawberry/comments/commentRss/169055.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/strawberry/services/trackbacks/169055.html</trackback:ping><description><![CDATA[<div class="top fc03 bdwb bdc0 bds2 clearfix" id="$_divTopLink"><span class="ilft iblock icn0 icn0-620">&nbsp;</span> <font size="4"><strong>open():</strong></font></div>
<div class="mcnt ztag">
<div class="nbw-bitm bdwb bds2 bdc0 ">
<div class="bct fc05 fc11 nbw-blog ztag js-fs2" __1332769240906__="ev_5164634815">
<p><font size="3">#include&lt;fcntl.h&gt; <br />#include&lt;types.h&gt;<br />#include&lt;sys/stat.h&gt;/*此头文件里面定义了mode标志*/<br /><br />int open(const char *path, int oflags);<br /><br />int open(const char *path, int oflags, mode_t mode);<br />函数描述：open建立了一条到文件或设备的访问路径。如果操作成功，它将返回一个文件描述符，如果失败，返回－1。<br /><br />此函数说明：第一个参数表示：路径名或者文件名。路径名为绝对路径名，文件则是在当前工作目录下的。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 第二个参数表示：打开文件所采取的动作。<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可能值：必须指定下面某一种:O_RDONLY(只读),O_WRONLY（只写）,O_RDWR（可读可写）<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;可选用值（常用）：O_APPEND(写入的数据追加在文件末尾),O_TRUNC(文件长度设置为零，丢弃已有内容),O_CREAT(如果访问的文件不存在，则根据mode值创建文件),O_EXCL(与O_CREAT一起使用。open是一个原子操作，它只执行一个函数调用。使用这个模式可以防止两个程序同时创建一个文件，如果文件已经存在，open调用失败)<br />&nbsp;&nbsp; &nbsp;&nbsp; 第三个参数表示：设置文件访问权限的初始值。（与用户掩码umask变量有关，实际的访问权限有mode &amp; ~umask确定）<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;S_IRUSR,S_IWUSER,S_IXUSR,S_IRGRP,S_IWGRP,S_IXGRP,S_IROTH,S_IWOTH,S_IXOTH.其中I：，R：读，W：写，X：执行，USR：文件所属的用户，GRP：文件所属的组，OTH：其他用户。<br /><br />注：第三个参数是在第二个参数中有O_CREAT时才用作用。若没有，则第三个参数可以忽略。<br /><br />open函数与fopen函数的差别：creat open,close,read,write,lseek等系统调用是使用底层文件描述符来标识文件，而文件描述符特定的存在于unix/linux中，不方便移植。fopen，fclose,fread,fseek属于c语言I/O标准库中的函数。编写跨平台程序时，用标准库函数比较方便移植。 </font></p>
<p><font size="3"></font>&nbsp;</p>
<p><font size="4"><strong>read()_write():</strong></font></p>
<p style="text-indent: 2em"><br />read<br />函数从打开的设备或文件中读取数据。</p><pre>#include &lt;unistd.h&gt; ssize_t read(int fd, void *buf, size_t count); 返回值：成功返回读取的字节数，出错返回-1并设置errno，如果在调read之前已到达文件末尾，则这次read返回0</pre>
<p style="text-indent: 2em">&nbsp;<wbr></p>
<p style="text-indent: 2em">参数<br />count<br />是请求读取的字节数，读上来的数据保存在缓冲区<br />buf<br />中，同时文件的当前读写位置向后移。注意这个读写位置和使用C标准I/O库时的读写位置有可能不同，这个读写位置是记在内核中的，而使用C标准I/O库时的读写位置是用户空间I/O缓冲区中的位置。比如用<br />fgetc<br />读一个字节，<br />fgetc<br />有可能从内核中预读1024个字节到I/O缓冲区中，再返回第一个字节，这时该文件在内核中记录的读写位置是1024，而在<br />FILE<br />结构体中记录的读写位置是1。注意返回值类型是<br />ssize_t<br />，表示有符号的<br />size_t<br />，这样既可以返回正的字节数、0（表示到达文件末尾）也可以返回负值-1（表示出错）。<br />read<br />函数返回时，返回值说明了<br />buf<br />中前多少个字节是刚读上来的。有些情况下，实际读到的字节数（返回值）会小于请求读的字节数<br />count<br />，例如：</p>
<p><br />&nbsp;</p>
<p style="text-indent: 2em">读常规文件时，在读到<br />count<br />个字节之前已到达文件末尾。例如，距文件末尾还有30个字节而请求读100个字节，则<br />read<br />返回30，下次<br />read<br />将返回0。</p>
<p><br />&nbsp;</p>
<p style="text-indent: 2em">从终端设备读，通常以行为单位，读到换行符就返回了。</p>
<p><br />&nbsp;</p>
<p style="text-indent: 2em">从网络读，根据不同的传输层协议和内核缓存机制，返回值可能小于请求的字节数，后面socket编程部分会详细讲解。</p>
<p><br />&nbsp;</p>
<p style="text-indent: 2em"><br />write<br />函数向打开的设备或文件中写数据。</p><pre>#include &lt;unistd.h&gt; ssize_t write(int fd, const void *buf, size_t count); 返回值：成功返回写入的字节数，出错返回-1并设置errno</pre>
<p style="text-indent: 2em">写常规文件时，<br />write<br />的返回值通常等于请求写的字节数<br />count<br />，而向终端设备或网络写则不一定。</p>
<p style="text-indent: 2em">读常规文件是不会阻塞的，不管读多少字节，<br />read<br />一定会在有限的时间内返回。从终端设备或网络读则不一定，如果从终端输入的数据没有换行符，调用<br />read<br />读终端设备就会阻塞，如果网络上没有接收到数据包，调用<br />read<br />从网络读就会阻塞，至于会阻塞多长时间也是不确定的，如果一直没有数据到达就一直阻塞在那里。同样，写常规文件是不会阻塞的，而向终端设备或网络写则不一定。</p>
<p style="text-indent: 2em">现在明确一下阻塞（Block）<a rel="nofollow"></a>这个概念。当进程调用一个阻塞的系统函数时，该进程被置于睡眠（Sleep）<a rel="nofollow"></a>状态，这时内核调度其它进程运行，直到该进程等待的事件发生了（比如网络上接收到数据包，或者调用<br />sleep<br />指定的睡眠时间到了）它才有可能继续运行。与睡眠状态相对的是运行（Running）<a rel="nofollow"></a>状态，在Linux内核中，处于运行状态的进程分为两种情况：</p>
<p><br />&nbsp;</p>
<p style="text-indent: 2em">正在被调度执行。CPU处于该进程的上下文环境中，程序计数器（<br />eip<br />）里保存着该进程的指令地址，通用寄存器里保存着该进程运算过程的中间结果，正在执行该进程的指令，正在读写该进程的地址空间。</p>
<p><br />&nbsp;</p>
<p style="text-indent: 2em">就绪状态。该进程不需要等待什么事件发生，随时都可以执行，但CPU暂时还在执行另一个进程，所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程，那么该调度谁执行呢？内核的调度算法是基于优先级和时间片的，而且会根据每个进程的运行情况动态调整它的优先级和时间片，让每个进程都能比较公平地得到机会执行，同时要兼顾用户体验，不能让和用户交互的进程响应太慢。</p>
<p>&nbsp;</p>
<p><strong><font size="4">close():</font></strong></p>
<p>&nbsp;close函数（关闭文件）<br />此系统调用功能，恰好运行与open调用相反的操作，即告诉系统关闭一些不再使用的文件。<br />它的使用格式如下：<br />retval = close(handle) ；<br />如果调用成功，则系统返回0给变量retval，否则返回-1，也可直接写成：<br />close(handle) ；<br />不过，使用这种方式关闭文件会造成无法从系统的返回值retval判定系统是否成功地关闭了这个文件。<br /></p>
<p><font size="4"><strong>程序应用：</strong></font></p>
<p>/*write.c*/<br />#include &lt;unistd.h&gt;<br />#include &lt;sys/types.h&gt;<br />#include &lt;sys/stat.h&gt;<br />#include &lt;fcntl.h&gt;<br />#include &lt;stdlib.h&gt;<br />#include &lt;stdio.h&gt;<br />#include &lt;string.h&gt;<br />#define MAXSIZE&nbsp; 128<br />int main(void)<br />{<br />&nbsp;int i,fd,size,len;<br />&nbsp;char *buf="Hello! I'm writing to this file!";<br />&nbsp;char buf_r[10];<br />&nbsp;<br />&nbsp;len = strlen(buf);<br />&nbsp;buf_r[10] = '\0';<br />&nbsp;if((fd = open("/mnt/hgfs/Source/hello.c", O_CREAT | O_TRUNC | O_RDWR,0666 ))&lt;0)<br />&nbsp;{<br />&nbsp;&nbsp;perror("open:");<br />&nbsp;&nbsp;exit(1);<br />&nbsp;}<br />&nbsp;else<br />&nbsp;&nbsp;printf("open file:hello.c %d\n",fd);</p>
<p>&nbsp;if((size = write( fd, buf, len)) &lt; 0){<br />&nbsp;&nbsp;perror("write:");<br />&nbsp;&nbsp;exit(1);<br />&nbsp;}<br />&nbsp;else<br />&nbsp;&nbsp;printf("Write:%s\n",buf);</p>
<p>&nbsp;&nbsp; //&nbsp;&nbsp;&nbsp; lseek( fd, 0, SEEK_SET );&nbsp; //即把文件指针移至buf文件的开始处<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lseek( fd, -10, SEEK_END);&nbsp; //即把文件指针往前移动10个字符<br />&nbsp;if((size = read( fd, buf_r, 10))&lt;0)<br />&nbsp;{ <br />&nbsp;&nbsp;perror("read:");<br />&nbsp;&nbsp;exit(1);<br />&nbsp;}<br />&nbsp;else<br />&nbsp;&nbsp;printf("read form file:%s\n",buf_r);<br />&nbsp;<br />&nbsp;if( close(fd) &lt; 0 )<br />&nbsp;{<br />&nbsp;&nbsp;perror("close:");<br />&nbsp;&nbsp;exit(1);<br />&nbsp;}<br />&nbsp;else<br />&nbsp;&nbsp;printf("Close hello.c\n");<br />&nbsp;<br />&nbsp;exit(0);<br />}</p>
<p><br /><br />&nbsp;</p></div></div></div><img src ="http://www.cppblog.com/strawberry/aggbug/169055.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/strawberry/" target="_blank">futual</a> 2012-03-26 21:43 <a href="http://www.cppblog.com/strawberry/articles/169055.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux samba 服务器 相关问题</title><link>http://www.cppblog.com/strawberry/articles/168871.html</link><dc:creator>futual</dc:creator><author>futual</author><pubDate>Sun, 25 Mar 2012 01:28:00 GMT</pubDate><guid>http://www.cppblog.com/strawberry/articles/168871.html</guid><wfw:comment>http://www.cppblog.com/strawberry/comments/168871.html</wfw:comment><comments>http://www.cppblog.com/strawberry/articles/168871.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/strawberry/comments/commentRss/168871.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/strawberry/services/trackbacks/168871.html</trackback:ping><description><![CDATA[<div class="brief bgF8F8F8">
<div class="brieftext">
<p class="f14 green">Linux Samba服务的主配置文件：/etc/Linux Samba/smb.confLinux Samba服务的密码文件：/etc/Linux Samba/smbpasswdLinux Samba服务的日志文件：/var/log/Linux Samba/*.log Linux Samba共享文件夹的权限是靠Linux Samba配置文件和liunux系统的文件夹权限共同控制的。配置的时候一定要注意。</p>
<p class="ad">AD： </p></div></div>
<div class="tag bgF8F8F8" id="indexlist" style="display: none">
<ul id="indexliststr"></ul></div>
<div class="content bgF8F8F8 f14">
<div id="content">
<p><strong>一．检查是否安装Linux Samba客户端和服务器端：</strong> </p>
<p>1. 检查：</p><pre><ol class="dp-xml"><li class="alt"><span>[root@localhost&nbsp;~]#&nbsp;rpm&nbsp;-qa|grep&nbsp;Linux&nbsp;Samba &nbsp;</span></li><li class=""><span>Linux&nbsp;Samba-3.0.23c-2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-----服务器端 &nbsp;</span></li><li class="alt"><span>Linux&nbsp;Samba-client-3.0.23c-2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-----客户端端 &nbsp;</span></li><li class=""><span>Linux&nbsp;Samba-common-3.0.23c-2&nbsp;&nbsp;</span></li></ol></pre>
<p>RHEL5默认只安装了客户端。</p>
<p>2. 安装：</p>
<p>可以在安装盘的第二张光盘Server下找到服务器端安装:rpm -ivh /mnt/Server/Linux Samba-3.0.23c-2.i386.rpm 然后启动Linux Samba服务：输入 ntsysv 在界面中空格选中smb 服务，确认离开。</p>
<p>3.关闭selinux：</p>
<p>SELinux(Security-Enhanced Linux) 是美国国家安全局（NAS）对于强制访问控制的实现，是 Linux&amp;reg; 上的新安全子系统。NSA是在Linux社区的帮助下开发了一种访问控制体系，在这种访问控制体系的限制下，进程只能访问那些在他的任务中所需要文件。SELinux 默认安装在 Fedora 和 Red Hat Enterprise Linux 上。 </p>
<p>如果打了SELinux补丁，则Linux Samba的共享文件夹就不能访问，我们需要关闭SELinux：直接修改/etc/sysconfig/selinux selinux=disable 在/usr/sbin中setsebool -P Linux Samba_enable_home_dirs=1重启系统生效。</p>
<p><strong>二．Linux Samba基础：</strong></p>
<p>Linux Samba服务的主配置文件：/etc/Linux Samba/smb.confLinux Samba服务的密码文件：/etc/Linux Samba/smbpasswdLinux Samba服务的日志文件：/var/log/Linux Samba/*.log Linux Samba共享文件夹的权限是靠Linux Samba配置文件和liunux系统的文件夹权限共同控制的。配置的时候一定要注意。</p>
<p><strong>三．使用Linux Samba共享Linux文件夹</strong></p>
<p>介绍对Linux Samba的三种典型配置方法pub - 不需要密码，且可读写及删除文件。read-only - 不需要密码，但只可以读取文件。user1 - 需要密码，可读写及删除文件。</p>
<p>步骤如下：</p>
<p>1. 首先以root身分登录进入系统</p>
<p>2. 建立系统用户</p>
<p>新增三个linux系统用户，分别是pub，read-only，user1。可以使用shell命令，也可以使用图形界面，系统&#8212;&gt;管理&#8212;&gt;用户和组群完成这一步。三个用户对应主目录分别是</p><pre><ol class="dp-xml"><li class="alt"><span>/home/pub/ &nbsp;</span></li><li class=""><span>/home/&nbsp;read-only&nbsp;/ &nbsp;</span></li><li class="alt"><span>/home/&nbsp;user1/&nbsp;</span></li></ol></pre>
<p>分别赋权限：</p><pre><ol class="dp-xml"><li class="alt"><span>chmod&nbsp;777&nbsp;pub &nbsp;</span></li><li class=""><span>chmod&nbsp;755&nbsp;read-only &nbsp;</span></li><li class="alt"><span>chmod&nbsp;700&nbsp;user1&nbsp;</span></li></ol></pre>
<p>3. 建立三个Linux Samba用户对应系统用户</p><pre><ol class="dp-xml"><li class="alt"><span>smbpasswd&nbsp;&#8211;a&nbsp;pub &nbsp;</span></li><li class=""><span>smbpasswd&nbsp;&#8211;a&nbsp;read-only &nbsp;</span></li><li class="alt"><span>smbpasswd&nbsp;&#8211;a&nbsp;user1&nbsp;</span></li></ol></pre>
<p>系统会提示指定Linux Samba用户密码，密码可自己指定完成后，打开/etc/Linux Samba/smbpasswd文件，可以看到新增的Linux Samba用户</p>
<p>4. 配置主配置文件：/etc/Linux Samba/smb.con</p>
<p>找到[global]节点，修改安全性：security = share&nbsp;&nbsp;&nbsp; ----注意去掉前面的注释&#8216;;&#8217;然后在结尾处增加：</p><pre><ol class="dp-xml"><li class="alt"><span>[pub] &nbsp;</span></li><li class=""><span></span><span class="attribute"><font color="#ff0000">comment</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">Public</font></span><span>&nbsp;Areas &nbsp;</span></span></li><li class="alt"><span></span><span class="attribute"><font color="#ff0000">path</font></span><span>&nbsp;=&nbsp;/home/pub &nbsp;</span></span></li><li class=""><span></span><span class="attribute"><font color="#ff0000">browseable</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">yes</font></span><span>&nbsp;</span></span></li><li class="alt"><span>guest&nbsp;</span><span class="attribute"><font color="#ff0000">ok</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">yes</font></span><span>&nbsp;</span></span></li><li class=""><span></span><span class="attribute"><font color="#ff0000">writable</font></span><span>&nbsp;=</span><span class="attribute-value"><font color="#0000ff">yes</font></span><span>&nbsp;</span></span></li><li class="alt"><span>[read-only] &nbsp;</span></li><li class=""><span></span><span class="attribute"><font color="#ff0000">comment</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">Read</font></span><span>-Only&nbsp;Areas &nbsp;</span></span></li><li class="alt"><span></span><span class="attribute"><font color="#ff0000">path</font></span><span>&nbsp;=&nbsp;/home/read-only &nbsp;</span></span></li><li class=""><span></span><span class="attribute"><font color="#ff0000">browseable</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">yes</font></span><span>&nbsp;</span></span></li><li class="alt"><span>guest&nbsp;</span><span class="attribute"><font color="#ff0000">ok</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">yes</font></span><span>&nbsp;</span></span></li><li class=""><span>[user1] &nbsp;</span></li><li class="alt"><span></span><span class="attribute"><font color="#ff0000">comment</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">Password</font></span><span>&nbsp;Required &nbsp;</span></span></li><li class=""><span></span><span class="attribute"><font color="#ff0000">path</font></span><span>&nbsp;=&nbsp;/home/user1 &nbsp;</span></span></li><li class="alt"><span></span><span class="attribute"><font color="#ff0000">browseable</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">yes</font></span><span>&nbsp;</span></span></li><li class=""><span></span><span class="attribute"><font color="#ff0000">writable</font></span><span>&nbsp;=&nbsp;</span><span class="attribute-value"><font color="#0000ff">yes</font></span><span>&nbsp;</span></span></li></ol></pre>
<p>保存退出</p>
<p>5. 重行运行Linux Samba：终端运行命令 service smb restart </p>
<p><strong>四．访问共享文件夹：</strong></p>
<p>在Windows文件浏览器中，我们要通过\\IP或域名\共享目录名查看，而在Linux的Gnome文件浏览器中查看的方式是 smb://域名或ip地址/共享目录名。</p>
<p>本例中的三个目录，在windows中访问：pub不需要要用户名密码，有读写权限；read-only不需要用户名密码，有只读权限；user1需要密码验证（因为security = share，所以不需要验证用户名），输入步骤三中第3条设定的Linux Samba用户的users1密码即可读写访问。</p>
<p>如果在/etc/Linux Samba/smb.conf 中设为 security = user ，则共享文件夹都需要用户名和密码的验证。</p></div></div><img src ="http://www.cppblog.com/strawberry/aggbug/168871.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/strawberry/" target="_blank">futual</a> 2012-03-25 09:28 <a href="http://www.cppblog.com/strawberry/articles/168871.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>