内容


人人可用的 Rexx

使用自由软件 Rexx 的实现来编写脚本

Comments

关于 Rexx

Rexx 编程语言最初创建于 1979 年,是一个层次非常高的脚本语言,有特别强大的用于文本处理任务的工具。从 Rexx 诞生起,IBM 就将各种版本的 Rexx 加入到它的操作系统中 —— 从主机到中型机再到终端用户操作系统,比如 OS/2 和 PC-DOS。其他 OS 制造商,比如 Amiga,也将 Rexx 作为永远可用的系统脚本语言集成进来。此外,许多独立软件开发商为很多平台创建了 Rexx 环境。再晚些时候,ANSI 于 1996 年正式为 Rexx 采纳了一个标准。

当前(尤其是在 Linux 或者起源于 BSD 的 OS 上),Rexx 的那些大部分早期实现,主要作为历史足迹为人们所关心。不过,两个当前保持的 Rexx 实现可以用于包括 Linux、MacOSX 和 Windows 在内的许多平台:Regina 和NetRexx。Regina 是本地执行文件,以免费软件源代码方式,或者为诸多平台预编译好的方式获得 —— 您可以像安装任何其他语言解释器一样安装它。NetRexx 是一个有趣的“混合物”。此语言派生自普通的 Rexx。更类似于 Jython 或者 Jacl,NetRexx 将类似于 Rexx 源代码编译为 Java 字节代码,并(可选)在 JVM 中运行作为结果生成的 .class 文件。

NetRexx 是一个 IBM 项目,用于为 Java 虚拟机编译类似于 Rexx 的代码。在实际能力和编程级别上,Rexx 最接近于 bash 加上 GNU 文件工具(外加 grepsed );或者可能相当于 awk 或 Perl。当然,相对于 Python、Ruby 或者 Java 来说,Rexx 让人感觉更为快而粗糙。Rexx 的冗长 —— 或者更应该说是,简洁 —— 类似于 Perl、Python、Ruby 或者 TCL。并且,Rexx 当然是完全 Turing 的,支持模块和结构化编程,有面向专门任务的库,例如 GUI 接口、网络编程和数据库访问。但是它最自然的目标在于系统脚本的自动化和本文处理任务。与 shell 脚本一起,Rexx 使得应用程序的控制非常自然且显而易见;但相对于 bash (或者 tcshksh 等等),Rexx 中包含了更丰富的内置控制结构和(文本处理)函数。

在文体上,Rexx 的 IBM/mainframe 根源体现在它对命令的大小写敏感;在某种次要程度上也体现在它使用标点符号相对较少 (更多用关键字而不是符号)。我倾向于认为这些特性的目的是提高可读性;但这在很大程度上取决于个人的爱好。

从流和栈开始

作为一个简单的小例子,让我来介绍一个特别简单的工具的多个版本,它可以列出文件并对其进行编号。Rexx 和 shell 脚本的一个共同点是,它用于和底层操作系统打交道的函数相对很少 —— 几乎仅限于可以打开、读和修改文件。对大部分其他事情,您需要依赖于外部实用程序来完成手头上的工作。实用程序 numbered-1.rexx 只是处理 STDIN:

清单 1. numbered-1.rexx
#!/usr/bin/rexx
DO i=1 UNTIL lines()==0
  PARSE LINEIN line
  IF line\="" THEN
    SAY i || ") " || line
END

无处不在的指令 PARSE 可以从各种不同的源读入。在这里,它将 STDIN 的下一行赋给变量 line 。我们也会检查某一行是否为空,如果是空的话,则不进行显示和编号。例如,与 ls 组合使用我们可以得到:

清单 2. 将命令通过管道传输到 numbered-1
$ ls | ./numbered-1.rexx
1) ls-1.rexx
2) ls-2.rexx
3) ls-3.rexx
4) ls-4.rexx
5) ls-5.rexx
6) ls-6.rexx
7) numbered-1.rexx
8) numbered-2.rexx

同样您可以方便地将任何其他命令通过管道传输进来。

