该命令是以标准的linux命令行程序为参照,采用golang进行编写的。
作用是可以从某个输入文档中截取指定页数的文本到标准输出或者重定向文件。
该指令支持-s,-e,-l,-f 标准参数以及之后的可选参数。
go selPage.go -s2 -e5 -l60 ./test.file
go selvage.go -s2 -e6 ./test.file >./test2.file
go selvage.go -s2 -e6 -f ./test.file 1&>2
等等。
导入方式 在将该地址上的包通过 go get 指令下载到本地后,将这行代码添加到package下面:import flag "github.com/spf13/pflag"
pflag
包是go自带包flag的一个插件,作用和flag
类似,用于处理命令行指令的解析。
具体使用方法可以前往查看源码以及相关博客。
以下是代码中使用到了该包内容的相关部分
func wordSepNormalizeFunc(f *flag.FlagSet, name string) flag.NormalizedName {
from := []string{"-", "_"}
to := "."
for _, sep := range from {
name = strings.Replace(name, sep, to, -1)
}
return flag.NormalizedName(name)
}
flag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc)
s := flag.IntP("startPage", "s", 1, "this decides the start page, default 1 ")
e := flag.IntP("endPage", "e", 1000, "this decides the end page, default 1000 ")
l := flag.IntP("pageLines", "l", 72, "this decides the lines one page, default 72 ")
flag.StringP("usef", "f", "", "this decides use the \\f, no argument")
flag.Parse()
这段代码作用是告诉程序该如何解析命令行指令后的标准参数的。
其中wordSepNormalizeFunc
函数用于将用户误输的_和-正确解析为.,
之后主要使用对应的typeP
类型函数,将参数的名称,简写,默认值以及介绍告诉程序,以便之后的解析。
在完成相关设定之后,需要调用flag.Parse
函数来完成提交。
flag.Args()
用于获取所有的非标准参数,也就是不带-的参数。
flag.Arg(num int)
用于获取指定位置的非标准参数。
flag.NFlag() / flag.NArg()
分别用于给出标准 / 非标准参数的个数。
flag.LookUp(name string).changed
bool类型,LookUp函数通过名称检索标准参数,并判断该参数是否被设定。(设定了则changed属性为true)
if *s > *e {
ErrReport("startPage must smaller than or equal with endPage")
}
isL, isF := flag.Lookup("pageLines").Changed, flag.Lookup("usef").Changed
if isL && isF {
ErrReport("l and f argus at the same time")
}
这段代码是对用户的输入进行初步检测,比如起始页数必须不大于结尾页数,-l和-f不能同时存在等等。
程序中出现的用户类错误均交由ErrReport
函数处理。
func ErrReport(err ...string) {
var strs = err
for _, str := range strs {
_, _ = fmt.Fprintln(os.Stderr, str)
}
os.Exit(2)
}
在这一部分,程序直接使用多个if-else对flag.Args获得的非标准参数进行循环处理。
着重处理包含>fileName
, 1/2>&fileName
, 1/2&>1/2
三种情况。
而go语言为我们提供了一定的重定向机制,比如:os.stdout = os.stderr 将会将标准输出内容重定向至标准错误输出。
var outFile string
for n, arg := range flag.Args() {
if n == 0 {
continue
}
if arg[0:1] == ">" {
outFile = arg[1:]
f, err := os.OpenFile(outFile, os.O_RDWR | os.O_APPEND, 666)
if err != nil {
panic(err)
}
os.Stdout = f
}else if arg[1:3] == "&>" {
outFile = arg[3:]
f, err := os.OpenFile(outFile, os.O_RDWR | os.O_APPEND, 666)
if err != nil {
panic(err)
}
if arg[0:1] == "1" {
os.Stdout = f
}else {
os.Stderr = f
}
}else if arg[1:3] == ">&" {
if arg[0:1] == "1" {
os.Stdout = os.Stderr
}else {
os.Stderr = os.Stdout
}
}else {
ErrReport("无法解析该命令:", arg)
}
}
由于需要打开文件进行修改,因此这里不能使用默认的os.Open
函数,从该函数获取的*File指针将只具有读的权限,因此需要os.OpenFile
函数,自行设置文件权限。
对应页数的文本获取方法有两种,一是由-l指定的根据行数计算页数,另外则是由-f指定的根据换页符计算页数。但大体上,两种方法的工作机理都是外层循环统计当前页数,内部逻辑处理页尾的判定。
首先将文件开头非目标文本段通过readBytes('\n')
或者readBytes('\f')
抛弃,
在到达指定开头后,再次通过该函数将目标文本读取到一个外部的string类型数据中保存。
func readByLine(f *os.File, s, e, l int) {
countL, countP := 0, 0
reader := bufio.NewReader(f)
for ;countP < s-1; {
countL = 0
for {
_, _ = reader.ReadBytes('\n')
countL++;
if countL >= l {
countP++
break
}
}
}
for ; countP < e; {
countL = 0;
for {
bytes, err := reader.ReadBytes('\n')
if err != nil {
panic(err)
}
lines += string(bytes)
_, _ = fmt.Fprintf(os.Stderr, "读入 %d 个字节\n", len(bytes))
countL++;
if countL >= l {
countP++
break
}
}
}
}
func readByF(f *os.File, s, e int) {
countP := 0
reader := bufio.NewReader(f)
for ;countP < s-1; {
_, _ = reader.ReadBytes('\f')
}
for ; countP < e; {
bytes, err := reader.ReadBytes('\f')
if err != nil {
panic(err)
}
lines += string(bytes)
_, _ = fmt.Fprintf(os.stderr, "读入 %d 个字节", len(bytes))
}
}
至于内容输出,则是简单地将保存的string类型数据放置到标准输出中,因为前面已经进行了输出的重定向,因此这里也就会被输出到相应的地方去。