用C语言编写Linux实用程序的艺术(转)

news/2024/7/7 16:52:21
用C语言编写Linux实用程序的艺术(转)[@more@]  Linux 和其他类 UNIX 系统总是附带了大量的工具,它们执行从显而易见的到不可思议的广泛功能。类 UNIX 编程环境的成功很大程度上归功于工具的高品质和选择,以及这些工具之间相互衔接的简易性。
  
  作为开发人员,您可能会发现现有实用程序并不总是能够解决问题。虽然能够通过结合使用现有实用程序来容易地解决许多问题,然而解决其他问题却至少需要一些实 际的编程工作。这些后面的任务通常是创建新实用程序的候选任务,结合现有实用程序来创建新实用程序可以通过做最少的工作来解决问题。本文考察优秀实用程序所具有的品质,以及设计这种实用程序所经历的过程。
  
   优秀的实用程序具有哪些品质?
  Kernighan & Pike 所著的 The UNIX Programming Environment 一书中包含了对此问题的精彩讨论。优秀的实用程序是把自己的工作做得尽可能好的实用程序。它必须与其他实用程序配合融洽;必须能够容易地与其他实用程序结合使用。无法与其他实用程序结合使用的程序不是实用程序,而是应用程序。
  
  实用程序应该允许您根据手边的材料廉价而容易地构建一次性的应用程序。许多人认为实用程序就像是工具箱中的工具。设计实用程序的目标不是为了让单个工具来做所有事情,而是为了拥有一组工具,其中每个工具都尽可能好地做一件事情。
  
  有些实用程序自身就是相当有用的,而其他实用程序则必须与一系列实用程序配合使用。前者的例子包括 sort 和 grep。另一方面,xargs 除了与其他实用程序(最常见的是 find)配合使用外,很少单独使用。
  
   使用什么语言来编写实用程序?
  大多数 UNIX 系统实用程序都是用 C 语言来编写的。本文中的例子使用 Perl 和 sh。应该使用恰当的工具来做恰当的事情。如果您对某个实用程序使用得足够频繁,那么用编译型语言来编写它的成本也许能通过性能提升来获得回报。另一方面,对于程序的工作负荷很轻这种相当普遍的情况,使用脚本语言也许会提供更快的开发速度。
  
  如果无法肯定,您应该使用自己最了解的语言。至少当您在对某个实用程序进行原型化,或在弄清它是如何有用时,程序员效率将优先于性能调整。大多数 UNIX 系统实用程序都是用 C 编写的,这只是因为这些实用程序使用得足够频繁,以致考虑效率比考虑开发成本更加重要。Perl 和 sh(或 ksh)可能是用于快速原型化的很好语言。对于与其他程序配合实用的实用程序,使用 shell 来编写它们或许要比使用更传统的编程语言来编写它们要容易一些。另一方面,当您希望与原始的字节交互时,C 或许就是最好的选择。
  
   设计实用程序
  一个不错的经验法则就是当您第二次必须解决某个问题时,首先考虑实用程序的设计。不要对第一次编写的一次性作品感到遗憾;您可以将它看作是一个原型。第二次,请把您所需的功能与第一次所需的功能作比较。在第三次前后,您应该开始考虑花时间来编写一个通用实用程序。即使纯粹的重复性任务也可能会给实用程序的开发带来好处;例如,由于人们对尝试以通用的方式重命名文件感到失望,于是开发了许多通用文件重命名程序。
  
  做好一件事情;不要糟糕地做多件事情。关于做好一件事情的最佳例子或许是 sort。除了 sort 外,没有其他 哪个实用程序具有排序功能。基本的思想很简单:如果一次仅解决一个问题,您就能花时间把它解决好。
  
  设想一下,如果大多数程序都具有排序功能,但是有些仅支持按词法排序,而其他一些仅支持按数字排序,另外一些甚至支持关键字选择而不是对整行排序,那将是一件多么令人沮丧的事情。起码,这也是恼人的。
  
  当您发现某个问题需要解决时,应尝试将问题分解为多个部分,不要重复那些其他实用程序中已经存在的部分。您对允许配合现有工具使用的工具关注得越多,您的实用程序就越有可能保持有用。
  
  也许您需要编写多个程序。完成专门任务的最佳途径通常是编写一两个实用程序,再用一些线索将它们联系起来,而不是编写单个程序来解决整件事情。使用 20 行的 shell 脚本来将新的实用程序与现有工具结合起来是很理想的。如果尝试一次解决整个问题,随之而来的第一个变更就可能要求您全盘重新考虑。
  
  我偶尔需要从数据库生成两列或三列的输出。编写一个程序在单个列中生成输出,然后结合使用一个对输出进行分列的程序,这样通常会更有效率。组合这两个实用程序的 shell 脚本本身是临时性的,单独的实用程序比这个脚本的使用寿命更长。
  
  有些实用程序服务于非常专一的需要。针对一个包含大量内容的目录,如果 ls 的输出非常快地滚出屏幕,这可能是因为其中有一个文件具有非常长的文件名,从而迫使 ls 仅对输出使用单个列。使用 more 来对输出分页会花一些时间。为什么不像下面这样就按长度对行排序,然后通过 tail 来管道输出结果呢?
  
  清单 1. 世间能找到的最小实用程序 sl
  
  #/usr/bin/perl -w
  print sort { length $a <=> length $b } <>;
  
  清单 1 中的脚本确切地就做一件事情。它不接受任何选项,因为它不需要选项;它仅关心行的长度。归功于 Perl 便利的 <> 表达方式,这个小实用程序既适用于标准输入,也适用于命令行指定的文件。
  
   成为一个过滤器
  几乎所有实用程序都最适合想像为过滤器,尽管有一些非常有用的实用程序不符合这个模型。(例如,某个程序在执行计数时可能非常有用,尽管它作为过滤器工作得并不好。仅接受命令行参数作为输入并潜在地产生复杂输出的程序可能非常有用。)然而,大多数实用程序都应该作为过滤器来工作。根据惯例,过滤器对文本的行起作用。大多数过滤器都应该支持多个输入文件。
  
  记住实用程序需要在命令行和脚本中运行。有时,理想的行为会稍有不同。例如,大多数版本的 ls 都会在向终端写出时自动将输入排序到多个列中。grep 的默认行为是在指定多个文件的情况下打印从其中找到匹配项的那个文件名称。这样的差别应该与用户希望的实用程序工作方式有关,而不是与其他事项有关。例如,旧版本的 GNU bc 在启动时显示强迫性的版权标记。请不要那样做。让您的实用程序仅做它应该做的事情。
  
  实用程序喜欢生活在管道中。管道允许实用程序专注于自己的工作,而不是去关注旁枝末节。为了生活在管道中,实用程序需要从标准输入读取数据,然后向标准输出写出数据。如果您希望处理记录,那么您最好能够使每一行成为一个“记录”。诸如 sort 和 join 之类的现有程序已经在那样考虑了。它们将会因为您这样做而感谢您。
  
  我偶尔使用这样一个实用程序,它针对一个文件树反复调用其他程序。这充分利用了标准的 UNIX 实用程序过滤器模型,但是该模型仅适用于读取输入然后写出输出的实用程序;不能将它用于就地操作或接受输入输出文件名的实用程序。
  
  可以使用标准输入来运行的大多数程序也完全可以针对单个文件或一组文件运行。注意,可以证明这样违背了反对重复工作的规则;显而易见,这可以通过将 cat 的输出馈送给该系列中的下一个程序来解决。然而这在实践中似乎是合理的。
  
  有些程序可能合法地读取一种格式的记录,但是却产生完全不同的输出。这样的一个例子就是将输入材料划分为列的实用程序。这样一个实用程序可能将输入中的行视为记录,但是却在输出中的每行上产生多个记录。
  
  并非每个实用程序都完全符合这个模型。例如,xargs 不是接受记录而是接受文件名作为输入,并且所有的实际处理都是由其他程序完成的。
  
   通用化
  尝试将任务看作与您实际执行的任务类似;如果您能找出这些任务的通用描述,那么最好尝试编写一个符合该描述的实用程序。例如,如果您发现自己一天在根据词法对文本排序,而另一天在根据数字对文本排序,那么考虑编写一个通用排序实用程序也许是有意义的。
  
  对功能进行通用化有时会导致您发现:某个看起来似乎像单个实用程序的程序,实际上却是配合起来使用的两个实用程序。这很好。编写两个设计良好的实用程序可能要比编写一个丑陋的或复杂的实用程序更容易。
  
  做好一件事情并不意味着 仅仅 做一件事情。它意味着处理一致但有用的问题空间。许多人都使用 grep。然而,它的大量效用在于执行相关任务的能力。grep 的各种选项完成许多小实用程序的工作,如果这些工作都由单独的小实用程序来完成,最终会造成大量共享的、重复的代码。
  
  这条规则,以及做好一件事情的规则,都是一个根本原理的必然结果:无论何时都要尽可能避免代码重复。如果您编写半打程序,其中每个都对行排序,您最终可能必须六次修复六个类似的 bug,而不是去使用一个得到更好维护的 sort 程序。
  
  这是编写实用程序的一部分,即把大多数工作添加到完成该实用程序的过程中。您也许没有时间在最初就完全通用化一个实用程序,但是当您一直使用该实用程序就会获得相应的回报。
  
  有时,向某个程序添加相关功能是很有用的,即使这个功能并不是用来完成完全相同的任务。例如,当运行在终端设备上时,对原始二进制数据进行完美打印的程序可能更为有用,因为它使终端进入原始模式。这样使得测试涉及键盘映射、新键盘等的问题变得容易多了。不确定为什么当您按 delete 键时却得到代字号(~)吗? 这是弄清实际发送了什么内容的容易途径。这并不是完全相同的任务,但它足够类似,因而可能成为一个附加特性。
  
  清单 2 中的 errno 实用程序就是通用化的很好例子,因为它同时支持数字和符号名称。
  
   健壮
  实用程序的稳定性是很重要的。容易崩溃或无法处理真实数据的实用程序不是有用的实用程序。实用程序应该能够处理任意长度的行、巨型文件,等等。实用程序无法处理超过其内存容量的数据集或许是可以容忍的,但是有些实用程序不是这样;例如,sort 通过使用临时文件,一般能够对比其内存容量大得多的数据集排序。
  
  应该尽量确保弄清楚您的实用程序可能要操作哪些数据。不要简单地忽略无法处理的数据的可能性。应该检查这种情况并诊断您的实用程序。错误消息越明确,您对用户就越有帮助。尽量给用户提供足够的信息,以便让他们知道发生了什么情况以及如何解决。当处理数据文件时,尽量准确识别出不良的数据。当尝试解析数字时,不要简单地放弃;应该告诉用户您得到了什么数据,而且如果可能的话,还应该告诉用户该数据位于输入流中的哪一行上。
  
  作为一个很好的例子,请考虑 dc 的两种实现之间的区别。如果您运行 dc /home ,其中一种实现会显示“Cannot use directory as input!”而另一种实现只是无声地返回,没有错误消息,也没有不寻常的退出代码。当您错误地键入一个 cd 命令时,您更希望当前路径中有哪一种实现呢?类似地,如果您提供某个目录中的数据流(或许是执行 dc < /home),前者会给出详细的错误消息。另一方面,当它在获得无效数据的早期就选择放弃可能是理想的。
  
  安全漏洞经常植根于在意料之外的数据面前表现得不够健壮的程序中。务必记住,优秀的实用程序能够设法在 shell 脚本中作为根(root)用户身份运行。诸如 find 这样的程序中的缓冲区溢出可能会给大量的系统带来风险。
  
  程序对意料之外的数据处理得越好,它就更可能适应变化的环境。通常,设法使程序更健壮会导致您更好地理解该程序的作用,从而更好地使之通用化。
  
   新颖
  要编写的最糟糕的实用程序种类之一就是您已经有了的实用程序。我编写过一个名为 count 的美妙的实用程序。它允许我执行几乎任何计数任务。它是一个出色的实用程序,但是已经有一个名为 jot 的标准 BSD 实用程序做同样的事情。同样地,我的一个用于将数据转换为列的灵活的程序重复了一个现有实用程序 rs 的功能,这个实用程序同样可以在 BSD 系统上找到,只不过 rs 更灵活,设计得更好。请参阅下面的 参考资料 以了解关于 jot 和 rs 的更多信息。
  
  如果您即将开始编写一个实用程序,请花一点时间浏览一下各种系统,以确定那样的实用程序是否已经存在。不要害怕在 BSD 上借用 Linux 实用程序,或在 Linux 上借用 BSD 实用程序;实用程序代码的乐趣之一在于,几乎所有实用程序都具有很好的可移植性。
  
  不要忘了考察一下组合现有应用程序来形成一个实用程序的可能性。从理论上讲,组合现有程序来形成的实用程序运行得不足够快是可能的,但是编写一个新的实用程序很少会比等待一个稍慢的管道更快。
  
   一个例子实用程序
  从某种意义上,这个程序是一个可执行文件,因为对于作为过滤器来说,它决不会有任何用处。然而,它作为一个命令行实用程序却工作得非常好。
  
  这个程序仅做一件事情。它以近乎完美的输出格式输出 /usr/include/sys/errno.h 中的 errno 行。例如:
  
  $ errno 22
  EINVAL [22]: Invalid argument
  
  清单 2. errno 查找器
  
    
