'use strict'; // `<, >&, 2>&1) are NOT treated as separators, and the * body of a here-document stays in the segment of the command that reads it. */ function splitShellSegments(command) { const segments = []; let current = ''; let quote = null; // Here-documents opened on the current line, read after its newline. let heredocs = []; for (let i = 0; i < command.length; i++) { const ch = command[i]; // Inside quotes: handle escapes and closing quote if (quote) { if (ch === '\\' && i + 1 < command.length) { current += ch + command[i + 1]; i++; continue; } if (ch === quote) quote = null; current += ch; continue; } // Backslash escape outside quotes if (ch === '\\' && i + 1 < command.length) { current += ch + command[i + 1]; i++; continue; } // Opening quote if (ch === '"' || ch === "'") { quote = ch; current += ch; continue; } const next = command[i + 1] || ''; const prev = i > 0 ? command[i - 1] : ''; const heredoc = ch === '<' && next === '<' ? heredocAt(command, i) : null; if (heredoc) { heredocs.push(heredoc); current += heredoc.text; i += heredoc.text.length - 1; continue; } // A newline ends a command, after the bodies of the here-documents opened on its line if (ch === '\n') { const end = endOfHeredocBodies(command, i + 1, heredocs); current += command.slice(i, end); i = end - 1; heredocs = []; if (current.trim()) segments.push(current.trim()); current = ''; continue; } // && operator if (ch === '&' && next === '&') { if (current.trim()) segments.push(current.trim()); current = ''; i++; continue; } // || operator if (ch === '|' && next === '|') { if (current.trim()) segments.push(current.trim()); current = ''; i++; continue; } // ; separator if (ch === ';') { if (current.trim()) segments.push(current.trim()); current = ''; continue; } // Single & — but skip redirection patterns (&>, >&, digit>&) if (ch === '&' && next !== '&') { if (next === '>' || prev === '>') { current += ch; continue; } if (current.trim()) segments.push(current.trim()); current = ''; continue; } current += ch; } if (current.trim()) segments.push(current.trim()); return segments; } module.exports = { splitShellSegments };