shellwords.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package shellwords
  2. import (
  3. "errors"
  4. "os"
  5. "regexp"
  6. )
  7. var (
  8. ParseEnv bool = false
  9. ParseBacktick bool = false
  10. )
  11. var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
  12. func isSpace(r rune) bool {
  13. switch r {
  14. case ' ', '\t', '\r', '\n':
  15. return true
  16. }
  17. return false
  18. }
  19. func replaceEnv(s string) string {
  20. return envRe.ReplaceAllStringFunc(s, func(s string) string {
  21. s = s[1:]
  22. if s[0] == '{' {
  23. s = s[1 : len(s)-1]
  24. }
  25. return os.Getenv(s)
  26. })
  27. }
  28. type Parser struct {
  29. ParseEnv bool
  30. ParseBacktick bool
  31. Position int
  32. }
  33. func NewParser() *Parser {
  34. return &Parser{ParseEnv, ParseBacktick, 0}
  35. }
  36. func (p *Parser) Parse(line string) ([]string, error) {
  37. args := []string{}
  38. buf := ""
  39. var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
  40. backtick := ""
  41. pos := -1
  42. got := false
  43. loop:
  44. for i, r := range line {
  45. if escaped {
  46. buf += string(r)
  47. escaped = false
  48. continue
  49. }
  50. if r == '\\' {
  51. if singleQuoted {
  52. buf += string(r)
  53. } else {
  54. escaped = true
  55. }
  56. continue
  57. }
  58. if isSpace(r) {
  59. if singleQuoted || doubleQuoted || backQuote || dollarQuote {
  60. buf += string(r)
  61. backtick += string(r)
  62. } else if got {
  63. if p.ParseEnv {
  64. buf = replaceEnv(buf)
  65. }
  66. args = append(args, buf)
  67. buf = ""
  68. got = false
  69. }
  70. continue
  71. }
  72. switch r {
  73. case '`':
  74. if !singleQuoted && !doubleQuoted && !dollarQuote {
  75. if p.ParseBacktick {
  76. if backQuote {
  77. out, err := shellRun(backtick)
  78. if err != nil {
  79. return nil, err
  80. }
  81. buf = out
  82. }
  83. backtick = ""
  84. backQuote = !backQuote
  85. continue
  86. }
  87. backtick = ""
  88. backQuote = !backQuote
  89. }
  90. case ')':
  91. if !singleQuoted && !doubleQuoted && !backQuote {
  92. if p.ParseBacktick {
  93. if dollarQuote {
  94. out, err := shellRun(backtick)
  95. if err != nil {
  96. return nil, err
  97. }
  98. buf = out
  99. }
  100. backtick = ""
  101. dollarQuote = !dollarQuote
  102. continue
  103. }
  104. backtick = ""
  105. dollarQuote = !dollarQuote
  106. }
  107. case '(':
  108. if !singleQuoted && !doubleQuoted && !backQuote {
  109. if !dollarQuote && len(buf) > 0 && buf == "$" {
  110. dollarQuote = true
  111. buf += "("
  112. continue
  113. } else {
  114. return nil, errors.New("invalid command line string")
  115. }
  116. }
  117. case '"':
  118. if !singleQuoted && !dollarQuote {
  119. doubleQuoted = !doubleQuoted
  120. continue
  121. }
  122. case '\'':
  123. if !doubleQuoted && !dollarQuote {
  124. singleQuoted = !singleQuoted
  125. continue
  126. }
  127. case ';', '&', '|', '<', '>':
  128. if !(escaped || singleQuoted || doubleQuoted || backQuote) {
  129. if r == '>' && len(buf) > 0 {
  130. if c := buf[0]; '0' <= c && c <= '9' {
  131. i -= 1
  132. got = false
  133. }
  134. }
  135. pos = i
  136. break loop
  137. }
  138. }
  139. got = true
  140. buf += string(r)
  141. if backQuote || dollarQuote {
  142. backtick += string(r)
  143. }
  144. }
  145. if got {
  146. if p.ParseEnv {
  147. buf = replaceEnv(buf)
  148. }
  149. args = append(args, buf)
  150. }
  151. if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
  152. return nil, errors.New("invalid command line string")
  153. }
  154. p.Position = pos
  155. return args, nil
  156. }
  157. func Parse(line string) ([]string, error) {
  158. return NewParser().Parse(line)
  159. }