一、selpg指令简介
selpg 是一个自定义命令行程序,全称select page,即从源(标准输入流或文件)读取指定页数的内容到目的地(标准输出流或给给打印机打印)。selpg 是以在 Linux 中创建命令的事实上的约定为模型创建的,这些约定包括:
- 独立工作
- 在命令管道中作为组件工作(通过读取标准输入或文件名参数,以及写至标准输出和标准错误)
- 接受修改其行为的命令行选项
该实用程序从标准输入或从作为命令行参数给出的文件名读取文本输入。它允许用户指定来自该输入并随后将被输出的页面范围。
二、具体参数用法介绍
“-sNumber”和“-eNumber”强制选项:
selpg 要求用户用两个命令行参数“-sNumber”(例如,“-s10”表示从第 10 页开始)和“-eNumber”(例如,“-e20”表示在第 20 页结束)指定要抽取的页面范围的起始页和结束页。selpg 对所给的页号进行合理性检查;换句话说,它会检查两个数字是否为有效的正整数以及结束页是否不小于起始页。这两个选项,“-sNumber”和“-eNumber”是强制性的,而且必须是命令行上在命令名 selpg 之后的头两个参数:
1 |
|
(... 是命令的余下部分,下面对它们做了描述)。
“-lNumber”和“-f”可选选项:
selpg 可以处理两种输入文本:
类型 1:该类文本的页行数固定。这是缺省类型,因此不必给出选项进行说明。也就是说,如果既没有给出“-lNumber”也没有给出“-f”选项,则 selpg 会理解为页有固定的长度(每页 72 行)。
选择 72 作为缺省值是因为在行打印机上这是很常见的页长度。这样做的意图是将最常见的命令用法作为缺省值,这样用户就不必输入多余的选项。该缺省值可以用“-lNumber”选项覆盖,如下所示:
1 |
|
这表明页有固定长度,每页为 66 行。
类型 2:该类型文本的页由 ASCII 换页字符(十进制数值为 12,在 C 中用“\f”表示)定界。该格式与“每页行数固定”格式相比的好处在于,当每页的行数有很大不同而且文件有很多页时,该格式可以节省磁盘空间。在含有文本的行后面,类型 2 的页只需要一个字符 ― 换页 ― 就可以表示该页的结束。打印机会识别换页符并自动根据在新的页开始新行所需的行数移动打印头。
将这一点与类型 1 比较:在类型 1 中,文件必须包含 PAGELEN - CURRENTPAGELEN 个新的行以将文本移至下一页,在这里 PAGELEN 是固定的页大小而 CURRENTPAGELEN 是当前页上实际文本行的数目。在此情况下,为了使打印头移至下一页的页首,打印机实际上必须打印许多新行。这在磁盘空间利用和打印机速度方面效率都很低(尽管实际的区别可能不太大)。
类型 2 格式由“-f”选项表示,如下所示:
1 |
|
该命令告诉 selpg 在输入中寻找换页符,并将其作为页定界符处理。
注:“-lNumber”和“-f”选项是互斥的。
“-dDestination”可选选项:
selpg 还允许用户使用“-dDestination”选项将选定的页直接发送至打印机。这里,“Destination”应该是 lp 命令“-d”选项(请参阅“man lp”)可接受的打印目的地名称。该目的地应该存在 ― selpg 不检查这一点。由于本地实验并没有使用打印机这种设备,参考了其他同学的做法后,我将Destination作为一个关键字字符串,我把selpg 输出给了命令 grep,查找出selpg的输出中与文件keyword内容相关的部分,再输出到屏幕。
三、设计分析
- 需要引入的包:(根据网站提供的c语言源码可以大致了解整个设计过程并选择我们所需要的包https://www.ibm.com/developerworks/cn/linux/shell/clutil/selpg.c)
- 设计我们所需要的结构体:分析可得用户必须输入的参数有起始页和结束页,因此我们的结构体里应该有记录这两个数据的变量,其次,用户的可选参数中包括打印的方式,每一页的行数,所使用打印机等等参数,因此也将他们包含在结构体中。
- 由于用户在命令行中输入相关指令,为了获取对应的参数,我们需要用到Go语言一个独特的包“flag”,利用其中的flag.xxxVar函数,就可以将相应标签后面的对应内容提取出来,例如:
flag.IntVar(&sa.startPage,"s",-1,"the starting page")
这一行代码表示我们将一个标签为s的命令行参数与sa.startPage绑定,其描述为“the starting page”,如果用户没有输入带有-sx格式的命令,则将其默认置为-1。将我们结构体中所有的都是用相同的方法完成初始化并绑定。
-
考虑到用户初次使用该指令的情况,当用户输入错误格式的指令时,我们需要有一个函数来告诉用户正确的使用方法,于是我们可以添加一个名为usage的函数
func usage() { fprintf(stderr, "\nUSAGE: %s -sstart_page -eend_page [ -f | -llines_per_page ]" " [ -ddest ] [ in_filename ]\n", progname); }
(该函数来源于网站提供的c语言源码)其中带有[]的参数为可选参数,其他参数为必填参数。
-
在获取参数的过程中,我们并不仅仅只是完成数据的提取,若是用户输入了错误的参数,我们也需要给出提示并请求用户重新输入,所以需要对我们获取到的参数进行处理。
func process_args (sa selpgArgs, notFlagNum int) { start_end_valid := sa.startPage <= sa.endPage && sa.startPage >=1 num_valid := notFlagNum==1 || notFlagNum ==0 l_f := !(sa.pageType == "f" && sa.pageLen != -1) if !start_end_valid || !num_valid || !l_f { usage() os.Exit(1) } }
该函数用于判断起始页和结束页之间的逻辑是否正确(起始页不能大于结束页),同时由于整个slepg的命令中只有一个非标签参数,即:需要打印的文件的名称,所以如果用户输入了超过一个非标签参数也是错误的。不仅如此,若用户选择了-f的打印格式,但是 又指定了每一页的行数,我们也是不允许的。
-
完成了参数的处理之后,我们就基本获取到了所有的信息,剩下的部分便是完成打印的工作。首先我们需要一个记录当前行数和页数的变量来确定我们下一步所需要打印的东西:
currentPage := 1 currentLine := 0
之后,我们需要打开用户指定的文件
if sa.inFilename !="" { fin, err = os.Open(sa.inFilename) if err != nil { fmt.Fprintf(os.Stderr, "selpg: could not open input file \"%s\"\n", sa.inFilename) fmt.Println(err) usage() os.Exit(1) } defer fin.Close() }
再利用bufio包中的scanner和line等函数将内容一行行的输出出来即可。
四、实验结果展示
如此一来即可完成相应分页符的插入来测试-f指令。
- 未输入必备参数的情况:
- 输入起始页大于结束页:
- 输入结束页大于总页数:
- 输入多于一个非标签参数:
- 所选文件不存在或者没有权限读取:
- 指定输出格式(-l指令):
- 指定输出格式(-f指令并指定行数):
- 指定输出格式(-f指令正确用法):测试文件内容如图: 在创建测试文件时,需要注意的是我们并不能直接用文本编辑器等类似软件直接插入分页符,需要用到一个c++程序使用文件流操作直接在测试文件中写入分页符‘\f’。
#include<iostream> #include <fstream> using namespace std; int main(void){ ofstream out("testFile"); if(!out) { cout<<"file can't open"<<endl; } char c = 12; out<<"data"<<c<<endl; out<<"coming"<<c<<"this is the last line"<<c<<endl; out.close(); return 0; }
- 输出重定向:
五、完整代码展示
package main
import (
"flag"
"fmt"
"strings"
"bufio"
"io"
"os"
"os/exec"
)
/*define a structer to solve this problem*/
type slepg struct {
start_page int
end_page int
page_len int
page_type string
print_des string
inFilename string
}
func main () {
sa := new(slepg)
/*initiate the parameter*/
flag.IntVar(&sa.start_page,"s",-1,"the start page")
flag.IntVar(&sa.end_page,"e",-1,"the end page")
flag.IntVar(&sa.page_len,"l",72,"the page length")
flag.StringVar(&sa.print_des,"d","","the printer")
/*check if there is command -f in terminal*/
exist_f :=flag.Bool("f",false,"")
flag.Parse()
if *exist_f {
sa.page_type = "f"
sa.page_len=-1
} else {
sa.page_type = "l"
}
/*this is the situation that user input the
filename directly without any parameter*/
if flag.NArg() == 1 {
sa.inFilename = flag.Arg(0)
} else {
sa.inFilename = ""
}
process_args(*sa,flag.NArg())
exe(*sa)
}
func usage() {
fmt.Fprintf(os.Stderr, "\nUSAGE: ./selpg [-s start_page] [-e end_page] [ -l lines_per_page | -f ] [ -d dest ] [ in_filename ]\n")
}
func process_args (sa slepg, notFlagNum int) {
start_end_valid := sa.start_page <= sa.end_page && sa.start_page >=1
num_valid := notFlagNum==1 || notFlagNum ==0
l_f := !(sa.page_type == "f" && sa.page_len != -1)
if !start_end_valid || !num_valid || !l_f {
usage()
os.Exit(1)
}
}
func exe (sa slepg) {
currentPage := 1
currentLine := 0
fin := os.Stdin
fout := os.Stdout
var inpipe io.WriteCloser
var err error
/*check if user assign the file*/
if sa.inFilename !="" {
fin, err = os.Open(sa.inFilename)
if err != nil {
fmt.Fprintf(os.Stderr, "selpg: could not open input file \"%s\"\n", sa.inFilename)
fmt.Println(err)
usage()
os.Exit(1)
}
defer fin.Close()
}
/*confirm the printer, because there is no printer, so we use pipe to check it*/
if sa.print_des !="" {
cmd := exec.Command("grep","-nf","keyword")
inpipe, err = cmd.StdinPipe()
if err !=nil {
fmt.Println(err)
os.Exit(1)
}
defer inpipe.Close()
cmd.Stdout = fout
cmd.Start()
}
if sa.page_type == "l" {
line := bufio.NewScanner(fin)
for line.Scan() {
if currentPage >= sa.start_page && currentPage <= sa.end_page {
fout.Write([]byte(line.Text()+"\n"))
if sa.print_des !="" {
inpipe.Write([]byte(line.Text()+"\n"))
}
}
currentLine++
if currentLine%sa.page_len == 0 {
currentLine = 0
currentPage++
}
}
} else {
rd :=bufio.NewReader(fin)
for {
page, ferr := rd.ReadString('\f')
if ferr != nil || ferr == io.EOF {
if ferr == io.EOF {
if currentPage >=sa.start_page && currentPage <= sa.end_page {
fmt.Fprintf(fout,"%s",page)
}
}
break
}
page = strings.Replace(page,"\f","",-1)
currentPage++
if currentPage >= sa.start_page && currentPage <= sa.end_page {
fmt.Fprintf(fout,"%s",page)
}
}
}
if currentPage < sa.end_page {
fmt.Fprintf(os.Stderr, "./selpg: end_page (%d) greater than total pages (%d), less output than expected\n", sa.endPage, currentPage)
}
}