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.exestill - inappropriately - parses the command line as if it had been submitted from inside acmd.exesession. (Unfortunately, it has always worked this way and is unlikely to change).Therefore, any
^characters in an unquoted argument are interpreted ascmd.exeescape 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^@testthat 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.exeunfortunately does when batch files are called. Specifically,^should not be special when a batch file is called from outside acmd.exesessions.GitHub issue #15143 proposes that PowerShell implement accommodations for
cmd.exeand other high-profile CLIs on Windows, so as to minimize such edge cases - unfortunately, it looks like that won't happen.
