// Copyright (c) 2021, Daniel Martà <mvdan@mvdan.cc>// See LICENSE for licensing informationpackage syntaximport ()typeQuoteErrorstruct { ByteOffset int Message string}func ( QuoteError) () string {returnfmt.Sprintf("cannot quote character at byte %d: %s", .ByteOffset, .Message)}const ( quoteErrNull = "shell strings cannot contain null bytes" quoteErrPOSIX = "POSIX shell lacks escape sequences" quoteErrRange = "rune out of range" quoteErrMksh = "mksh cannot escape codepoints above 16 bits")// Quote returns a quoted version of the input string,// so that the quoted version is expanded or interpreted// as the original string in the given language variant.//// Quoting is necessary when using arbitrary literal strings// as words in a shell script or command.// Without quoting, one can run into syntax errors,// as well as the possibility of running unintended code.//// An error is returned when a string cannot be quoted for a variant.// For instance, POSIX lacks escape sequences for non-printable characters,// and no language variant can represent a string containing null bytes.// In such cases, the returned error type will be *QuoteError.//// The quoting strategy is chosen on a best-effort basis,// to minimize the amount of extra bytes necessary.//// Some strings do not require any quoting and are returned unchanged.// Those strings can be directly surrounded in single quotes as well.func ( string, LangVariant) (string, error) {if == "" {// Special case; an empty string must always be quoted, // as otherwise it expands to zero fields.return"''", nil } := false := false := 0for := ; len() > 0; { , := utf8.DecodeRuneInString()switch {// Like regOps; token characters.case';', '"', '\'', '(', ')', '$', '|', '&', '>', '<', '`',// Whitespace; might result in multiple fields.' ', '\t', '\r', '\n',// Escape sequences would be expanded.'\\',// Would start a comment unless quoted.'#',// Might result in brace expansion.'{',// Might result in tilde expansion.'~',// Might result in globbing.'*', '?', '[',// Might result in an assignment.'=': = truecase'\x00':return"", &QuoteError{ByteOffset: , Message: quoteErrNull} }if == utf8.RuneError || !unicode.IsPrint() {if == LangPOSIX {return"", &QuoteError{ByteOffset: , Message: quoteErrPOSIX} } = true } = [:] += }if ! && ! && !IsKeyword() {// Nothing to quote; avoid allocating.return , nil }// Single quotes are usually best, // as they don't require any escaping of characters. // If we have any invalid utf8 or non-printable runes, // use $'' so that we can escape them. // Note that we can't use double quotes for those.varstrings.Builderif { .WriteString("$'") := false := 0for := ; len() > 0; { := false , := utf8.DecodeRuneInString()switch {case == '\'', == '\\': .WriteByte('\\') .WriteRune()caseunicode.IsPrint() && != utf8.RuneError:if && isHex() { .WriteString("'$'") } .WriteRune()case == '\a': .WriteString(`\a`)case == '\b': .WriteString(`\b`)case == '\f': .WriteString(`\f`)case == '\n': .WriteString(`\n`)case == '\r': .WriteString(`\r`)case == '\t': .WriteString(`\t`)case == '\v': .WriteString(`\v`)case < utf8.RuneSelf, == utf8.RuneError && == 1:// \xXX, fixed at two hexadecimal characters.fmt.Fprintf(&, "\\x%02x", [0])// Unfortunately, mksh allows \x to consume more hex characters. // Ensure that we don't allow it to read more than two.if == LangMirBSDKorn { = true }case > utf8.MaxRune:// Not a valid Unicode code point?return"", &QuoteError{ByteOffset: , Message: quoteErrRange}case == LangMirBSDKorn && > 0xFFFD:// From the CAVEATS section in R59's man page: // // mksh currently uses OPTU-16 internally, which is the same as // UTF-8 and CESU-8 with 0000..FFFD being valid codepoints.return"", &QuoteError{ByteOffset: , Message: quoteErrMksh}case < 0x10000:// \uXXXX, fixed at four hexadecimal characters.fmt.Fprintf(&, "\\u%04x", )default:// \UXXXXXXXX, fixed at eight hexadecimal characters.fmt.Fprintf(&, "\\U%08x", ) } = [:] = += } .WriteString("'")return .String(), nil }// Single quotes without any need for escaping.if !strings.Contains(, "'") {return"'" + + "'", nil }// The string contains single quotes, // so fall back to double quotes. .WriteByte('"')for , := range {switch {case'"', '\\', '`', '$': .WriteByte('\\') } .WriteRune() } .WriteByte('"')return .String(), nil}func isHex( rune) bool {return ( >= '0' && <= '9') || ( >= 'a' && <= 'f') || ( >= 'A' && <= 'F')}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.