Rexx 的核心概念之一是巧妙地处理多个栈或流。类似 bash 的风格,在 Rexx 中任何没有被识别为内部指令或函数的内容,都被假定为一个外部实用程序。没有特定的函数或语法用于调用外部命令。Regina 的实用程序 rxqueue 可以将输出压入 Rexx 栈,利用它我们可以写一个这样的“有编号的 ls”实用程序:

清单 3. ls-1.rexx
#!/usr/bin/rexx
"ls | rxqueue"
DO i=1 WHILE queued() \= 0
  PARSE PULL line
  SAY i || ") " || line
END

Rexx 中有一些指令可以显式地指定要操作的栈;但是其他指令操作是在您用 ADDRESS 指令配置的 环境 中进行。STDIN、STDOUT、STDERR、文件和内存内部数据栈都以统一而优雅的方式处理。上面我们用的是外部 rxqueue 实用程序,不过在 Rexx 内部我们同样可以重定向实用程序的输出。例如:

清单 4. ls-2.rexx
#!/usr/bin/rexx
ADDRESS SYSTEM ls WITH OUTPUT FIFO '' ERROR NORMAL
DO i=1 WHILE queued() \= 0
  PARSE PULL line; SAY i || ") " || line; END

可能看起来 ADDRESS 命令只是获得 ls 实用程序的输出;但实际上它改变了后面外部调用的整体执行环境。这些例子运行于固定大小写/区分大小写 的文件系统上;在很多系统下,您将不得不保持“ls”的大小写去引用它。这个执行相同:

清单 5. ls-5.rexx
#!/usr/bin/rexx
ADDRESS SYSTEM WITH OUTPUT FIFO '' ERROR NORMAL
ls
DO i=1 WHILE queued()\=0; PARSE PULL ln; SAY i||") "||ln; END

任意接下来的外部命令,如果他们是在默认的 SYSTEM 环境下运行,将把它们的输入定向到默认的 FIFO(先入先出,first-in-first-out)。您也可以改为输出到 LIFO(或者是命名的,或者是默认的)—— 不同之处是,FIFO 向栈的“底部”添加,而 LIFO 向“顶部”添加。指令 PUSHQUEUE 对应的是对栈的 LIFO 和 FIFO 操作。指令 PULL 或者 PARSE PULL 从栈顶取得一个字符串。

另一个值得说明的有用栈是 Rexx 脚本的命令行参数。例如,我们可能希望在我们的编号实用程序中执行任意的命令,而不是只执行 ls :

清单 6. numbered-1.rexx
#!/usr/bin/rexx
PARSE ARG cmd
ADDRESS SYSTEM cmd WITH OUTPUT FIFO ''
DO i=1 WHILE queued()\=0; PARSE PULL ln; SAY i||") "||ln; END

脚本执行:

清单 7. 向 numbered-1 传递命令
$ ./numbered-2.rexx ps -a -x
1)   PID  TT  STAT      TIME COMMAND
2)     1  --  Ss     0:00.00 /sbin/init
3)     2  --  Ss     0:00.19 /sbin/mach_init
4)    51  --  Ss     0:01.95 kextd
[...]

PARSE PULL 可以用于从用户输入中得到行。按照参数 cmd 执行的例子,您可以在 Rexx 中写一个 shell 或者交互式的环境(或许会运行外部的实用程序或者内置命令,正如同 bash )。

词干变量和关联数组

在 Rexx 中 —— 有些类似于在 TCL 中 —— 在很大程度上 一切都是字符串。由多行组成的栈和流给您一个简单的字符串列表或者数组。但是,通常, 根据需要字符串完全可以有像其他数据类型一样行为。例如,包含有对一个数字(阿拉伯数字、十进制数、指数“e”,等等)适当描述的字符串可以用于数学运算。对于处理报告、日志文件等类似工作,这是您实际上期望的行为。

不过,Rexx 确实有一个另外的标准数据类型:关联数组。在 Rexx 中,它们被命名为“词干变量”,但是其概念非常类似于很多其他语言中的程序库。词干变量的语法对 OOP 语言 (如 Java、Python 或者 Rudy) 的用户来说也会惊人地熟悉:一个句点将“对象”和它们的“属性”隔开。这不是真正的面向对象,但是其语法确实(偶然地)突出了对象与特别健壮的程序库的相似程度。 其实有对 Rexx 的 OOP 扩展,但是本文将不会介绍它们。

