Windows 11 & Powershell 7.2.5: I'm trying to pass a regular expression in a parameter to my node.js app. The expression, ^@Test
, contains a ^ character, which is apparently also used to denote escape sequences in Powershell. Let's say this is my app:
const param = process.argv[2];
console.log(param);
When I run it this way:
node index.js "^@Test"
it works correctly and prints ^@Test
But if I configure a script in package.json
like this:
"scripts": {
"start": "node index.js"
}
And I run:
npm run start "^@Test"
the leading ^ gets trimmed and it prints only @Test
Curiously, it does not get trimmed if ^ character is surrounded by a space on either side. So the following work correctly:
npm run start "^ @Test" // prints "^ @Test", correct
npm run start " ^@Test" // prints " ^@Test", correct
npm run start " ^ @Test" // prints " ^ @Test", correct
I tried escaping with ^^, `^ or ^, but neither works:
npm run start "^^@Test" // prints "@Test"
npm run start "`^@Test" // prints "@Test"
npm run start "\^@Test" // prints "\\@Test"
This also does not make a difference:
npm run start -- "^@Test" // prints "@Test"
With different quotes and without quotes it also does not work:
npm run start ^@Test // prints "@Test"
npm run start '^@Test' // prints "@Test"
Is this a bug or am I escaping it wrong?
CodePudding user response:
tl;dr
Note: The following workarounds are required only if your argument does not contain spaces.
For literal arguments, use --%
, the stop-parsing token:
npm run start --% "^@Test"
For variable-based arguments, use embedded quoting:
$var = 'Test'
npm run start "`"^@$var`""
You're seeing the confluence of two surprising behaviors, one by cmd.exe
and the other by PowerShell:
On Windows,
npm
's entry point is a batch file,npm.cmd
, and is therefore subject tocmd.exe
's parsing rules.When a batch file is called from outside
cmd.exe
,cmd.exe
still - inappropriately - parses the command line as if it had been submitted from inside acmd.exe
session. (Unfortunately, it has always worked this way and is unlikely to change).Therefore, any
^
characters in an unquoted argument are interpreted ascmd.exe
escape character; if^
precedes a character with no special meaning (tocmd.exe
), it is simply removed (e.g.,^@
turns into just@
).
PowerShell, which has its own quoting syntax and escape character, of necessity needs to rebuild command lines behind the scenes, so as to use only
"..."
-based quoting (double-quoting), as that is the only form of quoting CLIs are generally expected to support.In this rebuilding process, double-quoting is employed on demand, namely based on whether a given argument contains spaces.
Thus, despite you having specified
"^@test"
on the original command line, as parsed by PowerShell, on the rebuilt command line it is unquoted^@test
that is passed, which causesnpm
, due to being a batch file, to effectively drop the^
, as explained above.
Note:
PowerShell's behavior is defensible, as CLIs shouldn't parse their command lines as if they were shell command lines, which is what
cmd.exe
unfortunately does when batch files are called. Specifically,^
should not be special when a batch file is called from outside acmd.exe
sessions.GitHub issue #15143 proposes that PowerShell implement accommodations for
cmd.exe
and other high-profile CLIs on Windows, so as to minimize such edge cases - unfortunately, it looks like that won't happen.