quote.go (2331B)
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 }