MCPcopy Index your code
hub / github.com/codeaashu/claude-code / rearrangePipeCommand

Function rearrangePipeCommand

src/utils/bash/bashPipeCommand.ts:14–100  ·  view source on GitHub ↗
(command: string)

Source from the content-addressed store, hash-verified

12 * causing the stdin redirect to apply to eval itself rather than the first command.
13 */
14export function rearrangePipeCommand(command: string): string {
15 // Skip if command has backticks - shell-quote doesn't handle them well
16 if (command.includes('`')) {
17 return quoteWithEvalStdinRedirect(command)
18 }
19
20 // Skip if command has command substitution - shell-quote parses $() incorrectly,
21 // treating ( and ) as separate operators instead of recognizing command substitution
22 if (command.includes('$(')) {
23 return quoteWithEvalStdinRedirect(command)
24 }
25
26 // Skip if command references shell variables ($VAR, ${VAR}). shell-quote's parse()
27 // expands these to empty string when no env is passed, silently dropping the
28 // reference. Even if we preserved the token via an env function, quote() would
29 // then escape the $ during rebuild, preventing runtime expansion. See #9732.
30 if (/\$[A-Za-z_{]/.test(command)) {
31 return quoteWithEvalStdinRedirect(command)
32 }
33
34 // Skip if command contains bash control structures (for/while/until/if/case/select)
35 // shell-quote cannot parse these correctly and will incorrectly find pipes inside
36 // the control structure body, breaking the command when rearranged
37 if (containsControlStructure(command)) {
38 return quoteWithEvalStdinRedirect(command)
39 }
40
41 // Join continuation lines before parsing: shell-quote doesn't handle \<newline>
42 // and produces empty string tokens for each occurrence, causing spurious empty
43 // arguments in the reconstructed command
44 const joined = joinContinuationLines(command)
45
46 // shell-quote treats bare newlines as whitespace, not command separators.
47 // Parsing+rebuilding 'cmd1 | head\ncmd2 | grep' yields 'cmd1 | head cmd2 | grep',
48 // silently merging pipelines. Line-continuation (\<newline>) is already stripped
49 // above; any remaining newline is a real separator. Bail to the eval fallback,
50 // which preserves the newline inside a single-quoted arg. See #32515.
51 if (joined.includes('\n')) {
52 return quoteWithEvalStdinRedirect(command)
53 }
54
55 // SECURITY: shell-quote treats \' inside single quotes as an escape, but
56 // bash treats it as literal \ followed by a closing quote. The pattern
57 // '\' <payload> '\' makes shell-quote merge <payload> into the quoted
58 // string, hiding operators like ; from the token stream. Rebuilding from
59 // that merged token can expose the operators when bash re-parses.
60 if (hasShellQuoteSingleQuoteBug(joined)) {
61 return quoteWithEvalStdinRedirect(command)
62 }
63
64 const parseResult = tryParseShellCommand(joined)
65
66 // If parsing fails (malformed syntax), fall back to quoting the whole command
67 if (!parseResult.success) {
68 return quoteWithEvalStdinRedirect(command)
69 }
70
71 const parsed = parseResult.tokens

Callers 1

buildExecCommandFunction · 0.85

Calls 9

containsControlStructureFunction · 0.85
joinContinuationLinesFunction · 0.85
tryParseShellCommandFunction · 0.85
hasMalformedTokensFunction · 0.85
findFirstPipeOperatorFunction · 0.85
buildCommandPartsFunction · 0.85
singleQuoteForEvalFunction · 0.85

Tested by

no test coverage detected