Go语言学习笔记(十七)

一、使用命令行程序

在图形用户界面(GUI)面世前,与计算机交互通常是通过命令行进行的。当前,对程序员和系统管理员来说,命令行程序依然是一种流行而实用的与底层操作系统交互的方式。出于如下原因,程序员想创建命令行程序。

  1. 为创建能够定期自动运行的脚本
  2. 为创建与系统中的文件交互的脚本
  3. 为创建能够执行系统维护任务的脚本
  4. 为避免设计图形用户界面这种无谓的开销
    命令行程序通常执行下面的操作:管理目录中的文件或接收一些输入数据并返回一些输出数据。一个这样的典型实例是Windows、Linux和macOS都支持的命令sort,这个命令接受一个美行都包含单词的文件,并返回排序后的版本。
  5. 命令行程序可用任何编程语言来编写,只要脚本是可执行的,就可使用中断来运行(执行)它。

1 操作输入和输出

编写命令行程序前,必须明白一些理论,确保我们编写的脚本能够与操作系统和其他脚本交互。命令行程序操作输入和输出。对于这些输入和输出,Windows、MacOS和Linux使用的术语相同,而Go语言也是用这些术语,鉴于此,熟悉有关输入和输出的术语和代码很有用
:如下表

名称代码描述
标准输入0包含提供给程序的输入
标准输出1包含显示到屏幕上的输出
标准错误2包含显示到屏幕上的错误消息
  1. 标准输入是提供给命令行程序的数据,他可以是文件,也可以是文本字符串。
  2. 标准输出是来自程序的输出。标准错误时来自程序的错误
  3. 长期运行的进程(如Web服务器)常常将数据同时记录到标准输入和标准输出,及通常将数据发送到日志文件。与命令行程序相关的生态系统几乎都是从正确地使用标准流(即标准输入、标准输出和标准错误)的程序衍生而来的,因此对彪顺流有大致的了解将对我们有很大帮助。

2 访问命令行参数

在创建命令行程序方面,Go语言提供了强大的支持。它遵循接收输入并发送输出的理念,且通常会自动确保输出被发送到正确的输出流。在命令行中传递给命令行程序的数据被称为参数。在Go语言中,要读取传递给命令行的参数,可使用标准库中的os包,如下面程序所示:

package main

import (
	"fmt"
	"os"
)

func main() {
	for i, arg := range os.Args {
		fmt.Println("argument", i, "is", arg)
	}
}

方法Args返回一个结果切片,其中包含程序的名称以及传递给程序的所有参数。在这个实例中使用range来遍历参数并将其打印到终端。
go build之后,在Windows下将生成一个.exe的文件,在Linux和macOS系统中,执行go build将生成一个没有拓展名的可执行文件。请注意,在Windows和Linux/macOS系统中执行文件的方式存在细微差别。在Linux/或macOS中,要执行当前工作目录下的可执行文件,必须在指定文件时加上前缀./:而在Windows系统中,不需要添加这样的前缀。
Windows下结果如图:
在这里插入图片描述

3 分析命令行标志

虽然可使用os包来获取命令行参数,但Go语言还在标准库中提供了flag包。除了os.Args的功能外,这个包还提供了众多其他的功能,其中包括以下几点。

  • 指定作为参数传递的值的类型
  • 设置标志的默认值
  • 自动生成帮助文本
    演示程序如下:
package main

import (
	"flag"
	"fmt"
)

func main() {
	s := flag.String("s", "Hello world", "String help text")
	flag.Parse()
	fmt.Println("value of s:", *s)
}

程序解读如下。

  1. 声明变量s并将其设置为flag.String返回的值
  2. flag.String能够让您声明命令行标志,并指定其名称、默认值和帮助文本。
  3. 调用flag.Parse,让程序能够传递声明的参数
  4. 最后打印变量s的值。请注意,flag.String返回的是一个指针,因此使用运算符*对其解除引用,以便显示底层的值,

