作为第四代语言的 UNIX Shell
Evan Schaffer 和 Mike Wolf
Revolutionary Software, Inc.
131 Rathburn Way, Santa Cruz CA 95062, USA
evan@rsw.com - wolf@hyperion.com
翻译:寒蝉退士
译者声明:译者对译文不做任何担保,译者对译文不拥有任何权利并且不负担任何责任和义务。
原文:http://www.rdb.com/lib/4gl.pdf
开源免费实现:
shell+AWK+perl: http://www.linux.it/~carlos/nosql/
ANSI C: http://cfa-www.harvard.edu/~john/starbase/starbase.html
译序:本文言辞过分尖刻但不乏真知灼见。时过境迁:COBOL 和第四代语言的提法已经成为了陈迹,现在的主流数据库系统是在 UNIX 上开发的,SQL 成为工业标准... 但本文所倡导和反对的仍是很现实的。翻译它不表示译者赞同其中的观点。
摘要
在 UNIX 上能获得很多数据库系统。但多数都是让你必须进入其中并抛舍 UNIX 自身能力的软件监狱。许多都是在非 UNIX 的操作系统上开发的。所以除了操作体统提供的特征之外,它们的开发者只拥有少量的软件特征来直接建造和书写他们需要的功能。结果的数据库系统是巨大、复杂的程序,它降低整体的系统性能,特别是运行在多用户环境的时候。
UNIX 提供了大量的可以用管道连起来的程序,去完成几乎任何可能的功能。没有什么东西能接近 UNIX 提供的功能。从其他系统搬运来的程序和哲学在用户和 UNIX 之间筑就了一堵墙,UNIX 的能力被扔掉了。
带有一些关系操作者扩展的 shell,是最适合 UNIX 环境的第四代语言。
- 1. 第四代系统
- 2. Shell
- 2.1 与 DOS 的兼容性
- 2.2 /rdb 如何定义一个关系数据库
- 2.3 什么是关系?
- 2.4 如何访问信息?
- 2.5 建立表和录入数据
- 2.6 报告
- 2.7 大文本字段问题
- 2.8 非文本数据结构
- 2.9 大表
- 2.10 结构性的性能增进
- 2.11 来自文学作品交易的一个例子
- 3. SQL
- 4. 第五代系统
- 5. 讨论
- 6. 结论
- 7. 引用
1. 第四代系统
近年来,在编程语言设计中呈现出多种发展。面向对象语言变得普遍,而明显的支持多任务和任务间通信的语言也开始流行。不幸的是,这些努力导致的生产力增长相对于软件系统大小和复杂性的增长实在是太小了。对此的回应曾是开发第四代编程语言。尽管通常不这么想,UNIX shell 是能获得的最强力和灵活的第四代语言。
1.1 尝试定义
在如何规定第三或第四代语言的定义上没有一致意见。主流的第三代语言是有类型的、过程式语言。它们是标准化的和独立于硬件的。在这些语言中的操作必须用详细的、一步接一步的算法方式指定。第三代语言做很少的隐含处理。第三代语言是通用的,即使它们多数在表面上被设计为专用语言。第四代语言通常意图用在特定应用领域中做设计工具。它们通常有自由形式的变量使用,通常不需要类型定义并允许动态类型的变量。它们不强调模块化、基于过程的编码式样。转而,它们包含一些预定义的过程来进行各种高层操作。这些高层操作涉及大量隐含的处理。例如,通常能获得“sort” 操作。第四代语言的这些设施比起第三代语言中的那些设施通常是更强力和更少灵活的。
第四代语言(4GL)应当尽可能的提供你需要的简单语句,而不是如何处理它的详细过程。尽管今天有许多产品称自己是 4GL,他们通常是 COBOL 的重写和报告书写器。它们太低层和乏味了。4GL 不应该这样是明确的。
1.2 前几代语言
第一代计算机语言是机器指令的零一序列。开始时人们必须以这种方式编码。
第二代是汇编语言,它与机器指令是一一对应的。人们可以写名字单字来转换成机器语言。例如下面的汇编代码加寄存器 1 到寄存器 2。
图 1. 第二代程序add r1,r2
一行代码生成一行计算机指令。此后,在大约 1956 年,写成了 FORTRAN 来做公式翻译,并能更容易的写程序。每行代码生成多个计算机指令。第三代语言已经涵盖了复杂的宏汇编语言,而其他叫做“高级语言”的还有 C、COBOL、Pascal、LISP、PL/I 等。它们有许多高级构造接近于英语如“if”、“then”、和“else”,但是这些类型的语句多数被限制在算术操作上,带有有限的字符串功能。典型的第三代语言是:
图 2. 第三代程序for i=1 to 10 print i, sqrt(i) next i
下一阶段是描述你想要的东西并让计算机考虑如何为你去做。第四代语言有类似英语的单字,但是语句处理的不只是数,并是“非过程的”。例如,排序在一个文件中所有行的一个程序,在第四代环境中简化成“sort 文件”。第四代语言基本操作(原语)经常包括关系操作者,而第三代语言通常不是。还有,当你需要混合过程和非过程指令的时候,这是很容易完成的。
图 3. 第四代程序for file in file1 file2 file3 do sort $file done
在 UNIX shell 层面你可以在很多情况下,说你想要的而不用说如何做(非过程),你可以用:
$ sort file
得到一个排序了的文件。用
$ spell file
得到在你的文件中的不在字典中的单词的一个列表。一行命令生成对一个或多个程序的调用,每个程序都可能有上千个指令。
使用 shell 你可以在几分钟或几小时中把应用程序组装起来,而不是使用 3GL 要求的几周或几个月。在 4GL 中你应当可以用一或两行来写多数应用程序。使用 shell 你可以有如下表示:
$ cut some columns < file | grep 'string' | sort | lpr
这个简短的程序得到一个文件中的一些列,连接管道通过 grep 来得到带有特定字符串的行、排序它们并把它们发送到行打印机。
在 COBOL、PL/I、C 和多数商业 4GL 中相同的报告将使用数十到数百行。在这些语言中你一次写一个指令,来一次处理一个文件的记录。这与写一个指令来在整个文件上操作相比较是非常乏味的。
1.3 在数据中的数据结构
在理想的环境中,数据的结构在数据中。记录的换行分隔符、和字段的列分隔符可以告诉所有程序字段和记录在哪里。3GL 语言把数据结构硬编码到数据中,所以一个程序只能读一类文件。
在传统的第三代语言环境中,文件的结构必须硬编码到程序中。在第四代语言环境中,文件有带有内嵌换行和 tab 作为记录和字段分隔符的自己的结构。任何程序都可以只通过察看字符流来找到一个记录。增加一个单一字符到 COBOL 程序读的数据文件,所有东西都会改变或丢失,所以你必须永远在 COBOL 环境中做文件转换。在 COBOL 中这是很累人的。任何改变都要求编辑和重新编译读这个文件的所有程序。
此外,在 3GL 中没有文件操作者,只有每次一个字段的指令。所以你必须写循环来处理每个记录。这比一次只处理整个文件使用更多的代码。多数商业 4GL 非常类似于 COBOL。你仍然必须做每次一个记录的处理。 如果 COBOL 程序使用 100 行,4GL 无论如何将使用 50 到 100 行,来做上面用一个管道所做的事情。
1.4 计算的革命
如果你在 UNIX 上写 C 程序,你失去了 shell 层面编程的多数好处。既然在 UNIX 上 C 和其他语言提供了 system 调用,建议你把他们转换到 4GL 中。这意味着如果汇编器有一个“system”命令则它是一个 4GL。但是非 UNIX 操作系统如 DOS 和 VMS 上没有象 UNIX 那样丰富的各种工具,UNIX 已经影响这些系统的那些领域除外。
UNIX 系统自身为数据组织提供一个完整的树索引方式: 有层次的文件系统自身。许多实用工具游历这些树,查找它们,增加和删除节点,并通常提供处理文件的过程性工具。对 DOS 和 Macintosh 系统也是这样。提供了不去重新发明轮子的机会。
这是一场计算的革命。使用伟大的工具工作将宠坏你,但是计算世界的大部分仍是写 COBOL。必须在这样的环境之间往返是痛苦的。
好的 4GL 应当是用 C ... 写一次。它应当写得通用和易用来从此使用它的功能,而不是在每个应用中重新编码。接着可以使用这些好的程序来组装应用程序,而不是用 C 编码整个应用程序,除非是关键的需要。
随着用户更加熟悉他们的环境,他们将更能够使用这些高级系统的能力,即使只是缩短重复性的命令序列,这是 4GL 的另一个关键特征。在所有计算环境中都有折叠击键序列的设施如别名、脚本、和宏构造。
营销人士得到 4GL 的风声并把它投入到一个巨大的市场策划中。多数数据库管理系统写它们自己的过程语言,类似于 COBOL 或 RPG 的并称之为 4GL。它们通常不如 COBOL,因为你必须学它们的新语言,而不是使用经典。很少有 4GL 设计者投入设计他们的语言中的时间和精力能达到投入到 COBOL 那样。
在第四代语言背后的驱动力来自多方面需要。编程项目通常牵涉一个人多年的工作。有经验的软件工程师的缺乏和对生产力增长的需求迫使我们趋向允许更快的开发周期的软件工具。没有经历正规计算机科学教育的用户对计算机使用的增加要求非常高级的工具来让编程新手集中在算法而不是实现细节上。随着在计算机上做更多的工作,更需要使用一个单一的程序来进行一个特定的任务。编码一个软件工具相对高的代价鼓励使用任何可获得的方法来简化开发。
因为第三代语言变得日益不能满足计算机用户的各种需要,多个软件设计原理获得顺利的流行,特别是在 UNIX 社区中: 数据应当保存在平常的 ASCII 文件中,而不是二进制文件中,这样我们总是可以见到我们在做什么,而不需要依赖某些特殊程序来为我们解码我们的数据。
编程应当在第四代语言中完成,除了在程序期望繁重的使用和/或资源消耗的时候,证实需要用第三代语言进行更有效的编码。程序应当是小的并应当把数据传递给另一个程序。必须避免软件监狱、或带有自我包含环境的大程序,因为它们需要学习并且它们使提取数据很困难。
我们应当建造软件和系统来满足接口标准,这样我们可以共享软件并停止梦想任何个人或公司可以完全从头做它。
遵循如此原理的软件工程有一些缺点。主要的缺点是第四代语言几乎总是产生比第三代语言更慢的代码。 随着计算机在速度和能力上的增长这些缺点将被更少考虑。随着改进了的编译器技术的传播,在 3GL 和 4GL 生成的代码之间的区别将更小。
1.5 范例
对于确保一个健壮的语言,它要有一致的语法和语义风格,一个编程范例是重要的。第四代语言的范例必须满足比第三代语言更严厉的要求。作为开始,第四代语言应当向使用各种复杂的数据类型的高级设施提供一个一致的接口,而同时为编码预定设施缺少的任何功能提供基本的低层语言构造。太多的 4GL 只适合在一个狭窄的应用领域内的项目。除非编程者指定在 3GL 中要求的详细程度,允许来自各种领域的高级构造是困难的。
我们为第四代语言选择的范例是操作者/流范例。在这个模式中,数据在单向的‘流 ’中流动,操作者就放置在其上。每个操作者转换传递来的数据。 在一个程序中的流的集合形成一个有向图,可能带有环。 这个范例集中在对数据需要做的,并强调在转换中用到的技术。那些尝试只使用主流第三代语言的过程式范例的第四代语言通常完结为被限制到一个特定应用领域。过程模型不能以足够抽象的方式描述数据。不同类型的操作要求太多详细的代码来一起工作,并且语言没有操作者/流范例在所有数据和操作者之间的那种简单关系。
使用操作者/流范例的一个副作用是促进了图形编程工具的设计。传统的第三代语言不被接受为图形编程接口。问题的主干部分的来自以直觉的图示方式表达各种可能性的困难。使用操作者/流范例作为一门语言的基础,一种图形化的编程辅助可以容易的运作在流上放置操作者的过程。
操作者/流范例在不止是语言设计的更多领域上证明是有效的。一些 UNIX 内核使用这个范例来简化操作系统代码的复杂性。不用大的复杂的一段代码来处理操作系统的一个特定的方面(比如设备驱动程序)的所有功能,在内核中数据运行在链接起来的一组操作者上,每个操作者进行一个小的,完善定义的功能。这允许用户通过介入新的操作者来修改系统,而不需要理解在流上的其他操作者的内情。
2. Shell
shell 和 UNIX 实用工具集形成了基于操作者/流范例的一个第四代语言(4GL)。shell 在 4GL 类别中加入的关键性特征是 UNIX 管道,它允许 shell 开始一序列的处理,每个都从它前面的处理读取它的输入,并把它的输出写给一个后续的处理。UNIX 管道是导致 UNIX 被接受为标准的多用户操作系统的主要原因。不幸的是,很少有人完全理解它背后的哲学;多数软件开发者仍然生产大的自包含的应用,使用与其他东西不相容的数据格式。
对于 shell,UNIX 管道提供了数据流,而上百个标准 UNIX 使用工具提供了操作者的核心集。这种方法的力量是巨大的。 因为数据流是平常的 ASCII 码,所有的操作者可以相互读别人的数据。UNIX 包含许多标准的实用工具,把一个程序的输出转换成另一个程序的需要的形式所需要的大多数数据格式化,它们都有能力做。 此外,使用单独程序作为操作者允许容易的使用定制或商用的操作者包,比如统计或数据库包。这种模块性鼓励代码重复使用,并且平常的 ASCII 流格式使其易于从各种来源获得操作者并相互交流。最后,因为操作者只能转换在其上通过的数据流,操作者的副作用不会给出未预期的结果使软件工程师措手不及。
UNIX 文件系统还为数据提供一个有层次的存储媒介。因为 UNIX 文件是平常的 ASCII 数据文件,并且 UNIX 做了有预谋的尝试使所有数据源看起来相同,多数实用工具不能区别来自流和来自文件的数据。这给出了巨大的灵活性,允许 shell 存储一个管道的结果到一个文件,并在将来的某个时刻把这些数据返回流中。
对第四代语言有两个常见的批评。经常被提及的是 4GL 只意图适合一个特定的应用领域,并且它的低层设施不能胜任提供要在高层库中不存在的灵活功能的任务。shell 避免了这个问题; UNIX 实用工具可以用任何语言写,从 shell 脚本到汇编语言。如果需要一个目前不存在的工具,开发者可自由的选择最适合解决这个问题的语言,不管是 CASE 工具还是标准 C。有能力把 shell 与所有其他现存的开发工具的产品组合起来,导致一个独特的通用的 4GL。
许多人还抱怨第四代语言牺牲了太多的效率来换取短代码和快速开发的好处。有能力使用来自任何源码的操作者也是对这个抱怨的回答。它允许 shell 编程者以更适合效率考虑的语言来编码苛求速度的例程。如果一个例程要求浮点数的紧要处理,你可用 Fortran 编码合适的例程,其他不苛求时间的代码片段可以仍用 高层 shell 代码完成。
使用 shell 开发是非常容易的即使是对于初学的程序员。这个解释环境允许在脚本运行的时候容易的访问到它的内部,近可能快的测试-检查-测试周期。平常的 ASCII 数据格式和没有操作者副作用易于检查不同的操作者在数据的流上的作用。shell 严重的依赖它的操作者。例如,它基本上没有表达式求值的能力。转而,它使用‘test’实用工具来提供表达式。
shell 只支持‘字符串’数据类型。shell 假定有操作者提供编程者可能需要的任何高级数据类型。存在操作者来进行数值功能。对于多字段纪录,操作者通常使用空格或 tab 字符作为字段分隔符和换行字符作为纪录的终止符。这允许巨大的灵活性,尽管对非字符串操作数把数据转换成 ASCII 和反向转换会招致花销。shell 的最伟大的力量之一是用一个单一命令处理一个完整的文件的能力。shell 允许定义过程,还有执行控制结构如 if、while、和 case。尽管这些流程控制结构经常是不需要的。在下面章节的例子中,没有明显的用 shell 脚本做的循环,因为在程序的每行上起作用的操作者做隐含的循环。
2.1 与 DOS 的兼容性
DOS 分享 UNIX 的关键底层特征, 足够在两种环境中同样的利用操作者/流范例。除了在文件名语法上次要的限制,DOS 的有层次的文件系统对于用户在功能上等同于 UNIX 文件系统。DOS 缺少的 UNIX 的多任务能力,不是这个范例的本质基础。尽管 DOS shell 使用中介临时文件来实现管道,甚至使用 COMMAND.COM,呈现给用户的接口都可以用我们这里用的操作者/流范例来描述。UNIX shell 和 awk 可作为 DOS shell 的替代和增强,明显的例子是 MKS Toolkit for DOS。
在脑子中有了这些基础,让我们察看一个使用 shell 作为开发环境的一个 4gl: /rdb。
2.2 /rdb 如何定义一个关系数据库
关系数据库是可以在一个或多个公用列上关联的一组关系或表。这样实现的关系数据库可以轻易的从一个环境转送到另一个环境。关系数据库有关系集合理论、关系代数、关系演算的坚实的数学基础。在关系数学中的定理证明了放置到关系数据库中任何数据都可以提取出来。数学基础还确保进行的操纵将得到正确的结果,如同算术能确保在计算机上做的数学函数会得到正确的结果一样。
2.3 什么是关系?
/rdb 关系或表,是普通 ASCII 文件。但是必须依从一些规则来使一个普通文件成为一个数据库表。/rdb 表有用换行分隔的行、或纪录。他有用一个 tab 字符分隔的字段、或列。每行必须有相同数目的列。表的第一行包含这些列的名字;第二行包含多列折线。在表中可以表述任何种类的信息: 数、字、文件名字等: /rdb 命令和关系集合理论不关心表的内容 -- 只要表的形式依从这些规则。在设计数据库的时候要记住的另一个重要规则是: 如果在一个单一行中使用多个列来描述相同类型的信息,则有必要建立一个新表。例如,考虑家族成员表:
id mom dad kid1 kid2 -- ---- --- ---- ---- 1 mary jack billy bobby 2 nancy joe terry susie 3 sally john adam
在这个例子中,在每行上有两个 kid 列。表达这个联系的正确方式是使用两个表: 一个给父母一个给子女。它们通过一个公共列 id 来关联或连接。
% cat folks id mom dad -- --- --- 1 mary jack 2 nancy joe 3 sally john% cat kids id kid -- --- 1 billy 1 bobby 2 terry 2 susie 3 adam
2.4 如何访问信息?
通过在 UNIX 提示或在 shell 或 C 程序中发起 /rdb 和 shell 命令来访问表。下面是一些常用 /rdb 命令的列表,它们这些例子中使用或提及。
图 4. 挑选的 /rdb 命令 /rdb 命令 描述 ------------------------------------------------------ column, project 选择特定的列 row, select 只选择特定的行 mean 计算选中的列的平均值 jointable 连接两个表 sorttable 排序一个表 compute 在列上做计算 subtotal 在选中的列上做部分和 total 在选中的列上做总和 rename 改变某列的名字 justify 使表正确的排列起来 headoff 删除在最先的两个头部行 report 报告复写器 ve vi 式的表编辑器
/rdb 命令是从标准输入读表并向标准输出写表的程序。假设有如下一个表:
% cat inventory Item Amount Cost Value Description ---- ------ ---- ----- -------------- 1 3 50 150 rubber gloves 2 100 5 500 test tubes 3 5 80 400 clamps 4 23 19 437 plates 5 99 24 2376 cleaning cloth 6 89 147 13083 bunsen burners 7 5 175 875 scales
接着是一个样例查询:
% column Item Cost Amount < inventory Item Cost Amount ---- ---- ------ 1 50 3 2 5 100 3 80 5 4 19 23 5 24 99 6 147 89 7 175 5
它可大声的读作: “从 inventory 表选择出 Item、Cost、和 Amount 列”。 念出查询是重要的,因为人们经常键入他们永远不能大声直接念出的材料。
% row 'Cost > 50' < inventory Item Amount Cost Value Description ---- ------ ---- ----- -------------- 3 5 80 500 clamps 6 89 147 16353.8 bunsen burners 7 5 175 1093.75 scales
这是,“从 inventory 表选择出 Cost 列大于 50 的那些行”。把命令放在一起:
% column Item Cost Value < inventory | row 'Cost > 50' Item Cost Value ---- ---- ----- 3 80 400 6 147 13083 7 175 875
这读作,“从 inventory 文件选择出 Item、Cost 和 Value 列并选取出 Cost 大于 50 的那些列”。 在单引号中的 < 和 > 符号分别读作小于和大于,而在单引号之外的则读作从和到。| (管道)符号读作并。要在列出每行的时候得到结果的平均:
% column Item Cost Value < inventory | row 'Cost > 50' | mean -l Value Item Cost Value ---- ---- ----- 3 80 400 6 147 13083 7 175 875 ---- ---- ----- 4786
2.5 建立表和录入数据
建立表有多种不同的方式 :编辑器、程序、shell 命令如 sed 或 awk 等。/rdb 表经常使用 /rdb 表编辑器 ve,来从头键入。ve 允许以熟悉的和容易的方式快速的建立表格。它非常象 vi。使用 ve 建立表的第一步是建立一个屏幕定义文件。这可以用任何编辑器来完成。下面是 states 文件的‘屏幕’文件:
% cat states-s The States File st < st > state < state >
ve 使用这个屏幕文件来建立表。屏幕文件的规则很简单: 列名字在尖括号中。在尖括号之外的任何东西都是出现在屏幕上的文本。在尖括号之间的空格在这个字段上的可见窗口的宽度,而不是对字段实际上多宽的限制。在建立了如上屏幕文件之后:
% ve states
将建立 states 文件。假定我们已经使用 ve 向我们的 states 表增加了新的纪录,它现在看起来是这样的:
% cat states st state -- ----- CA California NV Nevada NY New York
通过制作一个‘屏幕’文件并接着使用 ve 来增加一些新行,可以用相同的方式建立一个邮件列表:
% cat mlist-s Yet Another Mailing ListName <name> Street <address> City <city> State <st>% justify < mlist name address city st ---- ------- ---- -- Evan Main St. Santa Cruz CA Rod Broadway Ithaca NY
要“从 mlist 选择 st 和 name 列并与 states 表连接”。
% column st name < mlist | sorttable | jointable - states st name -- ---- CA Evan NY Rod
sorttable 命令是静默的。但却是必须的。要连接的两个文件必须被排序。states 文件已经是有序的了。 在 jointable 命令中的折线意味着使用标准输入,如同 UNIX join 命令一样。
2.6 报告
对于数值信息,用 justify 或 trim 命令来调整 /rdb 的标准的表输出通常是足够的,特别是在组合了 tabletotbl 和 UNIX tbl 和 nroff/troff 格式化器的时候。除了这些方法,/rdb 中的 report 命令使用了一个原型报告表单并有内置命令的处理能力。
让我们察看一个样例报告表单。它很象给 ve 的屏幕文件: 文本在括号之外,而列名字在括号之中。其他命令也可以在括号之中。下面是给 mlist 文件的一个报告表单:
% cat mlist.form <name> <address> < city >, <st> <! date +%D !> Dear < name >, This is a computer chain letter. I am also sending it to:<! column name city < mlist | row 'name != "<name>"' | justify !> Bye, <! echo <name> !>. % row 'name ~ /Evan/' < mlist | report mlist.form Evan Main St. Santa Cruz, CA 09/03/89 Dear Evan: This is a computer chain letter. I am also sending it to: name city ---- ------ Rod Ithaca Bye, Evan.
任意的文本在尖括号之外;列名字在尖括号之内,在尖括号之内的惊叹号之间可以有任何任意的命令或 shell 程序或 shell 命令,而你仍然可以从这里的当前纪录之中指定列。你甚至可以有在报告内的报告 ...
2.7 大文本字段问题
‘缺陷报告’、‘长文本列’、和‘全文索引’问题是同一种情况的多个方面。我们假定一个文件有一些相对短的列,并有一个或多个你希望在其上使用 vi 的长文本列。
举缺陷报告数据库作为实例,它关联着任意长的叙述性描述: 一种解决是把所有描述保存在一个子目录中,比如叫做 bugreports,每纪录一个文件,而文件名是 bugreports/recid,这里的来自当前纪录的纪录标识符。接着在 .verc 文件中映射 CTRL-键(类比 vi 的 .exrc 文件)到命令“vi bugreports/<recid>”。 这从一个记录中截获一个识别出来的列,构造关联文件的名字,并为用户在指名的文件上弹出 vi。这是很灵活的,即使与每个记录有多个相关联的文件,用击键在 ve 文件之间切换,这样就导致多重屏幕: 映射一个 CTRL-键 来写纪录并切换文件,而用另一个来切换回来。
一个简单报告制作一个两列的表,包括记录 id 和 word ,word 字段给出叙述中出现的每个字的一个列表,允许象给出提及字‘xyzzy’的所有缺陷这样的查询:
译注:下面的 wordy 过程,在当前目录下,对名字由数字组成的每个文件,执行一次 word 命令, word 命令输出在标准输入中出现的每个字的一个列表,把生成的列表中的每个元素和这个文件的名字写在一行上。
% cat wordy #!/bin/sh (echo "word id" echo "---- --" for i in [0-9]* do word < $i | awk '{print $1,"'$i'"}' done) | sorttable -u
现在可以轻易找到带有一个特定字的纪录。如果速度是考虑事项,在刚建立的 word/id 索引表(concordance list)上建造(另)一个反向的索引:
% cd bugreports % wordy > bugwords % index -mi -x bugwords word % echo xyzzy | search -mi -x bugwords word
它在标准输出上生成记录 id 编号的一个列表。一旦你有了记录 id 编号,需要多做一次 search 来在‘缺陷’表中找到原始记录。当然,使用这个纪录 id 编号,不需要做 search 来找到叙述,因为文件名字就是纪录 id。
2.8 非文本数据结构
假定一个字段是图片、或声音、或一些其他非文字对象。/rdb 的方法是在一个表的一个字段内用文本标识一个对象资源,描述这个对象的类型和位置。可以用与报表程序和定制 ve 屏幕文件相同的<列_名字>指定来在 .verc 文件中引用自当前纪录的字段。这允许用一种清晰的,用户定义的方式把 ve 结合到基于 X 的或其他图形用户界面中。例如, 假定一个字段包含一个图像文件的名字。在 .verc 文件中可以映射一个 CTRL-键 成产生正确的命令来弹出一个新窗口,调用一个图片显示程序,并在这个窗口中显示在这个字段中指名的文件。图像文件可以是任何格式的,并可以驻留在网络上的任何地方。附加功能连结 X-window 鼠标到这个系统中,这样在鼠标悬浮在一个字段之上并按键的时候,执行正确的命令。这种方式是 UNIX-式样的整合所有我们以前的 UNIX 经验和软件经验到 X 用户界面的方式,并展示了使用 /rdb 的现存报表生成特征是容易的。
2.9 大表
大表通常可以轻易的处理成小表。当使用非常大的表的时候需要某种形式的索引: 散列、反向顺序二级、 binary(有序的关系)、或某种形式的树(链表)。
shell 方式使用 UNIX 目录结构作为树索引的第一(few)层。使用 /rdb 的一个财务应用涉及 80 兆字节文件,它是 World Bank 的来自 International Monetary Fund 的时间序列(time serie)文件。照原样发布,要占用了大量机器 CPU 时间来读这个大文件并提取一个个别的时间序列。文件被分开到给每个国家的一个目录中,而在每个国家的目录中,每个时间序列一个文件,文件名字是 IMF 给出的时间序列代码。每个这样的时间序列文件都是一个 /rdb 文件。带有列 YR ANN Q1 Q2 Q3 Q4 等。在每个国家目录中有一个独立的“描述”文件为目录中每个文件准备了一行,给出 CODE DESCRIPTION UNITS。
这样,检索任何时间序列的时间(如果知道国家和时间序列代码)与数据库的大小是无关的。象“那些国家共有这个时间序列?”是用 ls 命令解答的。只要增加目录层次就能实现多于一层的索引。
UNIX 有许多命令来遍历目录树、增加、删除、和其他操作节点。对于这种方式节点是表,而剩余的 UNIX 目录和文件处理命令都是关系数据库操纵命令。
2.10 结构性的性能增进
因为 /rdb 是 shell 层次的方法,可以立即获得来自多进程体系的增强和利益。在用 tcp/ip 协议连接的多个处理器的松散连结体系中,下列代码片断在多个处理器上并行的进行查询:
#!/bin/sh cat head (for i in a b c ... do rsh $i "cat keys | search portion.$i | headoff" & done) | continue ...
这是 shell 的实现者已经做了工作,从在每个处理器上工作的并行查找进程收集单独的行,并裁决出这个输出,这样一个时刻只能有一行提供给在括号命令结束处的 "continue" 进程。当然,把数据本身分割成部分供给每个并行查找也涉及一定的花费。所以这个技术在并行获得的速度提高超过分割文件的花费的时候才是适当的。
在大规模并行 SIMD 和向量机器如 FPS 系统、IBM 3090 向量处理器、和 MasPar MP 平台,获得这种体系的利益的直接方法是在 shell 层次实现矩阵功能。矩阵是表,并在 shell 可调用的程序中装入优化过的例程是没有疑义的。
在中等粒度上也有重申的价值。例如, Cogent Research 提供基于 LINDA 系统的 transputer,在可获得的计算能力之上自动的分布多个处理器。最一般的 MIMD 方式在 shell 级别实现是最困难的,例如,获得 HyperCube 方式的全部利益的数据库功能将花更多时间来完全实现。
2.11 来自文学作品交易的一个例子
UNIX/World 杂志发表了关于第四代编程语言的两篇文章(在 July 1986 和 April 1987),并邀请了一些公司竞赛使用他们自己的 4GL 系统生成一个样例的报告。他们的语言更象是 COBOL 或 RPG 而不是真正的 4GL。如果他们能代表用什么衡量语言是第几代,shell 很容易就归类为第四代。
要展示 shell 的能力,这里有两个脚本生成 UNIX/World 文章中那个样例报表。这些脚本使用标准 UNIX 实用工具并只扩展了 /rdb 关系数据库管理工具。下面的第一个例子生成 UNIX/World 测试要求的数据,但保留为缺省的格式。第二个报告使用了完全的满足文章的例子所必须的格式化命令。
% pay number fname lname code hours rate total ------ ------- -------- ---- ----- ---- ----- 1 Evan Schaffer 2 3 75 225 1 Evan Schaffer 2 4 75 300 ------ ------- -------- ---- ----- ---- ----- 1 7 5252 Mike Wolf 1 4 85 340 2 Mike Wolf 2 5 85 425 ------ ------- -------- ---- ----- ---- ----- 2 9 7653 Barbara Wright 2 5 75 375 3 Barbara Wright 1 6 75 450 ------ ------- -------- ---- ----- ---- ----- 3 11 8252115
下面是生成这个报表的 shell 脚本。注意只用了 9 行简单的可读的代码。不象其他所谓的 4GL,这里没有列或字符计数。没有“每次一行”处理。 注意尽管在这个 shell 脚本中的命令差不多完全由 /rdb 命令组成,/rdb 使用 UNIX 实用工具来完成工作。多数 /rdb 命令是 shell 脚本或 C 程序,它们广泛的使用了 UNIX 实用工具。例如,‘compute’程序只是到‘awk’的一个前端。
% cat pay jointable hours employee | sorttable code | jointable -j1 code -j2 number - task | sorttable number | project number fname lname code hours rate total | compute 'total = hours * rate' | justify > tmp subtotal -l number hours total < tmp total total < tmp | justify | tail -1
在 UNIX/World 文章中未展示数据表自身。主要的原因是示例所有 4GL 都有自己文件的特殊二进制格式,不能轻易的打印出来只能通过他们的接口来访问。在使用 shell 编程的时候,数据在 ASCII 文件中。这意味着访问数据的可以是人、是 UNIX、或你选择要写的任何程序。这是在脚本中涉及到的文件:
% cat hours number hours code ------ ----- ---- 1 3 2 1 4 2 2 4 1 2 5 2 3 5 2 3 6 1 % cat employee number fname lname rate ------ ----- ----- ---- 1 Evan Schaffer 75 2 Mike Wolf 85 3 Barbara Wright 75 % cat task number name ------ ---- 1 unix/world 2 Lawrence Livermore
shell 程序生成要求的准确的格式,附加为展示 2,仍旧只用了 28 行。完成相同任务的 C 程序要用几页代码。July 1986 UNIX/World 包含的例子代码有9个 4GL 对这个问题的解答。注意只有一个语言使用了更少的行来解决这个问题,而在允许使用其他报表格式的时候,Progress 的解决不会变得更短,而 /rdb 可以。
语言 代码行数 ------------------------------- Progress 20 Rubix 32 Empress/32 34 Unify DBMS 34 filePro 16 Plus 42 Informix-4GL 48 SHAR->IX 48 C/Base 64 Plain English 84
学习使用 UNIX 实用工具比学习另一种特殊编程语言有更大的价值。一旦对 UNIX 的熟悉达到了一个临界状态,应用开发不过是写简单但强力的脚本来进行习惯于手工进行的任务,或者根本不做。
所有这些技术来自 UNIX 系统自身的设施相与 /rdb 提供的关系功能的结合。不重复发明轮子的这种态度是 shell 和 /rdb 方式的基础。所有 UNIX 知识都是数据库知识,和有关 UNIX 的越来越多得数据库教学经验。这是把 /rdb 扩展组合到 UNIX 和 shell 命令语言是最适合 UNIX 环境的 4GL 的原因。
3. SQL
SQL 是查询数据库的另一种语言。它被用作许多当代的 4GL 的基础。它不使用流/操作者范例,而用“嵌套”查询来从一个操作者传递数据到另一个。在开发 SQL 的时候 UNIX 不存在,所以必须开发一个完整的环境来表达查询。SQL 是需要学习的另一个系统,在自身之外没什么用处,而且典型的与周围的操作环境没有什么关系。
SQL 不指定任何特定的文件格式。但有表达查询的一个 ANSI 标准 SQL,实现者可以自主的存储任何他们想要的数据。在某种程度上这是一个矛盾,因为除掉在数据之间的这些围墙是非常重要的,这是数据库概念产生的首要原因。这种想法现在存在于集成和非模态(modeless)软件和最新的互操作性名义下。
有很多原因使基于 SQL 的系统流行甚至是理想的。基于 SQL 的系统是能广泛获得的,并能很容易的找到大的专业团队。许多联邦政府部门要求通过 SQL 访问团体的数据库,特别是国防工业。SQL 在非 UNIX 环境是很有价值的。部分的因为 SQL 对初学者是难学难用的,SQL 提供者典型的要开拓一个大的、帮助性的支持组织。当然,这促使价格上涨,并且不能充分的满足促成开发这些工具的首位的需求: 使非专家精通和高产于基本数据库应用的构造。
可以用 /rdb 中的 sql2rdb 过滤器把 SQL 查询轻易的转换成 shell 脚本。 附加的展示 1 是一个样例转换表。
4. 第五代系统
在第四代语言和 CASE 工具之间是有区别的。第四代系统的程序员仍然必须指定基本算法来完成一个任务,可能在一个更高的抽象层次上,特别是数据类型。在另一方面, CASE 工具只要求对任务的指定,并且生成的不只是代码,还有算法。 CASE 工具意图用在非常有限的领域。一个例子是屏幕布局工具。开发者绘出想要的窗口的位置,而工具生成代码来建立窗口,管理在其中的文本和图形,并处理图标和菜单。
一些图形 CASE 工具(X-rdb、 X-Builder 和 NeXT STEP)是可称为 5GL 的一些例子。使用一个图形用户界面,这些工具允许通过例子来建造应用。一些强制用户指定算法性的动作,而一些不强制。对什么是 5GL 比什么是 4GL 更加没有达成一致。
5. 讨论
shell 表现为一个真正的强力工具。但不是没有限制的。首先,只有一些公司目前生产面向在 shell 脚本中使用的工具。 /rdb 补救了 shell 不能存储复杂数据的弱点,并许多补充的工具来效正其他一些主要的限制,比如数值计算,统计分析,和商务图形输出。尽管 UNIX 有专门的数学和数值分析应用程序,多数自身是大的自包含的程序。比如一个编程者需要矩阵倒置,就必须接受现存的工具,如 System S 或 SAS,在 shell 脚本内工作,或者写一个特殊功能的工具。
shell 需要增进能力更直接的多个管道把连接在一起。最初的设计者没有预见除了线性管道之外的需要。尽管更加复杂,可以通过使用临时文件来建立非线性管道,这种方法只是能构造复杂的结构比如环流。最后,需要允许把到来的数据分割或复写到多个输出管道的更多的操作者。
shell 与弱类型语言共有另一个问题: 数据流的格式错误可能导致未预期的输出。因为没有作类型或格式检查的方法,编程者必须写代码来避免这个问题。解释环境允许仔细的检查数据流,因为它在每个操作者上传递,这减少了写免错代码的难度。shell 将无助于新近的 CASE 工具提供的正确性证明,除非提供一个形式定义,不只指定语法而且还有所有的操作者。
现在操作者/流范例被认识为编程语言设计的一个非常强力的模型,我们期望在近年内见到基于这个原理的一些新工具。更多得图形程序设计工具也应该开始出现了,现在 /rdb 已经被 X 窗口标准接受并提供了一个工具来图形化设计 shell 管道。随着可获得更多这样的图形化界面,建立 shell 脚本需要更少的编程经验,彻底的增加生产力。
6. 结论
操作者/流范例产生一个简单的、强力的、通用工具。它允许你在几小时或几天内原型化或生成一个概念的证明,而使用 C 可能需要几周。尽管 shell 过程代码比第三代语言慢,现代计算机不断增长的能力使对许多任务这是次要的。这个框架提供了操作大量(或小量)有结构的数据的一个易于可视化的方式。
尽管目前缺乏为在 shell 脚本中作为一般目的使用而设计的实用工具,对 shell 编程的潜能的认知正在传播着,而更多的在传统整体程序惯例之外的程序包正在写着。这样,计算将达成一个完整的轮回,返回到 Von Neumann 的最初的概念,它的计算范例具体化为通过中央处理单元的操作者的时序存储器的流。
展示 1. SQL 换算表 ---------------------------------------------------------------- SQL UNIX 和 /rdb ---------------------------------------------------------------- select col1 col2 from filename column col1 col2 < filename where column - expression row 'column == expression' compute column = expression compute 'column = expression' group by subtotal having row order by column sorttable column unique uniq count wc -l outer join jointable -a1 update delete, replace 嵌套 管道 ----------------------------------------------------------------展示 2. Shell 程序的精确格式 下面是生成严格的文章格式的修改了的 shell 程序:% cat payexact echo "Employee Charge" jointable hours employee | sorttable code | jointable -j1 code -j2 number - task | sorttable number | project number hours code fname lname rate name total | compute 'total = hours * rate; name = sprintf("%s %s",fname,lname)' | project number name code hours rate total > tmp compute 'if (name == prev) name = "";/ prev = name;/ hours = sprintf("%4.2f",hours);/ rate = sprintf("%6.2f",rate);/ total = sprintf("%7.2f",total)' < tmp | subtotal -l number hours total | compute 'if (code ~ / / && code !~ /-/) code = "* Employee Total";/ if (code ~ / / && code !~ /-/) number = ""' > tmp1 rename name "Employee Name" < tmp1 | justify -r number hours rate total -l "Employee Name" -c code | sed '/---/d s/^/ / s/rate/ rate/ s/total/ total/' TOTAL=`project total < tmp | total | compute 'total = sprintf("%10.2f",total)'| headoff` echo " / ** Report Total $TOTAL"