不是每一个字符串都是合法的 Rexx 符号 —— 为限制程序库中的关键字 —— 但是相对于大部分语言,Rexx 在符号命名方面非常自由。举例来说:

清单 8. 在 Rexx 中使用词干变量
$ cat stems.rexx
#!/usr/bin/rexx
foo.X_!1.bar = 1
foo.X_!1.23 = 2
foo.fop.fip = 3
foo.fop = 4
SAY foo.X_!1.bar # foo.X_!1.23 # foo.fop.fip # foo.fop # foo.fop.NOPE
$ ./stems.rexx
1 # 2 # 3 # 4 # FOO.FOP.NOPE

在这个例子中突出说明了几个特性。我们同时为词干及其复合变量赋值(举例来说, foo.fopfoo.fop.fip )。同时要注意,未定义的符号 foo.fop.nope 尽管没有赋值,但这样表示只是它本身的拼写。这使我们在大部分情况下可以不用去引用。在大部分 Rexx 上下文中,名称被规格化为大写的。

一个有用的技巧是为带句点的词干赋值,这个值将成为基于这个词干的复合名字的默认值。在下一个例子中,我们也会利用这一功能,将一个复合名的顺序编号的符号作为输出环境变量来 ADDRESS

清单 9. ls-3.rexx
#!/usr/bin/rexx
ls. = UNDEF
ADDRESS SYSTEM ls WITH OUTPUT STEM ls.
DO i=1
    IF ls.i == UNDEF THEN LEAVE
    SAY i || ") " || ls.i
END

当循环进行到某个没有被外部 ls 实用程序的输出所替换的复合变量名时,我们就会检测到“UNDEF”默认值并退出循环(不过,如果输出中可能包含那个字符串,会发生错误冲突)。

Rexx 还有一个错误处理系统,让您 SIGNAL 情况并适当地处理它们。不用再检查默认的复合值,您也可以捕获对未定义变量的访问。举例来说:

清单 10. ls-6.rexx
#!/usr/bin/rexx
ADDRESS SYSTEM ls WITH OUTPUT STEM ls.
SIGNAL ON NOVALUE NAME quit
DO i=1
    SAY i || ") " || ls.i
END
quit:

为完成我们的 ls 变量,接下来用一个文件作为它的 I/O:

清单 11. ls-4.rexx
#!/usr/bin/rexx
ADDRESS SYSTEM ls WITH OUTPUT STREAM files
DO i=1
    line = linein(files)
    IF line = "" THEN LEAVE
    SAY i || ") " || line
END
rm files

由于输出流是一个规则的文件,因此最好在结束时将其删除。

文本处理函数

通过前面简短的例子,读者将会对 Rexx 这门编程语言有一些感觉。当然,您也可以定义您自己的过程和函数 —— 在单独的模块文件中,如果您希望的话 —— 然后以 CALL 指令或者以带括号的参数方式来调用它们,正如本文中一些使用标准函数的例子。

或许,作为一门文本处理语言,Rexx 的最强大之处在于它所具备的实用的内置字符串处理函数。可能有超过一半的标准 Rexx 函数用于处理字符串,其他的大部分被用来以极其显而易见的方式处理位向量。此外,甚至位向量也经常作为由 1 和 0 构成的向量来处理(或者读入):

清单 12. bits.rexx
#!/usr/bin/rexx
SAY b2c('01100001') b2c('01100010')         /* --> a b */
SAY bitor(b2c('01100001'), b2c('01100010')) /* --> c   */
SAY bitor('a','b')                          /* --> c   */
EXIT
/* Function in ARexx, but not ANSI Rexx */
b2c: PROCEDURE
  ARG bits
  return x2c(b2x(bits))

Rexx 的文本处理函数的一个令人喜爱的特性是,它可以自然地处理以空格隔开的 组成的行。对文本的报表和日志文件来说,轻松忽略掉没用的空格是非常有必要的 —— ‘awk’有类似的功能,但是 Python 的 string.split() 在描述同样的操作时更“繁杂”。实际上,在 Rexx 中“数组”就是由空格隔开的字符串。 PULL 指令将以一个通用的模板模式从一行中得到变量,并支持最小程度的词拆分:

