Home > Net >  Windows Powershell and npm: passing "^" character in parameter string
Windows Powershell and npm: passing "^" character in parameter string

Time:07-11

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 to cmd.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 a cmd.exe session. (Unfortunately, it has always worked this way and is unlikely to change).

    • Therefore, any ^ characters in an unquoted argument are interpreted as cmd.exe escape character; if ^ precedes a character with no special meaning (to cmd.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 causes npm, 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 a cmd.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.

  • Related