selpg 是一个自定义命令行程序,全称select page,即从源(标准输入流或文件)读取指定页数的内容到目的地(标准输出流或给给打印机打印)
详细用法请参考 C语言实现自定义selpg,我这里是为练习go而写的,与原文用法有差异
selpg [-s startPage] [-e endPage] [-l linePerPage | -f] [-d dest] filename
必需标志以及参数:
可选参数:
结构体记录参数对应的值易于处理
type selpgArgs struct {
startPage int //起始页号
endPage int //终止页号
pageLen int //每页的行数
pageType string//分页类型,默认 -l 按行数分页
printDest string//选择的打印机
inFilename string//读取的文件名
}
解析命令的标志和对应参数
当我们输入命令 $ ./selpg -s 1 -e 6 -f -d dest input_file
时,有三种参数需要获取
类似”-s 30”形式,即 标志 + 对应参数值
要获取 -s 的参数30,只需调用如下函数
flag.IntVar(&sa.startPage, "s", -1, "the start page")
上面一行代码定义了标志 s 的默认参数值是 -1,当接受到 “-s 30” 则会把 30 赋值给 sa.startPage,否则把 -1 给sa.startPage
类似”-f”形式,单独一个标志,不需对应参数值
我们也能通过flag.TypeVar(*val, flag, defaultVal, usage)来定义 -f,但是因为命令行语法的原因,要识别 -flag ,那么它对应参数的值只能是 bool ,不能是 string int之类的。
因此,采用
input_f := flag.Bool("f", false, "some usage of -f")
通过上面函数,当命令行有输入 -f 时,input_f (-f 对应的参数)即为true,否则为false
类似”input_file”,没有标志,只有参数
flag.Arg(i) 则表示命令中的无标识参数中的第i的参数值
因此,通过 sa.inFilename = flag.Arg(0)
即能获取该参数
分页方式的处理
按行分页 -l pageLe
if sa.pageType == "l" {
line := bufio.NewScanner(fin)
//一行一行读取文件
for line.Scan() {
//如果当前的页号满足范围,则把处于该页中的该行输出
if currPage >= sa.startPage && currPage <= sa.endPage {
fout.Write([]byte(line.Text() + "\n"))
if sa.printDest != "" {
inpipe.Write([]byte(line.Text() + "\n"))
}
}
//满行则换页
currLine++
if currLine % sa.pageLen == 0 {
currPage++
currLine = 0
}
}
}
按分页符分页 -f
根据分页符,先把一页内容读进
rd := bufio.NewReader(fin)
for {
//ReadString每次从当前位置读取,直到遇到换页符'\f',然后下次从'\f'后面开始继续读取,即每次读一页
page, ferr := rd.ReadString('\f')
if ferr != nil || ferr == io.EOF {
//注意把最后一页输出
if ferr == io.EOF {
if currPage >= sa.startPage && currPage <= sa.endPage {
fmt.Fprintf(fout, "%s", page)
currPage++
}
}
break
}
page = strings.Replace(page, "\f", "", -1)
if currPage >= sa.startPage && currPage <= sa.endPage {
fmt.Fprintf(fout, "%s", page)
currPage++
}
}
通过管道把selpg输出给另一个命令
调用函数 exec.Command("命令名", "该命令的相关参数", ...)
即可返回获得该命令结构,再调用 cmd.StdinPipe() 可以获得管道的输入管道,然后把我们的 selpg 输出到该管道即可。
我是把selpg 输出给了命令 grep,查找出selpg的输出中与文件keyword内容相关的部分,再输出到屏幕
if sa.printDest != "" {
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()
}
下面的例子中我这里只介绍本程序的用法
用到的相关文件:
①input_file为输入文件
②input_file_f为输入文件,经处理过,每五行以一个分页符 ‘\f’ 结束,用于测试 -f 标志
③output_file为输出文件
④error_file为保存程序出错信息的文件
⑤keyword该文件里面只有i一个字母。
①②④内容如下,②③初始为空文件
不选择文件,则默认从键盘读入,输出到屏幕
$ ./selpg -s 1 -e 3
执行上面命令后,当我们从键盘输入一行,就会输出一行到屏幕,而且不用 -l 指定页的行数,所以(默认72行为一页)直到我们输够 3*72 行后,程序才会终止读取……
不指定-l,读取文件,则默认以72行为一页
$ ./selpg -s 1 -e 6 input_file
输出:
this
is
my
first
go-lang
program
and
i feel
really
happy
./selpg: end_page (6) greater than total pages (1), less output than expected
因为input_file只有6行,所以只能算一页,而我们指定了从第一页读取到第六页,因此会提示错误:指定结束页6比文件总页数1少了
从input_file读取其第2—5页的内容,指定每页的行数为 1
$ ./selpg -s 2 -e 5 -l 1 input_file
输出:
is
my
first
go-lang
从input_file读取其第2—5页内容,指定每页的行数为 1,把结果写入文件output_file
$ ./selpg -s 2 -e 5 -l 1 input_file > output_file
从input_file_f读取第1—2页内容 ,通过分页符定界,结果输出到屏幕
$ ./selpg -s 1 -e 2 -f input_file_f
输出:
this is my first go-lang program
in fact, i enjoy it
but i have so much homework to do
if i have more time
i can do it better
however, the reson in nonsense
It is not enough for a year.
I want to make my own progress.
So that I could find a great company or graduate
Do your best!hjm!
可以看到输出10行,原因是原文件中每五行后面才有一个分页符 ‘\f’ 分割
读取文件输出到打印机打印,注意我已经修改了该功能用于测试!!因为我没打印机测试,所以把这功能改为输入到 grep 中,从中找出含有 keyword文件(只含有字母 i)中关键字的内容,并且输出到屏幕上
$ ./selpg -s 1 -e 7 -l 1 -d none input_file
输出:
this
is
my
first
go-lang
program
and
1:this
2:is
4:first
可以看到输出中既有读取文件的内容,最后也列出根据关键字 i 搜索出的内容,最后三行带有行号的为 grep 执行结果,说明程序中的管道运用是正确的
定向其输入流为文件
$ ./selpg -s 1 -e 1 < input_file
输出:
this
is
my
first
go-lang
program
and
i feel
really
happy
把输出导向到文件output_file
$ ./selpg -s 1 -e 1 input_file > output_file
把错误信息导向文件error_file
$ ./selpg -s 1 -e 2> error_file
将命令”ps”的输出的第1页到第3页写至selpg的标准输出(屏幕);命令”ps”可以替换为任意其他linux行命令,selpg的输出也能成为另一个命令的输入。
$ ps | ./selpg -s 1 -e 3
输出:
PID TTY TIME CMD
10011 pts/2 00:00:00 bash
10648 pts/2 00:00:00 ps
10649 pts/2 00:00:00 selpg
./selpg: end_page (3) greater than total pages (1), less output than expected
将selpg的输出传给 cat 命令作为输入执行,cat结果显示在屏幕
$ ./selpg -s 1 -e 1 input_file | cat -n
输出:
1 this
2 is
3 my
4 first
5 go-lang
6 program
7 and
8 i feel
9 really
10 happy
命令输入出错时,会提示相应用法
标志缺少参数
$ ./selpg -s -l
输出:
invalid value "-l" for flag -s: strconv.ParseInt: parsing "-l": invalid syntax
Usage of ./selpg:
-d string
-e int
(default -1)
-f
-l int
(default 72)
-s int
(default -1)
缺少必须标志 -s -l
./selpg -s 1 input_file
输出:
USAGE: ./selpg [-s start_page] [-e end_page] [ -l lines_per_page | -f ] [ -d dest ] [ in_filename ]
文件不存在或没权限打开
$ ./selpg -s 1 -e 6 noFile
输出:
selpg: could not open input file "noFile"
open noFile: no such file or directory
USAGE: ./selpg [-s start_page] [-e end_page] [ -l lines_per_page | -f ] [ -d dest ] [ in_filename ]
github
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"os/exec"
"strings"
)
type selpgArgs struct {
startPage int
endPage int
pageLen int //lines per page
pageType string
printDest string
inFilename string
}
func main() {
sa := new(selpgArgs)
/** 定义标志参数
* -s -e -l -f -d
*/
flag.IntVar(&sa.startPage, "s", -1, "the start page")
flag.IntVar(&sa.endPage, "e", -1, "the end page")
flag.IntVar(&sa.pageLen, "l", 72, "the paging form")
flag.StringVar(&sa.printDest, "d", "", "the printer")
/**检查 -f是否存在,注意 -f 只支持bool类型
* 默认的,提供了-flag,则对应的值为true
*/
exist_f := flag.Bool("f", false, "")
//解析命令行参数到定义的flag
flag.Parse()
if *exist_f {
sa.pageType = "f"
sa.pageLen = -1
} else {
sa.pageType = "l"
}
// 非标志参数为文件名
if flag.NArg() == 1 {
sa.inFilename = flag.Arg(0)
//fmt.Printf("now in if flag.NArg() == 1\n")
} else {
sa.inFilename = ""
}
//检查参数合法性
checkArgs(*sa, flag.NArg())
//执行命令
exeSelpg(*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 checkArgs(sa selpgArgs, notFlagNum int) {
s_e_ok := sa.startPage <= sa.endPage && sa.startPage >= 1
num_ok := notFlagNum == 1 || notFlagNum == 0
l_f_ok := !(sa.pageType == "f" && sa.pageLen != -1)
if !s_e_ok || !num_ok || !l_f_ok {
usage()
os.Exit(1)
}
}
func exeSelpg(sa selpgArgs) {
currPage := 1
currLine := 0
fin := os.Stdin
fout := os.Stdout
var inpipe io.WriteCloser
var err error
//确定输入源,是否改为从文件读入
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()
}
/**确定输出源, 是否打印到打印机
* 由于没有打印机测试,用管道接通 grep 作为测试,结果输出到屏幕
* selpg内容通过管道输入给 grep, grep从中搜出带有keyword文件的内容
*/
if sa.printDest != "" {
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()
}
// -l 按行分页读取输出
if sa.pageType == "l" {
line := bufio.NewScanner(fin)
for line.Scan() {
if currPage >= sa.startPage && currPage <= sa.endPage {
fout.Write([]byte(line.Text() + "\n")) //一行行输出
if sa.printDest != "" {
inpipe.Write([]byte(line.Text() + "\n"))
}
}
currLine++
if currLine%sa.pageLen == 0 {
currPage++
currLine = 0
}
}
} else { // -f 按分隔符分页读取输出
rd := bufio.NewReader(fin)
for {
//一页一页的读
page, ferr := rd.ReadString('\f')
if ferr != nil || ferr == io.EOF {
if ferr == io.EOF {
if currPage >= sa.startPage && currPage <= sa.endPage {
fmt.Fprintf(fout, "%s", page)
}
}
break
}
page = strings.Replace(page, "\f", "", -1)
currPage++
if currPage >= sa.startPage && currPage <= sa.endPage {
fmt.Fprintf(fout, "%s", page)
}
}
}
if currPage < sa.endPage {
fmt.Fprintf(os.Stderr, "./selpg: end_page (%d) greater than total pages (%d), less output than expected\n", sa.endPage, currPage)
}
}