清单 13. pushpull.rexx
#!/usr/bin/rexx
PUSH "a b c d e f"
PULL x y " C " z   /* pull x and y before the C, remainder into z */
SAY x # y # z    /* --> A # B # D E F */

可以对那些可能是或者不是通过模板得到的字符串进行进一步完美的拆分。 wordpos()word()wordindex() 或者 words()subword() 这些函数让您可以访问字符串中的“词”,就像是它们构成一个列表一样,举例来说:

清单 14. 处理字符串中的词
seuss = "The cat in the hat came back"
thehat = wordpos('the hat', seuss)
SAY "'came'" is wordlength(seuss, thehat+2) letters long
/* --> 'came' IS 4 LETTERS LONG */

当然,您也可以得到足够的面向字符的函数集。可以同样容易地使用 reverse()right()justify()center()pos() 或者 substr() (还有其他的)函数来处理字符位置。

使用另外一些内置的函数,您可以以一种灵活的、面向报表的方法处理日期和数字。更确切地说,可以以各种格式对任意(可配置)精度的数字进行读和写。类似的,使用标准函数调用可以对日期进行读写和各种格式间的转换(例如,一周中的第几天、一个世纪中的第几天、欧洲时间相对于美国时间,等等)。处理日期和数字的灵活性,在编写系统脚本和处理日志文件时,其必要性可能比不上处理来自数据库应用的半结构化输出报表。但是当您需要这个功能时,使用充分测试过的内置函数要比编写您自己的特制的转换器和格式化工具要稳妥得多。

结束语

由于来自 IBM “大型机”环境比重多于 Unix 系统,Rexx 对很多 Linux 程序员和系统管理员是鲜为人知的。但是在 Linux 中仍然有一个重要的领域,在这里,相对于“过轻量级”的 bashksh shell,或者“过重量级”的解释性编程语言例如 Python、Perl、Ruby、TCL,或者可能还有 Scheme,Rexx 是更好的脚本解决方案。作为可以快速且简单易读的脚本,在对外部进程的输入和输出进行文本处理方面,Rexx 无懈可击,而且学习和安装也不困难。


相关主题

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
  • Regina是本文编写和测试例子时所用的 LGPL Rexx 实现,并且,对大多数人来说,它是安装环境的最好选择。Regina 可以用于极其广泛的平台,而且完全遵从 ANSI 标准(添加了一些扩展)。站点中还包括很多指向有用的 Rexx 库的链接,这些库可用于 Tk、Curses、SQL 等应用程序领域。
  • NetRexx是一个为 Java 虚拟机编译 Rexx 代码的 IBM 项目。尽管显然可以用 NetRexx 来满足客户机/工作站脚本的需要,但是 NetRexx 主要致力于支持服务器端 Java 应用程序(JSP 和相关技术)的更快速开发。
  • Rexx Language Association是一个普遍支持 Rexx 编程语言的团体。他们的站点上有各种各样的指向有用的库和其他资源的链接,包括详细的 Rexx ANSI 标准
  • IBM Hursley 还维护着一个极好的 Rexx 参考资料列表。您可以找到教程、参考资料和指向各种新库的链接。Hursley 站点还有一个关于 Rexx 的历史的网页。
  • IBM 提供当前 (as-is) 版本的 Object REXX 可用于 Linux其他平台。Object REXX 是一门面向对象的编程语言,适合于初学者和经验丰富 OO 程序员,它与先前版本的经典 REXX 向上兼容,并向现有的应用程序,比如 DB2、C 和 C++ 应用程序,提供了编程接口。
  • IBM REXX 家族运行于主机系统,比如 VM/ESA、VSE/ESA 和 MVS/ESA,以及工作站环境,比如 AIX、OS/2、Linux 和 Windows。
  • 还有一个用于 Apache Web 服务器的 Mod_Rexx包。
  • developerWorks Linux 专区查找更多为 Linux 开发者准备的资料。
  • 在 Developer Bookstore 的 Linux 专区您可以找到很多精选的关于 Linux 的书籍。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=21355
ArticleTitle=人人可用的 Rexx
publish-date=03012004