执行这个程序,会发现flag包给标志-s设置了默认值。
在这里插入图片描述
我们也可以给-s赋值
在这里插入图片描述
我们可以使用-h/–h/-help/–help来查看flag自动创建的一些帮助文本,如下结果完全一样,毕竟是一个很小的程序:
在这里插入图片描述
PS:在运行第一个例子的时候,可以传递多个参数。为此,可先指定这个可执行文件的名称,在指定用空格分割的参数。程序将收到这些参数并将它们打印到终端:
在这里插入图片描述

4 指定标志的类型

flag包根据声明分析标志的类型,这对应于Go语言的类型系统。编写命令程序时,必须考虑程序将接收的数据,并将其映射到正确的类型。下面的实例程序演示了如何分析String、Int和Boolean标志并将它们的值打印到终端。

package main

import (
	"flag"
	"fmt"
)

func main() {
	s := flag.String("s", "Hello world", "String help text")
	i := flag.Int("i", 1, "Int help text")
	b := flag.Bool("b", false, "Bool help text")
	flag.Parse()
	fmt.Println("value of s:", *s)
	fmt.Println("value of i:", *i)
	fmt.Println("value of b:", *b)
}

如果运行这个程序,将发现正确地设置了这些标志的值
在这里插入图片描述
同样我们可以修改这些参数,而且,程序会自动将我们传入的字符串(char(42))指定为对应参数的类型(int(42)),对于Boolean标志,指定即设为true,如下图:
在这里插入图片描述
当然如果输入的字符串不能转换为对应的类型将报错,
在这里插入图片描述

5 自定义帮助文本

虽然flag包会自动生成帮助文本,但完全可以覆盖默认的帮助格式并提供自定义的帮助文本。为此可将变量Usage设置为一个函数,这样每当在分析标志的过程中发生错误时,都将调用这个函数。这是关于这个函数的简单实现:

flag.Usage = func(){
	fmt.Fprintln(os.Stderr,"hello world")
}

这里使用了标准库中的os包来讲喜爱西打印到标准误差(standard Error),因为这条消息将在分析错误的时候显示,但输出是完全可定制的。

详细实例如下

package main

import (
	"flag"
	"fmt"
	"os"
)

func main() {
	flag.Usage = func() {
		usageText := `Usage example04 [OPTION]
An example of customising usage output

  -s, --s         example string argument, default: String help text
  -i, --i         example integer argument, default: Int help text
  -b, --b         example boolean argument, default: Bool help text`
		fmt.Fprintf(os.Stderr, "%s\n", usageText)

	}
	s := flag.String("s", "Hello world", "String help text")
	i := flag.Int("i", 1, "Int help text")
	b := flag.Bool("b", true, "Bool help text")
	flag.Parse()
	fmt.Println("value of s:", *s)
	fmt.Println("value of i:", *i)
	fmt.Println("value of b:", *b)
}

运行结果如下在这里插入图片描述

6 创建子命令

很多命令行都支持子命令,一个简单的实例是git,它包含顶级命令git和多个子命令,而这些子命令都有独立的选项和帮助文本。下面是git的一些子命令

git clone
fit branch

如果运行这些子命令的时候指定表示–help,我们将发现他们有独立的选项。flag包通过FlagSets提供了子命令支持,让我们能够创建子命令,并制定独立的标志集。要创建子命令并指定标志可像下面这样做:

cloneCmd :=flagNewFlagSet(“clone”,flag.ExitError)
其中第一个参数为子命令名,而第二个参数则制定了错误处理行为

1.flag.ContinueOneError:如果没有分析错误,就继续执行
2.flag.ExitOnError:如果有分析错误,就退出并将状态码设置为2
3.flag.PanicOnError:如果发生分析错误,就引发panic

使用NewFlagSet可创建独立的标志集。要根据参数作相应的处理,可使用switch语句。在switch语句中使用os.Arg来处理标志集。因为要处理的是标志集,而索引是从0开始的,所以这里要使用索引1:

switch os.Arg[1]{
	case "clone":
	//这里处理clone子命令
	case "branch":
	//这里处理branch子命令
	default:
	//处理其他操作
}

在下面的实例中,创建了一个命令行工具,它提供了两个命令:uppercase和lowercase。这些命令接受标志-s或者–s指定的字符串,并返回处理后的文本。

uppercaseCmd := flag.NewFlagSet("uppercase", flag.ExitOnError)
lowercaseCmd := flag.NewFlagSet("lowercase", flag.ExitOnError)