QUOTE:

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10617542/viewspace-947083/,如需转载,请注明出处,否则将追究法律责任。

上一篇: 在Linux环境中安装Qmail的经历(转)
下一篇: Linux文件及目录的Suid/Guid和T属性(转)
user_pic_default.png
请登录后发表评论 登录
全部评论
<%=items[i].createtime%>

<%=items[i].content%>

<%if(items[i].items.items.length) { %>
<%for(var j=0;j
<%=items[i].items.items[j].createtime%> 回复

<%=items[i].items.items[j].username%>   回复   <%=items[i].items.items[j].tousername%>: <%=items[i].items.items[j].content%>

<%}%> <%if(items[i].items.total > 5) { %>
还有<%=items[i].items.total-5%>条评论 ) data-count=1 data-flag=true>点击查看
<%}%>
<%}%> <%}%>
BSDLite
  • 博文量
    1873
  • 访问量
    3795501

最新文章

  • 红联Linux门户-做最出色的Linux技术社区网站(转)
  • 2005年中国开源软件大事记(转)
  • 《LINUX与UNIX SHELL编程指南》读书笔记(转)
  • TurboLinux中文版使用手册(转)
  • Red Hat Linux技术教程(转)
  • 了解Linux的时钟(转)
  • 别名——TurboLinux网络教室(转)
  • 在redhat 9上安装CJK-latex过程详解(转)
  • Linux系统可卸载内核模块完全指南(下)(转)
  • Linux系统可卸载内核模块完全指南(中)(转)

