Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ args, err := p.Parse("./foo `echo $SHELL`")
// args should be ["./foo", "/bin/bash"]
```

```go
p := shellwords.NewParser()
p.ParseComment = true
args, err := p.Parse("./foo # comment")
// args should be ["./foo"]
```

# Thanks

This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
Expand Down
18 changes: 17 additions & 1 deletion shellwords.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
var (
ParseEnv bool = false
ParseBacktick bool = false
ParseComment bool = false
)

func isSpace(r rune) bool {
Expand Down Expand Up @@ -97,6 +98,7 @@ func replaceEnv(getenv func(string) string, s string) string {
type Parser struct {
ParseEnv bool
ParseBacktick bool
ParseComment bool
Position int
Dir string

Expand All @@ -109,6 +111,7 @@ func NewParser() *Parser {
return &Parser{
ParseEnv: ParseEnv,
ParseBacktick: ParseBacktick,
ParseComment: ParseComment,
Position: 0,
Dir: "",
}
Expand All @@ -125,7 +128,7 @@ const (
func (p *Parser) Parse(line string) ([]string, error) {
args := []string{}
buf := ""
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote, comment bool
backtick := ""

pos := -1
Expand All @@ -135,6 +138,14 @@ func (p *Parser) Parse(line string) ([]string, error) {
loop:
for _, r := range line {
i++

if comment {
if r == '\n' {
comment = false
}
continue
}

if escaped {
if r == 't' {
r = '\t'
Expand Down Expand Up @@ -254,6 +265,11 @@ loop:
pos = i
break loop
}
case '#':
if p.ParseComment && len(buf) == 0 && !(escaped || singleQuoted || doubleQuoted) {
comment = true
continue loop
}
}

got = argSingle
Expand Down
30 changes: 28 additions & 2 deletions shellwords_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import (
"testing"
)

var testcases = []struct {
type testcase struct {
line string
expected []string
}{
}

var testcases = []testcase{
{``, []string{}},
{`""`, []string{``}},
{`''`, []string{``}},
Expand Down Expand Up @@ -53,6 +55,30 @@ func TestSimple(t *testing.T) {
}
}

func TestComment(t *testing.T) {
allCases := append(testcases, []testcase{
{"# comment", []string{}},
{"foo not#comment", []string{"foo", "not#comment"}},
{`foo "bar # baz" # comment`, []string{"foo", "bar # baz"}},
{"foo \"bar # baz\" # comment\nfoo\nbar # baz",
[]string{"foo", "bar # baz", "foo", "bar"}},
{"echo '# list all files' # line\\ncomment\n# whole line comment\nls -al '#' # more comment",
[]string{"echo", "# list all files", "ls", "-al", "#"}},
}...)

parser := NewParser()
parser.ParseComment = true
for _, testcase := range allCases {
args, err := parser.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 {
Expand Down