package main // Here-documents: cmd < len(src) { end = len(src) } cur.WriteString(src[i:end]) return end } for i := 0; i < len(src); { ch := src[i] switch { case ch == '\\': i = copyTo(i, i+2) wordStart = false case ch == '\'': end := strings.IndexByte(src[i+1:], '\'') if end >= 0 { end += i + 2 } i = copyTo(i, end) wordStart = false case ch == '"': end := dquoteEnd(src, i) if end >= 0 { end++ } i = copyTo(i, end) wordStart = false case isSubstStart(src, i): end := substEnd(src, i) if end >= 0 { end++ } i = copyTo(i, end) wordStart = false case ch == '#' && wordStart: // A comment, until the end of the line end := strings.IndexByte(src[i:], '\n') if end >= 0 { end += i } i = copyTo(i, end) case strings.HasPrefix(src[i:], "<<<"): i = copyTo(i, i+3) wordStart = false case strings.HasPrefix(src[i:], "<<"): h, n, err := parseHereOp(src[i:]) if err != nil { return "", err } segs = append(segs, cur.String()) cur.Reset() h.seg = len(segs) segs = append(segs, "") // filled in when the body has been read pending = append(pending, h) i += n wordStart = false case ch == '\n': cur.WriteByte(ch) i++ wordStart = true // The bodies of the here-documents of this line follow for _, h := range pending { body, n, ok := readHereBody(src[i:], h) if !ok { return "", errIncomplete } segs[h.seg] = hereString(body, h.quoted) i += n } pending = nil default: cur.WriteByte(ch) i++ wordStart = strings.IndexByte(" \t;&|()<>", ch) >= 0 } } if len(pending) > 0 { return "", errIncomplete } segs = append(segs, cur.String()) return strings.Join(segs, ""), nil } // dquoteEnd returns the index of the " that ends the double-quoted string // starting at s[i], or -1. func dquoteEnd(s string, i int) int { for j := i + 1; j < len(s); j++ { switch { case s[j] == '\\': j++ case isSubstStart(s, j): end := substEnd(s, j) if end < 0 { return -1 } j = end case s[j] == '"': return j } } return -1 } // parseHereOp parses a here-document operator <", s[i]) < 0 { switch c := s[i]; c { case '\'', '"': end := strings.IndexByte(s[i+1:], c) if end < 0 { return h, 0, errIncomplete } delim.WriteString(s[i+1 : i+1+end]) i += end + 2 h.quoted = true case '\\': if i+1 < len(s) { delim.WriteByte(s[i+1]) } i += 2 h.quoted = true default: delim.WriteByte(c) i++ } } if i == start { if i == len(s) || s[i] == '\n' { return h, 0, syntaxError("newline") } return h, 0, syntaxError(s[i : i+1]) } h.delim = delim.String() return h, min(i, len(s)), nil } // readHereBody reads the lines of a here-document from s up to the // delimiter line. It returns the body (each line with its newline) and the // number of bytes consumed, or ok == false if there is no delimiter line yet. func readHereBody(s string, h hereDoc) (body string, n int, ok bool) { var b strings.Builder for pos := 0; ; { end := strings.IndexByte(s[pos:], '\n') last := end < 0 if last { end = len(s) - pos } line := s[pos : pos+end] if h.stripTabs { line = strings.TrimLeft(line, "\t") } next := min(pos+end+1, len(s)) if line == h.delim { return b.String(), next, true } if last { return "", 0, false } b.WriteString(line) b.WriteByte('\n') pos = next } } // hereString returns the here-string (or redirection) that has the effect of // a here-document with the given body. func hereString(body string, quoted bool) string { if body == "" { return "= 0: b.WriteString(body[i : i+2]) i++ case c == '\\': b.WriteString(`\\`) case c == '"': b.WriteString(`\"`) default: b.WriteByte(c) } } return b.String() }