转载于:http://blog.itpub.net/10617542/viewspace-947083/


http://www.niftyadmin.cn/n/1112745.html

相关文章

JavaScript二进制数据序列化和反序列化

最近业余时间在搞h5小游戏&#xff0c;由于同步协议过于频繁&#xff0c;和服务器之间的同步直接用json就显得太浪费了&#xff0c;于是我们商讨之下决定改用二进制。学习过程中并没有遇到一篇就解决问题的文章&#xff0c;遂再总结一发。 1.二进制数据的存储 ArrayBuffer对象、…

网页中meta标记

网页中常常看见有这样的标记&#xff0c;他们是清浏览器缓存用的 <meta http-equiv"Pragma" content"no-cache"><meta http-equiv"Cache-Control" content"no-cache"><meta http-equiv"Expires" content&q…

补充(echo用法、for循环、continue、break、IFS字段分隔符)

文章目录echo用法for循环breakcontinueIFS字段分隔符echo用法 echo -n 表示不换行输出 echo -e 输出转义字符&#xff0c;将转义后的内容输出到屏幕上 常用的转义字符 \b&#xff1a;转义后相当于按退格键&#xff08;backspace&#xff09;&#xff0c;但前提是"\b&qu…

腾讯云独家详解小程序多人视频通话

欢迎大家前往云社区&#xff0c;获取更多腾讯海量技术实践干货哦~作者&#xff1a;小程序音视频产品经理功能体验 在微信小程序中搜索 腾讯视频云 可以加载到我们的演示用小程序&#xff0c;其中 多人音视频 功能可用于体验和测试多人音视频通话功能。出于 UI 美观和画面大小的…

