| const DOUBLE_QUOTE_ESCAPES = new Set(["\\", '"', "$", "`", "\n", "\r"]); | |
| function isDoubleQuoteEscape(next: string | undefined): next is string { | |
| return Boolean(next && DOUBLE_QUOTE_ESCAPES.has(next)); | |
| } | |
| export function splitShellArgs(raw: string): string[] | null { | |
| const tokens: string[] = []; | |
| let buf = ""; | |
| let inSingle = false; | |
| let inDouble = false; | |
| let escaped = false; | |
| const pushToken = () => { | |
| if (buf.length > 0) { | |
| tokens.push(buf); | |
| buf = ""; | |
| } | |
| }; | |
| for (let i = 0; i < raw.length; i += 1) { | |
| const ch = raw[i]; | |
| if (escaped) { | |
| buf += ch; | |
| escaped = false; | |
| continue; | |
| } | |
| if (!inSingle && !inDouble && ch === "\\") { | |
| escaped = true; | |
| continue; | |
| } | |
| if (inSingle) { | |
| if (ch === "'") { | |
| inSingle = false; | |
| } else { | |
| buf += ch; | |
| } | |
| continue; | |
| } | |
| if (inDouble) { | |
| const next = raw[i + 1]; | |
| if (ch === "\\" && isDoubleQuoteEscape(next)) { | |
| buf += next; | |
| i += 1; | |
| continue; | |
| } | |
| if (ch === '"') { | |
| inDouble = false; | |
| } else { | |
| buf += ch; | |
| } | |
| continue; | |
| } | |
| if (ch === "'") { | |
| inSingle = true; | |
| continue; | |
| } | |
| if (ch === '"') { | |
| inDouble = true; | |
| continue; | |
| } | |
| // In POSIX shells, "#" starts a comment only when it begins a word. | |
| if (ch === "#" && buf.length === 0) { | |
| break; | |
| } | |
| if (/\s/.test(ch)) { | |
| pushToken(); | |
| continue; | |
| } | |
| buf += ch; | |
| } | |
| if (escaped || inSingle || inDouble) { | |
| return null; | |
| } | |
| pushToken(); | |
| return tokens; | |
| } | |