Better Go shell scripts without Python
This commit is contained in:
parent
2523888e3a
commit
5291fe3f6a
|
@ -1,4 +1,4 @@
|
|||
///bin/true && exec /usr/bin/env gosh "$0" "$@"
|
||||
///bin/true && exec /usr/bin/env script.go "$0" "$@"
|
||||
// vim:set ft=go:
|
||||
package main
|
||||
|
||||
|
|
163
home/bin/script.go
Executable file
163
home/bin/script.go
Executable file
|
@ -0,0 +1,163 @@
|
|||
///bin/true && exec /usr/bin/env go run "$0" "$@"
|
||||
//
|
||||
// script.go is a Go program that can run itself as a shell script, as well as
|
||||
// help other Go scripts to run themselves.
|
||||
//
|
||||
// To make your Go scripts work with this, use the following "shebang" header
|
||||
// at the top of your script:
|
||||
//
|
||||
// ///bin/true && exec /usr/bin/env script.go "$0" "$@"
|
||||
// // vim:set ft=go:
|
||||
// package main
|
||||
//
|
||||
// The first line will cause your shell to run `script.go` passing the current
|
||||
// filename and the rest of the command line arguments. The vim modeline comment
|
||||
// may help your code editor to highlight the file as Go syntax.
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Version of this script.
|
||||
const Version = "1.0.0"
|
||||
|
||||
// Command line arguments
|
||||
var (
|
||||
debug bool
|
||||
version bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&debug, "debug", false, "Verbose debug logging")
|
||||
flag.BoolVar(&version, "version", false, "Show version number and exit")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if version {
|
||||
fmt.Printf("This is script.go v%s\n", Version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Parse the script name and remaining arguments.
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
usage()
|
||||
}
|
||||
scriptName := args[0]
|
||||
argv := args[1:]
|
||||
|
||||
// Verify it's a file.
|
||||
if _, err := os.Stat(scriptName); os.IsNotExist(err) {
|
||||
die("%s: not a file", scriptName)
|
||||
}
|
||||
|
||||
// Make a temp file with a *.go extension
|
||||
tmpfile, err := NamedTempFile("", "script", ".go")
|
||||
if err != nil {
|
||||
die("tempfile error: %s", err)
|
||||
}
|
||||
log("scriptName: %s; tmpFile: %s", scriptName, tmpfile.Name())
|
||||
|
||||
// Read the source and write it to the new file.
|
||||
src, err := ioutil.ReadFile(scriptName)
|
||||
dieIfError(err)
|
||||
_, err = tmpfile.Write(src)
|
||||
dieIfError(err)
|
||||
err = tmpfile.Close()
|
||||
dieIfError(err)
|
||||
|
||||
// Catch interrupt signals to clean up the tempfile.
|
||||
interrupt := make(chan os.Signal, 2)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-interrupt
|
||||
log("interrupt detected; cleaning up tempfile")
|
||||
err = os.Remove(tmpfile.Name())
|
||||
if err != nil {
|
||||
die("remove tmpfile error: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Finally, `go run` the script from $TMPDIR.
|
||||
goArgs := append([]string{"run", tmpfile.Name()}, argv...)
|
||||
c := exec.Command(
|
||||
"go",
|
||||
goArgs...,
|
||||
)
|
||||
c.Stdin = os.Stdin
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
err = c.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("[script.go] script error: %s\n", err)
|
||||
}
|
||||
|
||||
log("cleaning up tempfile")
|
||||
os.Remove(tmpfile.Name())
|
||||
}
|
||||
|
||||
// handler for Ctrl-C cleaning up the temp file.
|
||||
func cleanup() {
|
||||
fmt.Println("cleanup")
|
||||
}
|
||||
|
||||
// NamedTempFile is like ioutil.TempFile but accepts a suffix too.
|
||||
func NamedTempFile(dir, prefix, suffix string) (f *os.File, err error) {
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
// Random string generator.
|
||||
randomString := func() string {
|
||||
randBytes := make([]byte, 16)
|
||||
rand.Read(randBytes)
|
||||
return hex.EncodeToString(randBytes)
|
||||
}
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
name := filepath.Join(dir, prefix+randomString()+suffix)
|
||||
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if os.IsExist(err) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Print(
|
||||
"Usage: script.go [options] <go script path>\n",
|
||||
"See script.go -h for command line options.\n",
|
||||
)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func log(message string, v ...interface{}) {
|
||||
if debug {
|
||||
fmt.Printf("[script.go] "+message+"\n", v...)
|
||||
}
|
||||
}
|
||||
|
||||
func die(message string, v ...interface{}) {
|
||||
fmt.Printf(message+"\n", v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func dieIfError(err error) {
|
||||
if err != nil {
|
||||
die(err.Error())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user