-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #99 from xushiwei/log
x/gsh
- Loading branch information
Showing
2 changed files
with
213 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
Go+ DevOps Tools | ||
====== | ||
|
||
[![Language](https://img.shields.io/badge/language-Go+-blue.svg)](https://github.com/goplus/gop) | ||
[![GitHub release](https://img.shields.io/github/v/tag/goplus/gop.svg?label=Go%2b+release)](https://github.com/goplus/gop/releases) | ||
[![Discord](https://img.shields.io/badge/Discord-online-success.svg?logo=discord&logoColor=white)](https://discord.gg/mYjWCJDcAr) | ||
[![GoDoc](https://pkg.go.dev/badge/github.com/qiniu/x/gsh.svg)](https://pkg.go.dev/github.com/qiniu/x/gsh) | ||
|
||
This is an alternative to writing shell scripts. | ||
|
||
Yes, now you can write `shell script` in Go+. It supports all shell commands. | ||
|
||
## Usage | ||
|
||
First, let's create a Go+ source file named `./example.gsh` and write the following code: | ||
|
||
```coffee | ||
mkdir "testgsh" | ||
``` | ||
|
||
Then run it: | ||
|
||
```sh | ||
gop run example.gsh | ||
``` | ||
|
||
It's strange to you that the file extension of Go+ source is not `.gop` but `.gsh`. It is only because Go+ register `.gsh` as a builtin [classfile](https://github.com/goplus/gop/blob/main/doc/classfile.md). | ||
|
||
We can change `./example.gsh` more complicated: | ||
|
||
```coffee | ||
type file struct { | ||
name string | ||
fsize int | ||
} | ||
|
||
mkdir! "testgsh" | ||
|
||
mkdir "testgsh2" | ||
lastErr! | ||
|
||
mkdir "testgsh3" | ||
if lastErr != nil { | ||
panic lastErr | ||
} | ||
|
||
capout => { ls } | ||
println output.fields | ||
|
||
capout => { ls "-l" } | ||
files := [file{flds[8], flds[4].int!} for e <- output.split("\n") if flds := e.fields; flds.len > 2] | ||
println files | ||
|
||
rmdir "testgsh", "testgsh2", "testgsh3" | ||
``` | ||
|
||
### Check last error | ||
|
||
If we want to ensure `mkdir` successfully, there are three ways: | ||
|
||
The simplest way is: | ||
|
||
```coffee | ||
mkdir! "testsh" # will panic if mkdir failed | ||
``` | ||
|
||
The second way is: | ||
|
||
```coffee | ||
mkdir "testsh" | ||
lastErr! | ||
``` | ||
|
||
Yes, `gsh` provides `lastErr` to check last error. | ||
|
||
The third way is: | ||
|
||
```coffee | ||
mkdir "testsh" | ||
if lastErr != nil { | ||
panic lastErr | ||
} | ||
``` | ||
|
||
This is the most familiar way to Go developers. | ||
|
||
### Capture output of commands | ||
|
||
And, `gsh` provides a way to capture output of commands: | ||
|
||
```coffee | ||
capout => { | ||
... | ||
} | ||
``` | ||
|
||
Similar to `lastErr`, the captured output result is saved to `output`. | ||
|
||
For an example: | ||
|
||
```coffee | ||
capout => { ls "-l" } | ||
println output | ||
``` | ||
|
||
Here is a possible output: | ||
|
||
```s | ||
total 72 | ||
-rw-r--r-- 1 xushiwei staff 11357 Jun 19 00:20 LICENSE | ||
-rw-r--r-- 1 xushiwei staff 127 Jun 19 10:00 README.md | ||
-rw-r--r-- 1 xushiwei staff 365 Jun 19 00:25 example.gsh | ||
-rw-r--r-- 1 xushiwei staff 126 Jun 19 09:33 go.mod | ||
-rw-r--r-- 1 xushiwei staff 165 Jun 19 09:33 go.sum | ||
-rw-r--r-- 1 xushiwei staff 1938 Jun 19 10:00 gop_autogen.go | ||
``` | ||
|
||
We can use [Go+ powerful built-in data processing capabilities](doc/docs.md#data-processing) to process captured `output`: | ||
|
||
```coffee | ||
type file struct { | ||
name string | ||
fsize int | ||
} | ||
|
||
files := [file{flds[8], flds[4].int!} for e <- output.split("\n") if flds := e.fields; flds.len > 2] | ||
``` | ||
|
||
In this example, we split `output` by `"\n"`, and for each entry `e`, split it by spaces (`e.fields`) and save into `flds`. Condition `flds.len > 2` is to remove special line of output: | ||
|
||
```s | ||
total 72 | ||
``` | ||
|
||
At last, pick file name and size of all selected entries and save into `files`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
Copyright 2024 Qiniu Limited (qiniu.com) | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package gsh | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"os" | ||
"os/exec" | ||
) | ||
|
||
// App is project class of this classfile. | ||
type App struct { | ||
fout io.Writer | ||
ferr io.Writer | ||
fin io.Reader | ||
cout string | ||
err error | ||
} | ||
|
||
func (p *App) initApp() { | ||
p.fin = os.Stdin | ||
p.fout = os.Stdout | ||
p.ferr = os.Stderr | ||
} | ||
|
||
// Gop_Exec executes a shell command. | ||
func (p *App) Gop_Exec(name string, args ...string) error { | ||
cmd := exec.Command(name, args...) | ||
cmd.Stdin = p.fin | ||
cmd.Stdout = p.fout | ||
cmd.Stderr = p.ferr | ||
p.err = cmd.Run() | ||
return p.err | ||
} | ||
|
||
// LastErr returns error of last command execution. | ||
func (p *App) LastErr() error { | ||
return p.err | ||
} | ||
|
||
// Capout captures stdout of doSth() execution and save it to output. | ||
func (p *App) Capout(doSth func()) (string, error) { | ||
var out bytes.Buffer | ||
old := p.fout | ||
p.fout = &out | ||
defer func() { | ||
p.fout = old | ||
}() | ||
doSth() | ||
p.cout = out.String() | ||
return p.cout, p.err | ||
} | ||
|
||
// Output returns result of last capout. | ||
func (p *App) Output() string { | ||
return p.cout | ||
} | ||
|
||
// Gopt_App_Main is main entry of this classfile. | ||
func Gopt_App_Main(a interface{ initApp() }) { | ||
a.initApp() | ||
a.(interface{ MainEntry() }).MainEntry() | ||
} |