123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- 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('\'')
- }
- }
|