pax_global_header00006660000000000000000000000064140564226070014520gustar00rootroot0000000000000052 comment=973b9d5391598d4ee601db46fa32f6e186a356ac go-shellwords-1.0.12/000077500000000000000000000000001405642260700143725ustar00rootroot00000000000000go-shellwords-1.0.12/.github/000077500000000000000000000000001405642260700157325ustar00rootroot00000000000000go-shellwords-1.0.12/.github/FUNDING.yml000066400000000000000000000013151405642260700175470ustar00rootroot00000000000000# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: mattn # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] go-shellwords-1.0.12/.github/workflows/000077500000000000000000000000001405642260700177675ustar00rootroot00000000000000go-shellwords-1.0.12/.github/workflows/ci.yml000066400000000000000000000007741405642260700211150ustar00rootroot00000000000000name: ci on: pull_request: branches: - master push: branches: - master jobs: test: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] go: ["1.14", "1.15"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - run: | go test -cover -coverprofile coverage.txt -race -v ./... - uses: codecov/codecov-action@v1 go-shellwords-1.0.12/.travis.yml000066400000000000000000000002771405642260700165110ustar00rootroot00000000000000arch: - amd64 - ppc64le language: go sudo: false go: - tip before_install: - go get -t -v ./... script: - ./go.test.sh after_success: - bash <(curl -s https://codecov.io/bash) go-shellwords-1.0.12/LICENSE000066400000000000000000000020751405642260700154030ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 Yasuhiro Matsumoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-shellwords-1.0.12/README.md000066400000000000000000000027141405642260700156550ustar00rootroot00000000000000# go-shellwords [![codecov](https://codecov.io/gh/mattn/go-shellwords/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-shellwords) [![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords) [![PkgGoDev](https://pkg.go.dev/badge/github.com/mattn/go-shellwords)](https://pkg.go.dev/github.com/mattn/go-shellwords) [![ci](https://github.com/mattn/go-shellwords/ci/badge.svg)](https://github.com/mattn/go-shellwords/actions) Parse line as shell words. ## Usage ```go args, err := shellwords.Parse("./foo --bar=baz") // args should be ["./foo", "--bar=baz"] ``` ```go envs, args, err := shellwords.ParseWithEnvs("FOO=foo BAR=baz ./foo --bar=baz") // envs should be ["FOO=foo", "BAR=baz"] // args should be ["./foo", "--bar=baz"] ``` ```go os.Setenv("FOO", "bar") p := shellwords.NewParser() p.ParseEnv = true args, err := p.Parse("./foo $FOO") // args should be ["./foo", "bar"] ``` ```go p := shellwords.NewParser() p.ParseBacktick = true args, err := p.Parse("./foo `echo $SHELL`") // args should be ["./foo", "/bin/bash"] ``` ```go shellwords.ParseBacktick = true p := shellwords.NewParser() args, err := p.Parse("./foo `echo $SHELL`") // args should be ["./foo", "/bin/bash"] ``` # Thanks This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine). # License under the MIT License: http://mattn.mit-license.org/2017 # Author Yasuhiro Matsumoto (a.k.a mattn) go-shellwords-1.0.12/_example/000077500000000000000000000000001405642260700161645ustar00rootroot00000000000000go-shellwords-1.0.12/_example/pipe.go000066400000000000000000000011751405642260700174540ustar00rootroot00000000000000// +build ignore package main import ( "fmt" "log" "github.com/mattn/go-shellwords" ) func isSpace(r byte) bool { switch r { case ' ', '\t', '\r', '\n': return true } return false } func main() { line := ` /usr/bin/ls -la | sort 2>&1 | tee files.log ` parser := shellwords.NewParser() for { args, err := parser.Parse(line) if err != nil { log.Fatal(err) } fmt.Println(args) if parser.Position < 0 { break } i := parser.Position for ; i < len(line); i++ { if isSpace(line[i]) { break } } fmt.Println(string([]rune(line)[parser.Position:i])) line = string([]rune(line)[i+1:]) } } go-shellwords-1.0.12/go.mod000066400000000000000000000000571405642260700155020ustar00rootroot00000000000000module github.com/mattn/go-shellwords go 1.13 go-shellwords-1.0.12/go.test.sh000077500000000000000000000004131405642260700163120ustar00rootroot00000000000000#!/usr/bin/env bash set -e echo "" > coverage.txt for d in $(go list ./... | grep -v vendor); do go test -coverprofile=profile.out -covermode=atomic "$d" if [ -f profile.out ]; then cat profile.out >> coverage.txt rm profile.out fi done go-shellwords-1.0.12/shellwords.go000066400000000000000000000134411405642260700171120ustar00rootroot00000000000000package shellwords import ( "bytes" "errors" "os" "strings" "unicode" ) var ( ParseEnv bool = false ParseBacktick bool = false ) func isSpace(r rune) bool { switch r { case ' ', '\t', '\r', '\n': return true } return false } func replaceEnv(getenv func(string) string, s string) string { if getenv == nil { getenv = os.Getenv } var buf bytes.Buffer rs := []rune(s) for i := 0; i < len(rs); i++ { r := rs[i] if r == '\\' { i++ if i == len(rs) { break } buf.WriteRune(rs[i]) continue } else if r == '$' { i++ if i == len(rs) { buf.WriteRune(r) break } if rs[i] == 0x7b { i++ p := i for ; i < len(rs); i++ { r = rs[i] if r == '\\' { i++ if i == len(rs) { return s } continue } if r == 0x7d || (!unicode.IsLetter(r) && r != '_' && !unicode.IsDigit(r)) { break } } if r != 0x7d { return s } if i > p { buf.WriteString(getenv(s[p:i])) } } else { p := i for ; i < len(rs); i++ { r := rs[i] if r == '\\' { i++ if i == len(rs) { return s } continue } if !unicode.IsLetter(r) && r != '_' && !unicode.IsDigit(r) { break } } if i > p { buf.WriteString(getenv(s[p:i])) i-- } else { buf.WriteString(s[p:]) } } } else { buf.WriteRune(r) } } return buf.String() } type Parser struct { ParseEnv bool ParseBacktick bool Position int Dir string // If ParseEnv is true, use this for getenv. // If nil, use os.Getenv. Getenv func(string) string } func NewParser() *Parser { return &Parser{ ParseEnv: ParseEnv, ParseBacktick: ParseBacktick, Position: 0, Dir: "", } } type argType int const ( argNo argType = iota argSingle argQuoted ) func (p *Parser) Parse(line string) ([]string, error) { args := []string{} buf := "" var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool backtick := "" pos := -1 got := argNo i := -1 loop: for _, r := range line { i++ if escaped { buf += string(r) escaped = false got = argSingle continue } if r == '\\' { if singleQuoted { buf += string(r) } else { escaped = true } continue } if isSpace(r) { if singleQuoted || doubleQuoted || backQuote || dollarQuote { buf += string(r) backtick += string(r) } else if got != argNo { if p.ParseEnv { if got == argSingle { parser := &Parser{ParseEnv: false, ParseBacktick: false, Position: 0, Dir: p.Dir} strs, err := parser.Parse(replaceEnv(p.Getenv, buf)) if err != nil { return nil, err } args = append(args, strs...) } else { args = append(args, replaceEnv(p.Getenv, buf)) } } else { args = append(args, buf) } buf = "" got = argNo } continue } switch r { case '`': if !singleQuoted && !doubleQuoted && !dollarQuote { if p.ParseBacktick { if backQuote { out, err := shellRun(backtick, p.Dir) if err != nil { return nil, err } buf = buf[:len(buf)-len(backtick)] + out } backtick = "" backQuote = !backQuote continue } backtick = "" backQuote = !backQuote } case ')': if !singleQuoted && !doubleQuoted && !backQuote { if p.ParseBacktick { if dollarQuote { out, err := shellRun(backtick, p.Dir) if err != nil { return nil, err } buf = buf[:len(buf)-len(backtick)-2] + out } backtick = "" dollarQuote = !dollarQuote continue } backtick = "" dollarQuote = !dollarQuote } case '(': if !singleQuoted && !doubleQuoted && !backQuote { if !dollarQuote && strings.HasSuffix(buf, "$") { dollarQuote = true buf += "(" continue } else { return nil, errors.New("invalid command line string") } } case '"': if !singleQuoted && !dollarQuote { if doubleQuoted { got = argQuoted } doubleQuoted = !doubleQuoted continue } case '\'': if !doubleQuoted && !dollarQuote { if singleQuoted { got = argQuoted } singleQuoted = !singleQuoted continue } case ';', '&', '|', '<', '>': if !(escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote) { if r == '>' && len(buf) > 0 { if c := buf[0]; '0' <= c && c <= '9' { i -= 1 got = argNo } } pos = i break loop } } got = argSingle buf += string(r) if backQuote || dollarQuote { backtick += string(r) } } if got != argNo { if p.ParseEnv { if got == argSingle { parser := &Parser{ParseEnv: false, ParseBacktick: false, Position: 0, Dir: p.Dir} strs, err := parser.Parse(replaceEnv(p.Getenv, buf)) if err != nil { return nil, err } args = append(args, strs...) } else { args = append(args, replaceEnv(p.Getenv, buf)) } } else { args = append(args, buf) } } if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote { return nil, errors.New("invalid command line string") } p.Position = pos return args, nil } func (p *Parser) ParseWithEnvs(line string) (envs []string, args []string, err error) { _args, err := p.Parse(line) if err != nil { return nil, nil, err } envs = []string{} args = []string{} parsingEnv := true for _, arg := range _args { if parsingEnv && isEnv(arg) { envs = append(envs, arg) } else { if parsingEnv { parsingEnv = false } args = append(args, arg) } } return envs, args, nil } func isEnv(arg string) bool { return len(strings.Split(arg, "=")) == 2 } func Parse(line string) ([]string, error) { return NewParser().Parse(line) } func ParseWithEnvs(line string) (envs []string, args []string, err error) { return NewParser().ParseWithEnvs(line) } go-shellwords-1.0.12/shellwords_test.go000066400000000000000000000271761405642260700201630ustar00rootroot00000000000000package shellwords import ( "errors" "go/build" "os" "os/exec" "path" "reflect" "testing" ) var testcases = []struct { line string expected []string }{ {``, []string{}}, {`""`, []string{``}}, {`''`, []string{``}}, {`var --bar=baz`, []string{`var`, `--bar=baz`}}, {`var --bar="baz"`, []string{`var`, `--bar=baz`}}, {`var "--bar=baz"`, []string{`var`, `--bar=baz`}}, {`var "--bar='baz'"`, []string{`var`, `--bar='baz'`}}, {"var --bar=`baz`", []string{`var`, "--bar=`baz`"}}, {`var "--bar=\"baz'"`, []string{`var`, `--bar="baz'`}}, {`var "--bar=\'baz\'"`, []string{`var`, `--bar='baz'`}}, {`var --bar='\'`, []string{`var`, `--bar=\`}}, {`var "--bar baz"`, []string{`var`, `--bar baz`}}, {`var --"bar baz"`, []string{`var`, `--bar baz`}}, {`var --"bar baz"`, []string{`var`, `--bar baz`}}, {`a "b"`, []string{`a`, `b`}}, {`a " b "`, []string{`a`, ` b `}}, {`a " "`, []string{`a`, ` `}}, {`a 'b'`, []string{`a`, `b`}}, {`a ' b '`, []string{`a`, ` b `}}, {`a ' '`, []string{`a`, ` `}}, {"foo bar\\ ", []string{`foo`, `bar `}}, {`foo "" bar ''`, []string{`foo`, ``, `bar`, ``}}, {`foo \\`, []string{`foo`, `\`}}, {`foo \& bar`, []string{`foo`, `&`, `bar`}}, } func TestSimple(t *testing.T) { for _, testcase := range testcases { args, err := Parse(testcase.line) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(args, testcase.expected) { t.Fatalf("Expected %#v for %q, but %#v:", testcase.expected, testcase.line, args) } } } func TestError(t *testing.T) { _, err := Parse("foo '") if err == nil { t.Fatal("Should be an error") } _, err = Parse(`foo "`) if err == nil { t.Fatal("Should be an error") } _, err = Parse("foo `") if err == nil { t.Fatal("Should be an error") } } func TestShellRun(t *testing.T) { dir, err := os.Getwd() if err != nil { t.Fatal(err) } pwd, err := shellRun("pwd", "") if err != nil { t.Fatal(err) } pwd2, err := shellRun("pwd", path.Join(dir, "/_example")) if err != nil { t.Fatal(err) } if pwd == pwd2 { t.Fatal("`pwd` should be changed") } } func TestShellRunNoEnv(t *testing.T) { old := os.Getenv("SHELL") defer os.Setenv("SHELL", old) os.Unsetenv("SHELL") dir, err := os.Getwd() if err != nil { t.Fatal(err) } pwd, err := shellRun("pwd", "") if err != nil { t.Fatal(err) } pwd2, err := shellRun("pwd", path.Join(dir, "/_example")) if err != nil { t.Fatal(err) } if pwd == pwd2 { t.Fatal("`pwd` should be changed") } } func TestBacktick(t *testing.T) { goversion, err := shellRun("go version", "") if err != nil { t.Fatal(err) } parser := NewParser() parser.ParseBacktick = true args, err := parser.Parse("echo `go version`") if err != nil { t.Fatal(err) } expected := []string{"echo", goversion} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } args, err = parser.Parse(`echo $(echo foo)`) if err != nil { t.Fatal(err) } expected = []string{"echo", "foo"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } args, err = parser.Parse(`echo bar=$(echo 200)cm`) if err != nil { t.Fatal(err) } expected = []string{"echo", "bar=200cm"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } parser.ParseBacktick = false args, err = parser.Parse(`echo $(echo "foo")`) if err != nil { t.Fatal(err) } expected = []string{"echo", `$(echo "foo")`} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } args, err = parser.Parse("echo $(`echo1)") if err != nil { t.Fatal(err) } expected = []string{"echo", "$(`echo1)"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestBacktickMulti(t *testing.T) { parser := NewParser() parser.ParseBacktick = true args, err := parser.Parse(`echo $(go env GOPATH && go env GOROOT)`) if err != nil { t.Fatal(err) } expected := []string{"echo", build.Default.GOPATH + "\n" + build.Default.GOROOT} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestBacktickError(t *testing.T) { parser := NewParser() parser.ParseBacktick = true _, err := parser.Parse("echo `go Version`") if err == nil { t.Fatal("Should be an error") } var eerr *exec.ExitError if !errors.As(err, &eerr) { t.Fatal("Should be able to unwrap to *exec.ExitError") } _, err = parser.Parse(`echo $(echo1)`) if err == nil { t.Fatal("Should be an error") } _, err = parser.Parse(`echo FOO=$(echo1)`) if err == nil { t.Fatal("Should be an error") } _, err = parser.Parse(`echo $(echo1`) if err == nil { t.Fatal("Should be an error") } _, err = parser.Parse(`echo $ (echo1`) if err == nil { t.Fatal("Should be an error") } _, err = parser.Parse(`echo (echo1`) if err == nil { t.Fatal("Should be an error") } _, err = parser.Parse(`echo )echo1`) if err == nil { t.Fatal("Should be an error") } } func TestEnv(t *testing.T) { os.Setenv("FOO", "bar") parser := NewParser() parser.ParseEnv = true args, err := parser.Parse("echo $FOO") if err != nil { t.Fatal(err) } expected := []string{"echo", "bar"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestCustomEnv(t *testing.T) { parser := NewParser() parser.ParseEnv = true parser.Getenv = func(k string) string { return map[string]string{"FOO": "baz"}[k] } args, err := parser.Parse("echo $FOO") if err != nil { t.Fatal(err) } expected := []string{"echo", "baz"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestNoEnv(t *testing.T) { parser := NewParser() parser.ParseEnv = true args, err := parser.Parse("echo $BAR") if err != nil { t.Fatal(err) } expected := []string{"echo"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestEnvArguments(t *testing.T) { os.Setenv("FOO", "bar baz") parser := NewParser() parser.ParseEnv = true args, err := parser.Parse("echo $FOO") if err != nil { t.Fatal(err) } expected := []string{"echo", "bar", "baz"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestEnvArgumentsFail(t *testing.T) { os.Setenv("FOO", "bar '") parser := NewParser() parser.ParseEnv = true _, err := parser.Parse("echo $FOO") if err == nil { t.Fatal("Should be an error") } _, err = parser.Parse("$FOO") if err == nil { t.Fatal("Should be an error") } _, err = parser.Parse("echo $FOO") if err == nil { t.Fatal("Should be an error") } os.Setenv("FOO", "bar `") result, err := parser.Parse("$FOO ") if err == nil { t.Fatal("Should be an error: ", result) } } func TestDupEnv(t *testing.T) { os.Setenv("FOO", "bar") os.Setenv("FOO_BAR", "baz") parser := NewParser() parser.ParseEnv = true args, err := parser.Parse("echo $FOO$") if err != nil { t.Fatal(err) } expected := []string{"echo", "bar$"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } args, err = parser.Parse("echo ${FOO_BAR}$") if err != nil { t.Fatal(err) } expected = []string{"echo", "baz$"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestHaveMore(t *testing.T) { parser := NewParser() parser.ParseEnv = true line := "echo 🍺; seq 1 10" args, err := parser.Parse(line) if err != nil { t.Fatalf(err.Error()) } expected := []string{"echo", "🍺"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } if parser.Position == 0 { t.Fatalf("Commands should be remaining") } line = string([]rune(line)[parser.Position+1:]) args, err = parser.Parse(line) if err != nil { t.Fatalf(err.Error()) } expected = []string{"seq", "1", "10"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } if parser.Position > 0 { t.Fatalf("Commands should not be remaining") } } func TestHaveRedirect(t *testing.T) { parser := NewParser() parser.ParseEnv = true line := "ls -la 2>foo" args, err := parser.Parse(line) if err != nil { t.Fatalf(err.Error()) } expected := []string{"ls", "-la"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } if parser.Position == 0 { t.Fatalf("Commands should be remaining") } } func TestBackquoteInFlag(t *testing.T) { parser := NewParser() parser.ParseBacktick = true args, err := parser.Parse("cmd -flag=`echo val1` -flag=val2") if err != nil { panic(err) } expected := []string{"cmd", "-flag=val1", "-flag=val2"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestEnvInQuoted(t *testing.T) { os.Setenv("FOO", "bar") parser := NewParser() parser.ParseEnv = true args, err := parser.Parse(`ssh 127.0.0.1 "echo $FOO"`) if err != nil { panic(err) } expected := []string{"ssh", "127.0.0.1", "echo bar"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } args, err = parser.Parse(`ssh 127.0.0.1 "echo \\$FOO"`) if err != nil { panic(err) } expected = []string{"ssh", "127.0.0.1", "echo $FOO"} if !reflect.DeepEqual(args, expected) { t.Fatalf("Expected %#v, but %#v:", expected, args) } } func TestParseWithEnvs(t *testing.T) { tests := []struct { line string wantEnvs, wantArgs []string }{ { line: "FOO=foo cmd --args=A=B", wantEnvs: []string{"FOO=foo"}, wantArgs: []string{"cmd", "--args=A=B"}, }, { line: "FOO=foo BAR=bar cmd --args=A=B -A=B", wantEnvs: []string{"FOO=foo", "BAR=bar"}, wantArgs: []string{"cmd", "--args=A=B", "-A=B"}, }, { line: `sh -c "FOO=foo BAR=bar cmd --args=A=B -A=B"`, wantEnvs: []string{}, wantArgs: []string{"sh", "-c", "FOO=foo BAR=bar cmd --args=A=B -A=B"}, }, { line: "cmd --args=A=B -A=B", wantEnvs: []string{}, wantArgs: []string{"cmd", "--args=A=B", "-A=B"}, }, } for _, tt := range tests { t.Run(tt.line, func(t *testing.T) { envs, args, err := ParseWithEnvs(tt.line) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(envs, tt.wantEnvs) { t.Errorf("Expected %#v, but %#v", tt.wantEnvs, envs) } if !reflect.DeepEqual(args, tt.wantArgs) { t.Errorf("Expected %#v, but %#v", tt.wantArgs, args) } }) } } func TestSubShellEnv(t *testing.T) { myParser := &Parser{ ParseEnv: true, } errTmpl := "bad arg parsing:\nexpected: %#v\nactual : %#v\n" t.Run("baseline", func(t *testing.T) { args, err := myParser.Parse(`program -f abc.txt`) if err != nil { t.Fatalf("err should be nil: %v", err) } expected := []string{"program", "-f", "abc.txt"} if len(args) != 3 { t.Fatalf(errTmpl, expected, args) } if args[0] != expected[0] || args[1] != expected[1] || args[2] != expected[2] { t.Fatalf(errTmpl, expected, args) } }) t.Run("single-quoted", func(t *testing.T) { args, err := myParser.Parse(`sh -c 'echo foo'`) if err != nil { t.Fatalf("err should be nil: %v", err) } expected := []string{"sh", "-c", "echo foo"} if len(args) != 3 { t.Fatalf(errTmpl, expected, args) } if args[0] != expected[0] || args[1] != expected[1] || args[2] != expected[2] { t.Fatalf(errTmpl, expected, args) } }) t.Run("double-quoted", func(t *testing.T) { args, err := myParser.Parse(`sh -c "echo foo"`) if err != nil { t.Fatalf("err should be nil: %v", err) } expected := []string{"sh", "-c", "echo foo"} if len(args) != 3 { t.Fatalf(errTmpl, expected, args) } if args[0] != expected[0] || args[1] != expected[1] || args[2] != expected[2] { t.Fatalf(errTmpl, expected, args) } }) } go-shellwords-1.0.12/util_posix.go000066400000000000000000000007711405642260700171250ustar00rootroot00000000000000// +build !windows package shellwords import ( "fmt" "os" "os/exec" "strings" ) func shellRun(line, dir string) (string, error) { var shell string if shell = os.Getenv("SHELL"); shell == "" { shell = "/bin/sh" } cmd := exec.Command(shell, "-c", line) if dir != "" { cmd.Dir = dir } b, err := cmd.Output() if err != nil { if eerr, ok := err.(*exec.ExitError); ok { b = eerr.Stderr } return "", fmt.Errorf("%s: %w", string(b), err) } return strings.TrimSpace(string(b)), nil } go-shellwords-1.0.12/util_windows.go000066400000000000000000000007661405642260700174610ustar00rootroot00000000000000// +build windows package shellwords import ( "fmt" "os" "os/exec" "strings" ) func shellRun(line, dir string) (string, error) { var shell string if shell = os.Getenv("COMSPEC"); shell == "" { shell = "cmd" } cmd := exec.Command(shell, "/c", line) if dir != "" { cmd.Dir = dir } b, err := cmd.Output() if err != nil { if eerr, ok := err.(*exec.ExitError); ok { b = eerr.Stderr } return "", fmt.Errorf("%s: %w", string(b), err) } return strings.TrimSpace(string(b)), nil }