From 22f98c754286fbd54bdbc92ac41167dc577d18c9 Mon Sep 17 00:00:00 2001 From: Keegan Carruthers-Smith Date: Mon, 18 Jan 2016 03:57:34 +0200 Subject: [PATCH] Added Commandf and Sprintf --- command.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ command_test.go | 33 +++++++++++++++++++++++++++++++++ escape.go | 4 ++-- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 command.go create mode 100644 command_test.go diff --git a/command.go b/command.go new file mode 100644 index 0000000..3bb8082 --- /dev/null +++ b/command.go @@ -0,0 +1,44 @@ +package shell + +import ( + "fmt" + "io" + "os/exec" +) + +// Commandf runs a shell command based on the format string +func Commandf(format string, a ...interface{}) *exec.Cmd { + shellCmd := Sprintf(format, a...) + return exec.Command("/bin/sh", "-c", shellCmd) +} + +// Sprintf generates a shell command with the format arguments escaped. +func Sprintf(format string, a ...interface{}) string { + wrapped := make([]interface{}, len(a)) + for i, v := range a { + wrapped[i] = escapable{v} + } + return fmt.Sprintf(format, wrapped...) +} + +type escapable struct { + x interface{} +} + +func (e escapable) Format(f fmt.State, c rune) { + s := "%" + for i := 0; i < 128; i++ { + if f.Flag(i) { + s += string(i) + } + } + if w, ok := f.Width(); ok { + s += fmt.Sprintf("%d", w) + } + if p, ok := f.Precision(); ok { + s += fmt.Sprintf(".%d", p) + } + s += string(c) + formatted := fmt.Sprintf(s, e.x) + io.WriteString(f, ReadableEscapeArg(formatted)) +} diff --git a/command_test.go b/command_test.go new file mode 100644 index 0000000..8597e6f --- /dev/null +++ b/command_test.go @@ -0,0 +1,33 @@ +package shell + +import ( + "bytes" + "testing" +) + +func TestCommandf(t *testing.T) { + out, err := Commandf("echo %s world %s", "hello", "quote!me").Output() + if err != nil { + t.Fatal(err) + } + if string(out) != "hello world quote!me\n" { + t.Fatalf("Unexpected hello world output: %#v", string(out)) + } +} + +func TestCommandf_Redirect(t *testing.T) { + var stdout, stderr bytes.Buffer + cmd := Commandf("echo %s; echo %s 1>&2", "hello from stdout", "bye from stderr") + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + t.Fatal(err) + } + if stdout.String() != "hello from stdout\n" { + t.Errorf("Unexpected output from stdout: %v", stdout.String()) + } + if stderr.String() != "bye from stderr\n" { + t.Errorf("Unexpected output from stderr: %v", stderr.String()) + } +} diff --git a/escape.go b/escape.go index 8e174b0..c972829 100644 --- a/escape.go +++ b/escape.go @@ -13,8 +13,8 @@ func EscapeArg(arg string) string { } // ReadableEscapeArg will not escape strings that do not requiring -// escaping. Note that it is conservative in it's approach, so may escape -// strings which do not require it. +// escaping. Note that it is conservative, so may escape strings which do not +// require it. func ReadableEscapeArg(arg string) string { if readableRe.MatchString(arg) { return arg