“-sNumber”和“-eNumber”强制选项:
selpg 要求用户用两个命令行参数“-sNumber”(例如,“-s10”表示从第 10 页开始)和“-eNumber”(例如,“-e20”表示在第 20 页结束)指定要抽取的页面范围的起始页和结束页。selpg 对所给的页号进行合理性检查;换句话说,它会检查两个数字是否为有效的正整数以及结束页是否不小于起始页。这两个选项,“-sNumber”和“-eNumber”是强制性的,而且必须是命令行上在命令名 selpg 之后的头两个参数:
$ selpg -s10 -e20 ...
(... 是命令的余下部分,下面对它们做了描述)。
“-lNumber”和“-f”可选选项:
selpg 可以处理两种输入文本:
类型 1:该类文本的页行数固定。这是缺省类型,因此不必给出选项进行说明。也就是说,如果既没有给出“-lNumber”也没有给出“-f”选项,则 selpg 会理解为页有固定的长度(每页 72 行)。
选择 72 作为缺省值是因为在行打印机上这是很常见的页长度。这样做的意图是将最常见的命令用法作为缺省值,这样用户就不必输入多余的选项。该缺省值可以用“-lNumber”选项覆盖,如下所示:
$ selpg -s10 -e20 -l66 ...
这表明页有固定长度,每页为 66 行。
类型 2:该类型文本的页由 ASCII 换页字符(十进制数值为 12,在 C 中用“\f”表示)定界。该格式与“每页行数固定”格式相比的好处在于,当每页的行数有很大不同而且文件有很多页时,该格式可以节省磁盘空间。在含有文本的行后面,类型 2 的页只需要一个字符 — 换页 — 就可以表示该页的结束。打印机会识别换页符并自动根据在新的页开始新行所需的行数移动打印头。
将这一点与类型 1 比较:在类型 1 中,文件必须包含 PAGELEN - CURRENTPAGELEN 个新的行以将文本移至下一页,在这里 PAGELEN 是固定的页大小而 CURRENTPAGELEN 是当前页上实际文本行的数目。在此情况下,为了使打印头移至下一页的页首,打印机实际上必须打印许多新行。这在磁盘空间利用和打印机速度方面效率都很低(尽管实际的区别可能不太大)。
类型 2 格式由“-f”选项表示,如下所示:
$ selpg -s10 -e20 -f ...
该命令告诉 selpg 在输入中寻找换页符,并将其作为页定界符处理。
注:“-lNumber”和“-f”选项是互斥的。
“-dDestination”可选选项:
selpg 还允许用户使用“-dDestination”选项将选定的页直接发送至打印机。这里,“Destination”应该是 lp 命令“-d”选项(请参阅“man lp”)可接受的打印目的地名称。该目的地应该存在 — selpg 不检查这一点。在运行了带“-d”选项的 selpg 命令后,若要验证该选项是否已生效,请运行命令“lpstat -t”。该命令应该显示添加到“Destination”打印队列的一项打印作业。如果当前有打印机连接至该目的地并且是启用的,则打印机应打印该输出。这一特性是用 popen() 系统调用实现的,该系统调用允许一个进程打开到另一个进程的管道,将管道用于输出或输入。在下面的示例中,我们打开到命令
$ lp -dDestination
的管道以便输出,并写至该管道而不是标准输出:
selpg -s10 -e20 -dlp1
该命令将选定的页作为打印作业发送至 lp1 打印目的地。您应该可以看到类似“request id is lp1-6”的消息。该消息来自 lp 命令;它显示打印作业标识。如果在运行 selpg 命令之后立即运行命令 lpstat -t | grep lp1,您应该看见 lp1 队列中的作业。如果在运行 lpstat 命令前耽搁了一些时间,那么您可能看不到该作业,因为它一旦被打印就从队列中消失了。
输入处理
一旦处理了所有的命令行参数,就使用这些指定的选项以及输入、输出源和目标来开始输入的实际处理。
selpg 通过以下方法记住当前页号:如果输入是每页行数固定的,则 selpg 统计新行数,直到达到页长度后增加页计数器。如果输入是换页定界的,则 selpg 改为统计换页符。这两种情况下,只要页计数器的值在起始页和结束页之间这一条件保持为真,selpg 就会输出文本(逐行或逐字)。当那个条件为假(也就是说,页计数器的值小于起始页或大于结束页)时,则 selpg 不再写任何输出。瞧!您得到了想输出的那些页。
代码注释
是详细研究源代码的时候了。此刻,如果您还没有 selpg.c,则可能需要下载它(请参阅参考资料)。我将向您介绍代码,一次介绍一段。
以注释“==== includes =====”开始的行
这些行指定所需的头文件。stdio.h 是 C 标准输入输出库的头文件。文件的打开、关闭、读和写函数(fopen()、fclose()、fgets()、getc() 等等)、printf() 函数系列和 setvbuf() 函数都需要它。
stdlib.h 是 atoi() 函数(将 ASCII 码转换为整数)所需要的,该函数将字符串转换为整数。
string.h 是 strcpy() 和 strcmp() 这样的字符串函数所需要的。
unistd.h 是 access() 函数所需要的。
limits.h 是定义 INT_MAX 所需要的,INT_MAX 指定您的编译器/操作系统/硬件平台上 int 的最大可取值。使用它而不是硬编码的值可以提高代码的可移植性。
assert.h 用于 assert() 调试宏。
errno.h 是声明 errno 所需要的,errno 是全局系统调用错误号变量(下面会有更多介绍)。
以注释“==== types =====”开始的行
这里只定义了一个类型,即:selpg_args 结构。指向该类型变量的指针被传递到 process_args() 函数,返回该指针时,它包含从参数处理过程获得的值。typedef 用来给类型一个短名 sp_args。
我们还定义了宏 INBUFSIZ,它是在读取输入时用作缓冲区的字符数组的大小。这是为了有更好的性能。
以注释“==== globals ======”开始的行
Progname 是保存名称(命令就是通过该名称被调用)的全局 char* 变量,作为在错误消息中显示之用。用这种方法,即使您将 selpg 命令重命名为别的名称,新的名称也将在消息中显示;您不必修改该代码。
以注释“==== prototypes ===”开始的行
按照 ANSI C 约定,这些行声明了代码中所有函数的函数原型。这是现在的常规作法,它可以帮助编译器检测函数定义/声明和函数使用之间的类型不匹配。
main() 函数