script
script
script
如何导入?
import github.com/bitfield/script
我能用它做什么?
让我们看一个简单的例子。假设要将文件的内容作为字符串读取:
contents, err := script.File("test.txt").String()
这看起来很简单,但是假设您现在要计算该文件中的行数。
numLines, err := script.File("test.txt").CountLines()
对于更具挑战性的内容,让我们尝试计算文件中与字符串“Error”匹配的行数:
numErrors, err := script.File("test.txt").Match("Error").CountLines()
grep
script.Stdin().Match("Error").Stdout()
那太容易了!因此,让我们在命令行中传入一个文件列表,让程序按顺序读取它们并输出匹配的行:
script.Args().Concat().Match("Error").Stdout()
也许我们只对前10场比赛感兴趣。没问题:
script.Args().Concat().Match("Error").First(10).Stdout()
那是什么?您想将输出附加到文件而不是打印到终端?先生,你的态度很好。
script.Args().Concat().Match("Error").First(10).AppendFile("/var/log/errors.txt")
想帮忙下围棋吗?
约翰的Golang辅导帮助我建立了信心,填补了我知识上的空白。它提供了不可估量的帮助和指导,因此我正在申请我梦想中的SRE工作!-梅丽娜·布蒂鲁
John还是Kubernetes和云基础设施顾问,著有《cloudnativedevops withKubernetes一书。如果John可以帮助您完成基础设施或DevOps项目,请与我们联系!他很想听到你的消息。
目录script
它是如何工作的?
那些链式函数调用看起来有点奇怪。怎么回事?
Unix shell及其许多仿真器的一个优点是可以将操作组合到管道中:
cat test.txt | grep Error | wc -l
管道中每个阶段的输出输入到下一个阶段,您可以将每个阶段看作一个过滤器,它只将其输入的某些部分传递到其输出。
相比之下,在原始Go中编写shell-like脚本就不那么方便了,因为您所做的一切都返回了不同的数据类型,并且您必须(或至少应该)在每个操作之后检查错误。
在用于系统管理的脚本中,我们通常希望以一种快速、方便的方式组合不同的操作。如果在管道的某个地方发生错误,我们希望在结束时检查一次,而不是在每次操作之后。
一切都是烟斗scriptscript.PipeFile()
var p script.Pipe
p = script.File("test.txt")
File()File()
Error()
p = script.File("test.txt")
if p.Error() != nil {
log.Fatalf("oh no: %v", p.Error())
}
烟斗有什么用?
现在,你能用这个烟斗做什么?可以对其调用方法:
var q script.Pipe
q = p.Match("Error")
请注意,在管道上调用方法的结果是另一个管道。为了方便起见,您可以一步完成此操作:
var q script.Pipe
q = script.File("test.txt").Match("Error")
Handling errors
Match
不,不会的。一旦在管道上设置了错误状态,管道上的所有操作都变成no-ops。任何通常会返回新管道的操作都只会返回未更改的旧管道。因此,您可以运行任意长度的管道,如果在任何阶段发生错误,则不会发生任何崩溃,并且您可以在最后检查管道的错误状态。
errWriter
Getting output
String()
result, err := q.String()
if err != nil {
log.Fatalf("oh no: %v", err)
}
fmt.Println(result)
Errors
p.Error()
numLines, err := script.File("doesnt_exist.txt").CountLines()
fmt.Println(numLines)
// Output: 0
if err != nil {
log.Fatal(err)
}
// Output: open doesnt_exist.txt: no such file or directory
CountLines()
Closing pipes
如果您以前在Go中处理过文件,那么您将知道在处理完文件后需要关闭它。否则,程序将保留所谓的文件句柄(表示打开文件的内核数据结构)。对于给定的程序和整个系统,打开的文件句柄的总数是有限制的,因此泄漏文件句柄的程序最终会崩溃,同时也会浪费资源。
在阅读之后需要关闭的不仅仅是文件:网络连接、HTTP响应体等等也是如此。
scriptString()Close()
Close()
ReadAutoCloserio.Reader
ioutil.NopCloserRead()io.EOF
如果你没有读完,你有责任关闭管道。
为什么不只用shell呢?这是个公平的问题。Shell脚本和one-liners非常适合构建one-off任务、初始化脚本和将internet连接在一起的“粘合代码”。我说的是一个至少花了三十年时间做这件事的人。但在许多方面,它们并不适合重要的non-trivial程序:
*?>cutgrephead
说句公道话,这种事情并不是它的初衷。Shell是一个交互式作业控制工具,用于启动程序、将程序连接在一起,并在一定程度上操纵文本。它不是用来构建可移植的、可伸缩的、可靠的和优雅的程序的。这就是围棋的目的。
Go在标准库中内置了一个极好的测试框架。它有一个极好的标准库,以及数千个high-qualitythird-party包,几乎可以实现任何你能想象得到的功能。它是编译的,所以它很快,并且是静态类型的,所以它是可靠的。它是高效的memory-safe。Go程序可以作为单个二进制文件分发。例如,可以扩展到大型项目(Kubernetes)。
scriptscript
ifscript
real-world示例
script
212.205.21.11 - - [30/Jun/2019:17:06:15 +0000] "GET / HTTP/1.1" 200 2028 "https://example.com/ "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX1 Build/HUAWEIFIG-LX1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.156 Mobile Safari/537.36"
我们想提取访问者的IP地址(日志文件中的第一列),并计算该IP地址在文件中出现的次数。最后,我们可以按频率列出前10名访客。在shell脚本中,我们可以执行以下操作:
cut -d' ' -f 1 access.log |sort |uniq -c |sort -rn |head
script
package main
import (
"github.com/bitfield/script"
)
func main() {
script.Stdin().Column(1).Freq().First(10).Stdout()
}
examples/visitors/
快速入门:Unix等价物
script
script[ -f FILE ]>>>$*basenamecatFile()Concat()cutdirnameechogrepgrep -vheadfindlssedsha256sumSHA256Sum()SHA256Sums()tailuniq -cwc -lxargs
源、过滤器和接收器
script
File()Match()String()
script
Sources
这些是创建管道的操作。
Args
Args()
p := script.Args()
output, err := p.String()
fmt.Println(output)
// Output: command-line arguments
Echo
Echo()
p := script.Echo("Hello, world!")
output, err := p.String()
fmt.Println(output)
// Output: Hello, world!
Exec
Exec()stdoutstderr
p := script.Exec("bash -c 'echo hello'")
output, err := p.String()
fmt.Println(output)
// Output: hello
Exec()
Exit status
如果命令返回non-zero退出状态,则管道的错误状态将设置为字符串“exit status X”,其中X是整数退出状态。
p := script.Exec("ls doesntexist")
output, err := p.String()
fmt.Println(err)
// Output: exit status 1
ExitStatus()
p := script.Exec("ls doesntexist")
var exit int = p.ExitStatus()
fmt.Println(exit)
// Output: 1
ExitStatus()
Error output
String()String()
p := Exec("man bogus")
p.SetError(nil)
output, err := p.String()
fmt.Println(output)
// Output: No manual entry for bogus
File
File()
p = script.File("test.txt")
output, err := p.String()
fmt.Println(output)
// Output: contents of file
IfExists
IfExists()
p = script.IfExists("doesntexist.txt")
output, err := p.String()
fmt.Println(err)
// Output: stat doesntexist.txt: no such file or directory
这可用于创建仅在特定文件存在时才执行某些操作的管道:
script.IfExists("/foo/bar").Exec("/usr/bin/yada")
FindFiles
FindFiles()find -type f
script.FindFiles("/tmp").Stdout()
// lists all files in /tmp and its subtrees
ListFiles
ListFiles()ls/path/to/dir/path/to/file/path/to/*
p := script.ListFiles("/tmp/*.php")
files, err := p.String()
if err != nil {
log.Fatal(err)
}
fmt.Println("found suspicious PHP files in /tmp:")
fmt.Println(files)
Slice
Slice()
p := script.Slice([]string{"1", "2", "3"})
output, err := p.String()
fmt.Println(output)
// Output:
// 1
// 2
// 3
Stdin
Stdin()
p := script.Stdin()
output, err := p.String()
fmt.Println(output)
// Output: [contents of standard input]
Filters
过滤器是对同时返回管道的现有管道的操作,允许您无限期地链接过滤器。
Basename
Basename()/usr/local/bin/foofoo
Basename().
Examples:
Basename././rootroot/tmp/example.phpexample.php/var/tmp/tmp./src/filtersfiltersC:/Program FilesProgram Files
Column
Column()cut
例如,给定此输入:
PID TT STAT TIME COMMAND
1 ?? Ss 873:17.62 /sbin/launchd
50 ?? Ss 13:18.13 /usr/libexec/UserEventAgent (System)
51 ?? Ss 22:56.75 /usr/sbin/syslogd
这个节目:
script.Stdin().Column(1).Stdout()
这将是输出:
PID
1
50
51
Concat
Concat()abc
output, err := Echo("a\nb\nc\n").Concat().String()
fmt.Println(output)
// Output: contents of a, followed by contents of b, followed
// by contents of c
这使得编写在命令行上获取输入文件列表的程序非常方便,例如:
func main() {
script.Args().Concat().Stdout()
}
文件列表也可以来自一个文件:
// Read all files in filelist.txt
p := File("filelist.txt").Concat()
…或从命令的输出:
// Print all config files to the terminal.
p := Exec("ls /var/app/config/").Concat().Stdout()
Concat()cat
Dirname
Dirname()/usr/local/bin/foo/usr/local/bin
Dirname().dirnamefilepath.Dir
Dirname()
Examples:
Dirname.///root//tmp/example.php/tmp/var/tmp//var./src/filters./srcC:/Program FilesC:
EachLine
EachLine()strings.Builder
p := script.File("test.txt")
q := p.EachLine(func(line string, out *strings.Builder) {
out.WriteString("> " + line + "\n")
})
output, err := q.String()
fmt.Println(output)
Exec
Exec()stdoutstderr
Exec()Exec()
// `cat` copies its standard input to its standard output.
p := script.Echo("hello world").Exec("cat")
output, err := p.String()
fmt.Println(output)
// Output: hello world
ExecForEach
xargs
{{.}}
导致错误的第一个命令将相应地设置管道的错误状态,并且不会运行后续命令。
// Execute all PHP files in current directory and print output
script.ListFiles("*.php").ExecForEach("php {{.}}").Stdout()
First
First()head
script.Stdin().First(10).Stdout()
Freq
Freq()
banana
apple
orange
apple
banana
还有一个程序,比如:
script.Stdin().Freq().Stdout()
输出将是:
2 apple
2 banana
1 orange
这是shell脚本中查找文件中最多frequently-occurring行的常见模式:
sort testdata/freq.input.txt |uniq -c |sort -rn
Freq()sortuniq -csort -rnFreq()First()
script.Stdin().Freq().First(10).Stdout()
uniq -cFreq()
10 apple
4 banana
2 orange
1 kumquat
Join
Join()
p := script.Echo("hello\nworld\n").Join()
output, err := p.String()
fmt.Println(output)
// Output: hello world\n
Last
Last()tail
script.Stdin().Last(10).Stdout()
Match
Match()
p := script.File("test.txt").Match("Error")
MatchRegexp
MatchRegexp()Match()
p := script.File("test.txt").MatchRegexp(regexp.MustCompile(`E.*r`))
Reject
Reject()Match()
p := script.File("test.txt").Match("Error").Reject("false alarm")
RejectRegexp
RejectRegexp()Reject()
p := script.File("test.txt").Match("Error").RejectRegexp(regexp.MustCompile(`false|bogus`))
Replace
Replace()sed
p := script.File("test.txt").Replace("old", "new")
ReplaceRegexp
ReplaceRegexp()sed
p := script.File("test.txt").ReplaceRegexp(regexp.MustCompile("Gol[a-z]{1}ng"), "Go")
SHA256Sums
SHA256Sums()
Examples:
SHA256Sumstestdata/sha256Sum.input.txt1870478d23b0b4db37735d917f4f0ff9393dd3e52d8b0efa852ab85536ddad8etestdata/multiple_files/1.txttestdata/multiple_files/2.txttestdata/multiple_files/3.tar.gze3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Sinks
接收器是从管道返回一些数据的操作,结束管道。
AppendFile
AppendFile()WriteFile()
var wrote int
wrote, err := script.Echo("Got this far!").AppendFile("logfile.txt")
Bytes
Bytes()
var data []byte
data, err := script.File("test.bin").Bytes()
CountLines
CountLines()
var numLines int
numLines, err := script.File("test.txt").CountLines()
Read
Read()io.ReaderRead()
buf := make([]byte, 256)
n, err := r.Read(buf)
io.Readerioutil.ReadAllio.Copyjson.NewDecoderio.Reader
Read()
SHA256Sum
SHA256Sum()
var sha256Sum string
sha256Sum, err := script.File("test.txt").SHA256Sum()
为什么不是MD5?
MD5不安全。
Slice
Slice()\n
args, err := script.Args().Slice()
for _, a := range args {
fmt.Println(a)
}
Stdout
Stdout()
p := Echo("hello world")
wrote, err := p.Stdout()
Stdin()Stdout()cat
func main() {
script.Stdin().Stdout()
}
要仅过滤与字符串匹配的行,请执行以下操作:
func main() {
script.Stdin().Match("hello").Stdout()
}
String
String()
contents, err := script.File("test.txt").String()
String()String()
p := script.File("test.txt")
_, _ = p.String()
_, err := p.String()
fmt.Println(err)
// Output: read test.txt: file already closed
WriteFile
WriteFile()
var wrote int
wrote, err := script.File("source.txt").WriteFile("destination.txt")
Examples
script
- cat(将stdin复制到stdout)
- cat 2(在命令行上获取文件列表并将其内容连接到标准输出)
欢迎更多的例子!
scriptscript
我能做些什么?
有关一些有用的提示,请参阅投稿人指南。