switch os.Args[1] {
case "uppercase":
	s := uppercaseCmd.String("s", "", "A string of text to be uppercased")
	uppercaseCmd.Parse(os.Args[2:])
	fmt.Println(strings.ToUpper(*s))
case "lowercase":
	s := lowercaseCmd.String("s", "", "A string of text to be lowercased")
	lowercaseCmd.Parse(os.Args[2:])
	fmt.Println(strings.ToLower(*s))
default:
	flag.Usage()
}

解释如下:

  1. 创建了两个FlagSet,一个表示命令uppercase,另一个表示命令lowercase。
  2. 使用switch语句读取命令的第一个参数。
  3. 如果这个参数为uppercase,就在FlagSet uppercase中初始化一个字符串标志,再讲其他参数传递给FlagSet uppercase,并对他们进行分析。
  4. 将s的值传递给strings包中的方法ToUpper,以便将其转换为大写。如果用户没有给s指定值,将传递默认的空字符串
  5. 如果第一个参数既不是uppercase也不是lowercase,将执行switch语句的default部分,单在这里啥都没有做

如果没有参数,我们可以用Usage()函数设置输出,当没有第二个参数。即os.Arg的长度为1时调用,如下:

package main

import (
	"flag"
	"fmt"
	"os"
	"strings"
)

func flagUsage() {
	usageText := `example05 is an example cli tool.
        
Usage:
example05 command [arguments]
The commands are:
uppercase  uppercase a string 
lowercase  lowercase a string
Use "example05 [command] --help" for more information about a command.`
	fmt.Fprintf(os.Stderr, "%s\n\n", usageText)
}

func main() {

	flag.Usage = flagUsage
	uppercaseCmd := flag.NewFlagSet("uppercase", flag.ExitOnError)
	lowercaseCmd := flag.NewFlagSet("lowercase", flag.ExitOnError)

	if len(os.Args) == 1 {
		flag.Usage()
		return
	}

	switch os.Args[1] {
	case "uppercase":
		s := uppercaseCmd.String("s", "", "A string of text to be uppercased")
		uppercaseCmd.Parse(os.Args[2:])
		fmt.Println(strings.ToUpper(*s))
	case "lowercase":
		s := lowercaseCmd.String("s", "", "A string of text to be lowercased")
		lowercaseCmd.Parse(os.Args[2:])
		fmt.Println(strings.ToLower(*s))
	default:
		flag.Usage()
	}

}

运行结果如下:
在这里插入图片描述

7 POSIX兼容性

在Linux和macOS系统中,大多数命令行工具都要求以推荐标准POSIX指定的方式传递参数。POSIX是一系列标准,旨在确保操作系统之间彼此兼容。很多开发人员都希望采用这种方式,因此关于Go语言的flag包的话题中,经常会引发对POSIX兼容性的讨论。
虽然Go语言的eflag包没有醉熏这些推荐标准,但有多个第三方替代品遵循了这些推荐标准

8 安装和分享命令行程序

如果我们想要自己的命令行程序能够在任何地方使用可以使用go install命令安装它

  1. 将自己写的命令行程序放在%GOPATH%/src/github.com/[your github username]/[package name]
  2. 执行go install %GOPATH%/src/github.com/[your github username]/[package name]命令即可

9 相关问题

1 如何查看命令的退出状态?

要查看命令的退出状态,在Windows系统中可使用echo%errorlevel%,而在macOS和Linux中可使用echo $?

2 Go语言为何将-option--option视为同一个选项?

虽然使用一个连字符和两个连字符的选项通常是不同的选项,但Go设计者将它们视为相同的选项。

3 使用go install安装他人提供的命令行程序安全么?

安装并运行包之前,务必检查其内容。在访问操作系统时,Go程序的权限很大,因此务必谨慎地运行他们。

4 命令行程序出现错误时,如果想将其显示给用户,应该将其发送到哪种输出?

要在命令行脚本中显示错误,应使用标准错误。要将文本发送到标准错误,可像下面这样使用fmt和os包。

参考书籍
 [1]: 【Go语言入门经典】[英] 乔治·奥尔波 著 张海燕 译
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是兔不是秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值