Linux文件及目录的Suid/Guid和T属性(转)

Linux文件及目录的Suid/Guid和T属性(转)[more]  1. 4000---调整用户号 2000---调整组号 1000---粘着置位 2. suid/guid程序 当一个程序的用户或组被置位的时候&#xff0c;即4000或2000时&#xff0c;可实现某些特殊的功能一般来说&#xff0c;一个运行中的程序为运行这个程序…

用循环语句完成简单图案组成——一篇就够了!!!

文章目录九九乘法表for循环while循环成品直角三角形代码成品等腰三角形代码成品菱形代码成品长方形代码成品平行四边形代码成品梯形代码成品九九乘法表 for循环 while循环 成品 直角三角形 代码 成品 等腰三角形 代码 成品 菱形 代码 1 #!/bin/bash2 for ((i1;i<10;i))3…

JavaScript异步基础

唯一比不知道代码为什么崩溃更可怕的事情是&#xff0c;不知道为什么一开始它是工作的&#xff01;在 ECMA 规范的最近几次版本里不断有新成员加入&#xff0c;尤其在处理异步的问题上&#xff0c;更是不断推陈出新。然而&#xff0c;我们在享受便利的同时&#xff0c;也应该了…

TCSH shell变量和特征配置(转)

TCSH shell变量和特征配置(转)[more]TCSHshell可以使用户使用shell配置变量和特征配置自己的shell.也可以使用set命令设置特征.TCSH也有注册,注消以及何时进入TCSHshell的配置文件.一、TCSH shell 特征TCSH有几个特征,允许控制不同的shell操作的方法.TCSH shell特征不仅包括许多…