shellwords.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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 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 {
  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 {
  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 {
  92. doubleQuoted = !doubleQuoted
  93. continue
  94. }
  95. case '\'':
  96. if !doubleQuoted {
  97. singleQuoted = !singleQuoted
  98. continue
  99. }
  100. case ';', '&', '|', '<', '>':
  101. if !(escaped || singleQuoted || doubleQuoted || backQuote) {
  102. pos = i
  103. break loop
  104. }
  105. }
  106. got = true
  107. buf += string(r)
  108. if backQuote {
  109. backtick += string(r)
  110. }
  111. }
  112. if got {
  113. if p.ParseEnv {
  114. buf = replaceEnv(buf)
  115. }
  116. args = append(args, buf)
  117. }
  118. if escaped || singleQuoted || doubleQuoted || backQuote {
  119. return nil, errors.New("invalid command line string")
  120. }
  121. p.Position = pos
  122. return args, nil
  123. }
  124. func Parse(line string) ([]string, error) {
  125. return NewParser().Parse(line)
  126. }