作业内容
Go Online 传送门
Github 传送门
实验内容
使用 golang 开发 Linux 命令行实用程序中的 selpg
- 请按文档 使用 selpg 章节要求测试你的程序
- 请使用 pflag 替代 goflag 以满足 Unix 命令行规范, 参考:Golang之使用Flag和Pflag
- golang 文件读写、读环境变量,请自己查 os 包
- “-dXXX” 实现,请自己查 os/exec 库,例如案例 Command,管理子进程的标准输入和输出通常使用 io.Pipe,具体案例见 Pipe
实验过程
首先selpg提供了c语言版的源代码:源代码传送门
所有我只需要根据c语言源代码改写成go语言版本即可。
首先需要导入pflag,在终端上执行如下命令:
go get github.com/spf13/pflag
然后我们go代码需要的包如下:
import (
"bufio"
"github.com/spf13/pflag"
"fmt"
"io"
"math"
"os"
"os/exec"
)
在c语言版本中,定义了一个处理参数用的结构体:
struct selpg_args
{
int start_page;
int end_page;
char in_filename[BUFSIZ];
int page_len; /* default value, can be overriden by "-l number" on command line */
int page_type; /* 'l' for lines-delimited, 'f' for form-feed-delimited */
/* default is 'l' */
char print_dest[BUFSIZ];
};
所以我们也需要在go语言版本中定义一个这样的结构体,类型基本一样,但由于go没有提供char类型的变量,所以改成使用是string类型的变量
type selpg_args struct{
start_page int
end_page int
in_filename string
page_len int /* default value, can be overriden by "-l number" on command line */
page_type string /* 'l' for lines-delimited, 'f' for form-feed-delimited */
/* default is 'l' */
print_dest string
}
接着是定义帮助函数usage,可以更好地帮助用户使用该程序:
func usage() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, os.Args[0]+" -sstart_page_num -eend_page_num [ -f | -llines_per_page ] [ -ddest ] [ in_filename ]\n")
pflag.PrintDefaults()
}
对于读取参数部分,pflag包提供了简单命令的接口,我们直接使用这些函数,比c语言版本简化了许多:
对于pflag中的XXXVarP函数:
- 第一个参数是需要与参数名绑定的变量
- 第二个参数是参数名
- 第三个参数是参数名的简写
- 第四个参数是默认值
- 第五个参数是help输出的信息
func Init(args *selpg_args){
pflag.Usage = usage
pflag.IntVarP(&(args.start_page), "start", "s", -1, "start page")
pflag.IntVarP(&(args.end_page), "end", "e", -1, "end page")
pflag.IntVarP(&(args.page_len), "line", "l", 72, "page len")
pflag.StringVarP(&(args.print_dest), "destionation", "d", "", "print destionation")
pflag.StringVarP(&(args.page_type), "type", "f", "l", "type of print")
}
然后是check函数,对于c语言版本的process_args函数,主要用于检查参数的合法性:参数的检查包括起始页和结束页是否合法;每一页的长度是否合法;页的格式是否合法等,这里只提供部分代码,详细参考文末的GitHub链接:
//Not enough args
if len(os.Args) < 3 || args.start_page == -1 || args.end_page == -1{
fmt.Fprintf(os.Stderr, "ERROR: %s not enough parameters!\n", os.Args[0])
fmt.Fprintf(os.Stderr, "You should input the parameters start page and end page!\n")
usage()
os.Exit(0)
}
//Page's index error
if args.start_page <= 0{
fmt.Fprintf(os.Stderr, "ERROR: Invalid start page number! Can not less than or equal to 0!\n")
os.Exit(1)
}
if args.end_page <= 0{
fmt.Fprintf(os.Stderr, "ERROR: Invalid end page number! Can not less than or equal to 0!\n")
os.Exit(2)
}
注意在上述代码中,输出错误信息的时候要使用fmt.Fprintf(os.Stderr, "xxx")
函数,只有这样,在后续重定向输出错误信息到文件中的操作才能完成。
接着是处理输入的函数process_input,这个函数主要负责根据参数的输入判断输入是从文件读取,还是标准输入,输出是标准输出还是输出到文件中。首先是输入的处理:
var fin *os.File
if args.in_filename != ""{
var opErr error
fin, opErr = os.Open(args.in_filename)
if opErr != nil{
fmt.Fprintf(os.Stderr, "\nERROR! Can not open the input file: %s\n", args.in_filename)
os.Exit(9)
}
}else{
fin = os.Stdin
}
finBuffer := bufio.NewReader(fin)
根据in_filename来决定是标准输入还是文件输入,如果是文件输入,则需要打开用户所给的文件,打开失败则显示错误信息,成功后,调用NewReader函数读入到buffer中。输出与输入类似,这里就不再细讲了。
接着是对输入转换到输出的处理,对于是‘l’类型的,很简单,基本跟c语言版本的一致,只需把语法改成go语法的就好了:
line_ctr是当前的行数,page_ctr是页数,根据用户给定的参数page_len和start_page、end_page来输出
if args.page_type == "l" {
line_ctr := 0
page_ctr := 1
for {
line, crc := finBuffer.ReadString('\n')
if crc != nil {
break
}
line_ctr ++
if line_ctr > args.page_len {
page_ctr ++
line_ctr = 1
}
if (page_ctr >= args.start_page) && (page_ctr <= args.end_page) {
_, err := fout.Write([]byte(line))
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR!", err)
os.Exit(11)
}
}
}
}
如果是‘f’类型的就更简单了,与上述代码类似,只是少了line_ctr,以及写的方式是page:
page_ctr := 1
for{
page, crc := finBuffer.ReadString('\f')
if crc != nil {
break
}
page_ctr ++
if ( (page_ctr >= args.start_page) && (page_ctr <= args.end_page) ){
_, err := fout.Write([]byte(page))
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR!", err)
os.Exit(12)
}
}
}
最后记得把文件给关闭:
fin.Close()
fout.Close()
最后是主函数,只需新建一个类型为selpg_args*的变量,依次执行上述函数即可,值得注意的是还得获取输入文件名这一个操作:
func main() {
args := new(selpg_args)
//Get args
Init(args)
pflag.Parse()
//Get other args
othersArg := pflag.Args()
if pflag.NArg() > 0 {
args.in_filename = othersArg[0]
} else {
args.in_filename = ""
}
//Check the grammar
check(args)
process_input(args)
}
实验结果
-
首先测试了一下没传参数的结果:
-
$ selpg -s1 -e1 input_file
该命令将把“input_file”的第 1 页写至标准输出(也就是屏幕),因为这里没有重定向或管道。
可以对比一下test.txt文件,与原文件前72行一模一样:
-
$ selpg -s1 -e1 < input_file
该命令与示例 1 所做的工作相同,但在本例中,selpg 读取标准输入,而标准输入已被 shell/内核重定向为来自“input_file”而不是显式命名的文件名参数。输入的第 1 页被写至屏幕。
与上面一条命令输出一致:
-
$ other_command | selpg -s10 -e20
“other_command”的标准输出被 shell/内核重定向至 selpg 的标准输入。将第 10 页到第 20 页写至 selpg 的标准输出(屏幕)。
首先在终端输入了
ls
命令,输出当前路径的所有文件,然后再执行ls | selpg -s10 -e20
,可以看到按行输出了每一个文件名:
-
$ selpg -s10 -e20 input_file >output_file
selpg 将第 10 页到第 20 页写至标准输出;标准输出被 shell/内核重定向至“output_file”。
由于我的test.txt长度没有这么长,所以起始页都设为第一页,终止页为第二页:
打开当前路径下新出现的output_file.txt文件:
与test.txt文件进行对比,发现一致。 -
$ selpg -s10 -e20 input_file 2>error_file
selpg 将第 10 页到第 20 页写至标准输出(屏幕);所有的错误消息被 shell/内核重定向至“error_file”。请注意:在“2”和“>”之间不能有空格;这是 shell 语法的一部分(请参阅“man bash”或“man sh”)。
因为只是我输入的是没有错误的命令,所以error_file没有任何文字,接着尝试故意输入一条错误的命令,来观察error_file有没有改变:
我将输入文件名做了修改,可以看到error_file中出现了错误信息,无法打开输入文件。 -
$ selpg -s10 -e20 input_file >output_file 2>error_file
selpg 将第 10 页到第 20 页写至标准输出,标准输出被重定向至“output_file”;selpg 写至标准错误的所有内容都被重定向至“error_file”。当“input_file”很大时可使用这种调用;您不会想坐在那里等着 selpg 完成工作,并且您希望对输出和错误都进行保存。
命令前没加./所以出错了,也可以看到,错误信息输出到error_file中了。
执行了正确的命令,output_file中有了输出。 -
$ selpg -s10 -e20 input_file >output_file 2>/dev/null
selpg 将第 10 页到第 20 页写至标准输出,标准输出被重定向至“output_file”;selpg 写至标准错误的所有内容都被重定向至 /dev/null(空设备),这意味着错误消息被丢弃了。设备文件 /dev/null 废弃所有写至它的输出,当从该设备文件读取时,会立即返回 EOF。
可以看到,即使input_file是不存在的,但是也没有输出错误信息,因为错误的所有内容都重定向到空设备上了。 -
$ selpg -s10 -e20 input_file >/dev/null
selpg 将第 10 页到第 20 页写至标准输出,标准输出被丢弃;错误消息在屏幕出现。这可作为测试 selpg 的用途,此时您也许只想(对一些测试情况)检查错误消息,而不想看到正常输出。
只显示了错误信息,而当没有错误的时候,输出没有显示。 -
$ selpg -s10 -e20 input_file | other_command
selpg 的标准输出透明地被 shell/内核重定向,成为“other_command”的标准输入,第 10 页到第 20 页被写至该标准输入。“other_command”的示例可以是 lp,它使输出在系统缺省打印机上打印。“other_command”的示例也可以 wc,它会显示选定范围的页中包含的行数、字数和字符数。“other_command”可以是任何其它能从其标准输入读取的命令。错误消息仍在屏幕显示。
wc命令输出了输入中的行数,字数,字符数,本来想测试一下cd命令的,新建了一个文件夹,命名为1,新建一个文本文件test2,只有一行,内容为“1”,但是执行命令selpg -s1 -e1 test2 | cd
并没有任何反应,不知道哪里出错了… … -
$ selpg -s10 -e20 input_file 2>error_file | other_command
与上一条命令相似,只有一点不同:错误消息被写至“error_file”。
-
$ selpg -s10 -e20 -l66 input_file
该命令将页长设置为 66 行,这样 selpg 就可以把输入当作被定界为该长度的页那样处理。第 10 页到第 20 页被写至 selpg 的标准输出(屏幕)。
由于起始页是1,终止页是2,所以一共输出了2 * 66 = 132行。 -
$ selpg -s10 -e20 -f input_file
假定页由换页符定界。第 10 页到第 20 页被写至 selpg 的标准输出(屏幕)。
这一条指令不知道如何创建换页符的文件,所以没做测试。
-
$ selpg -s10 -e20 -dlp1 input_file
第 10 页到第 20 页由管道输送至命令“lp -dlp1”,该命令将使输出在打印机 lp1 上打印。
运行这条指令的时候出错了,不是很理解为什么说文件已经关闭了?