package shellquote import ( "bytes" "strings" "unicode/utf8" ) // Join quotes each argument and joins them with a space. // If passed to /bin/sh, the resulting string will be split back into the // original arguments. func Join(args ...string) string { var buf bytes.Buffer for i, arg := range args { if i != 0 { buf.WriteByte(' ') } quote(arg, &buf) } return buf.String() } const ( specialChars = "\\'\"`${[|&;<>()*?!" extraSpecialChars = " \t\n" prefixChars = "~" ) func quote(word string, buf *bytes.Buffer) { // We want to try to produce a "nice" output. As such, we will // backslash-escape most characters, but if we encounter a space, or if we // encounter an extra-special char (which doesn't work with // backslash-escaping) we switch over to quoting the whole word. We do this // with a space because it's typically easier for people to read multi-word // arguments when quoted with a space rather than with ugly backslashes // everywhere. origLen := buf.Len() if len(word) == 0 { // oops, no content buf.WriteString("''") return } cur, prev := word, word atStart := true for len(cur) > 0 { c, l := utf8.DecodeRuneInString(cur) cur = cur[l:] if strings.ContainsRune(specialChars, c) || (atStart && strings.ContainsRune(prefixChars, c)) { // copy the non-special chars up to this point if len(cur) < len(prev) { buf.WriteString(prev[0 : len(prev)-len(cur)-l]) } buf.WriteByte('\\') buf.WriteRune(c) prev = cur } else if strings.ContainsRune(extraSpecialChars, c) { // start over in quote mode buf.Truncate(origLen) goto quote } atStart = false } if len(prev) > 0 { buf.WriteString(prev) } return quote: // quote mode // Use single-quotes, but if we find a single-quote in the word, we need // to terminate the string, emit an escaped quote, and start the string up // again inQuote := false for len(word) > 0 { i := strings.IndexRune(word, '\'') if i == -1 { break } if i > 0 { if !inQuote { buf.WriteByte('\'') inQuote = true } buf.WriteString(word[0:i]) } word = word[i+1:] if inQuote { buf.WriteByte('\'') inQuote = false } buf.WriteString("\\'") } if len(word) > 0 { if !inQuote { buf.WriteByte('\'') } buf.WriteString(word) buf.WriteByte('\'') } }