[822] | 1 | package shellquote
|
---|
| 2 |
|
---|
| 3 | import (
|
---|
| 4 | "bytes"
|
---|
| 5 | "strings"
|
---|
| 6 | "unicode/utf8"
|
---|
| 7 | )
|
---|
| 8 |
|
---|
| 9 | // Join quotes each argument and joins them with a space.
|
---|
| 10 | // If passed to /bin/sh, the resulting string will be split back into the
|
---|
| 11 | // original arguments.
|
---|
| 12 | func Join(args ...string) string {
|
---|
| 13 | var buf bytes.Buffer
|
---|
| 14 | for i, arg := range args {
|
---|
| 15 | if i != 0 {
|
---|
| 16 | buf.WriteByte(' ')
|
---|
| 17 | }
|
---|
| 18 | quote(arg, &buf)
|
---|
| 19 | }
|
---|
| 20 | return buf.String()
|
---|
| 21 | }
|
---|
| 22 |
|
---|
| 23 | const (
|
---|
| 24 | specialChars = "\\'\"`${[|&;<>()*?!"
|
---|
| 25 | extraSpecialChars = " \t\n"
|
---|
| 26 | prefixChars = "~"
|
---|
| 27 | )
|
---|
| 28 |
|
---|
| 29 | func quote(word string, buf *bytes.Buffer) {
|
---|
| 30 | // We want to try to produce a "nice" output. As such, we will
|
---|
| 31 | // backslash-escape most characters, but if we encounter a space, or if we
|
---|
| 32 | // encounter an extra-special char (which doesn't work with
|
---|
| 33 | // backslash-escaping) we switch over to quoting the whole word. We do this
|
---|
| 34 | // with a space because it's typically easier for people to read multi-word
|
---|
| 35 | // arguments when quoted with a space rather than with ugly backslashes
|
---|
| 36 | // everywhere.
|
---|
| 37 | origLen := buf.Len()
|
---|
| 38 |
|
---|
| 39 | if len(word) == 0 {
|
---|
| 40 | // oops, no content
|
---|
| 41 | buf.WriteString("''")
|
---|
| 42 | return
|
---|
| 43 | }
|
---|
| 44 |
|
---|
| 45 | cur, prev := word, word
|
---|
| 46 | atStart := true
|
---|
| 47 | for len(cur) > 0 {
|
---|
| 48 | c, l := utf8.DecodeRuneInString(cur)
|
---|
| 49 | cur = cur[l:]
|
---|
| 50 | if strings.ContainsRune(specialChars, c) || (atStart && strings.ContainsRune(prefixChars, c)) {
|
---|
| 51 | // copy the non-special chars up to this point
|
---|
| 52 | if len(cur) < len(prev) {
|
---|
| 53 | buf.WriteString(prev[0 : len(prev)-len(cur)-l])
|
---|
| 54 | }
|
---|
| 55 | buf.WriteByte('\\')
|
---|
| 56 | buf.WriteRune(c)
|
---|
| 57 | prev = cur
|
---|
| 58 | } else if strings.ContainsRune(extraSpecialChars, c) {
|
---|
| 59 | // start over in quote mode
|
---|
| 60 | buf.Truncate(origLen)
|
---|
| 61 | goto quote
|
---|
| 62 | }
|
---|
| 63 | atStart = false
|
---|
| 64 | }
|
---|
| 65 | if len(prev) > 0 {
|
---|
| 66 | buf.WriteString(prev)
|
---|
| 67 | }
|
---|
| 68 | return
|
---|
| 69 |
|
---|
| 70 | quote:
|
---|
| 71 | // quote mode
|
---|
| 72 | // Use single-quotes, but if we find a single-quote in the word, we need
|
---|
| 73 | // to terminate the string, emit an escaped quote, and start the string up
|
---|
| 74 | // again
|
---|
| 75 | inQuote := false
|
---|
| 76 | for len(word) > 0 {
|
---|
| 77 | i := strings.IndexRune(word, '\'')
|
---|
| 78 | if i == -1 {
|
---|
| 79 | break
|
---|
| 80 | }
|
---|
| 81 | if i > 0 {
|
---|
| 82 | if !inQuote {
|
---|
| 83 | buf.WriteByte('\'')
|
---|
| 84 | inQuote = true
|
---|
| 85 | }
|
---|
| 86 | buf.WriteString(word[0:i])
|
---|
| 87 | }
|
---|
| 88 | word = word[i+1:]
|
---|
| 89 | if inQuote {
|
---|
| 90 | buf.WriteByte('\'')
|
---|
| 91 | inQuote = false
|
---|
| 92 | }
|
---|
| 93 | buf.WriteString("\\'")
|
---|
| 94 | }
|
---|
| 95 | if len(word) > 0 {
|
---|
| 96 | if !inQuote {
|
---|
| 97 | buf.WriteByte('\'')
|
---|
| 98 | }
|
---|
| 99 | buf.WriteString(word)
|
---|
| 100 | buf.WriteByte('\'')
|
---|
| 101 | }
|
---|
| 102 | }
|
---|