英文:
How to escape pipes in Maven argument string in Powershell
问题
我正在尝试在Powershell中使以下命令生效:
mvn -Dhttp.nonProxyHosts='xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1' -DskipTests clean install
这个命令在Ubuntu中运行正常,但不幸的是,我需要一个在Powershell中运行的版本。这个命令一直失败,并显示以下消息:
命令 "*.example.com" 拼写错误或找不到
所以很明显,管道被解释为管道符。
我尝试过:
- 使用反斜杠
\
、反引号`
和插入符^
来转义管道符 - 使用双引号将整个参数括起来(参数变为蓝色,但仍然出现相同的错误):
mvn "-Dhttp.nonProxyHosts='xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1'" "-DskipTests" clean install
- 使用双引号将参数括起来,并尝试上述所有转义字符。
这些尝试都没有产生任何效果。
我不知道如何让Powershell忽略参数内部的管道符号。Powershell版本是PSVersion 5.1.19041.3031
。
英文:
I am trying to make the following command work in Powershell:
mvn -Dhttp.nonProxyHosts='xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1' -DskipTests clean install
This command works fine in Ubuntu, but I unfortunately need a version working in Powershell. This command keeps failing with the message:
The command "*.example.com" is either misspelled or could not be found
So clearly, the pipes are interpreted as pipes.
I have tried:
- Escaping the pipes with backslashes
\
, backticks`
and circumflexes^
- Enclosing the whole argument with double quotes (turns the argument blue, still same error):
mvn "-Dhttp.nonProxyHosts='xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1'" "-DskipTests" clean install
- Enclosing the argument with double quotes and trying all of the escape characters above.
None of these had any effect.
I have no idea how to get Powershell to ignore the pipes inside of the argument. Powershell version is PSVersion 5.1.19041.3031
.
答案1
得分: 1
A lot of trial and error later, I got this to work:
mvn '-Dhttp.nonProxyHosts="xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1"' "-DskipTests" clean install
在经过许多试错之后,我终于让这个工作了:
mvn '-Dhttp.nonProxyHosts="xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1"' "-DskipTests" clean install
在Ubuntu中,原本在命令中的单引号必须被替换为双引号,并且整个Maven参数必须用单引号括起来。这样,竖线会被解释为字符串的一部分,而双引号将被保留。我无法使用参数中的单引号来使其正常工作,因为那会再次引发转义问题。
我本来想保留Maven参数中的单引号,但这种方法似乎也可以工作。
英文:
A lot of trial and error later, I got this to work:
mvn '-Dhttp.nonProxyHosts="xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1"' "-DskipTests" clean install
The single quotes from the command working in Ubuntu must be replaced with double quotes, and the whole Maven argument must be enclosed in single quotes. That way, the pipes are apparently interpreted as part of the string and the double quotes are kept. I could not get this to work with single quotes in the argument, that gave me the escaping problem again.
I would have liked to keep the single quotes in the maven argument, but this seems to work, too.
答案2
得分: 1
你的解决方法 是有效的(但你不需要在-DskipTests
周围加上"..."
)。因为有许多因素在起作用,让我尝试解释它们:
很明显,**问题不在于_管道_ (|
) 或在 PowerShell 那一边的引用。相反,你正在看到两个问题行为的交汇点:
-
调用_外部程序_ 时,PowerShell 的参数绑定器存在一个 长期存在的错误,至少在 v7.3.6 之前存在,影响到包含
.
的-
-前缀参数 - 详见 GitHub 问题 #6291。-
该错误会导致包含
.
的-
-前缀参数被拆分成两部分,在第一个.
处拆分。也就是说,在移除'
引号后,你的<br>
-Dhttp.nonProxyHosts=...
参数将被传递为两个参数,-Dhttp
和.nonProxyHosts=...
。 -
通常,最简单的解决方法是
`
-转义初始的-
,但由于mvn
是一个_批处理文件_,所以在这里不起作用,原因如下所述。 -
有关背景信息和替代解决方法,请参见 此答案,但请继续阅读,了解为什么需要你的特定解决方法,其中包含_嵌套_引号。
-
-
mvn
确实是一个批处理文件(mvn.cmd
),而批处理文件不恰当地解析它们的参数,就好像它们是从cmd.exe
会话内传递的(一个长期存在的“怪癖”,不会被修复)。-
当 PowerShell 将一个_没有空格的_参数传递给批处理文件,该参数偶然包含
cmd.exe
元字符(例如|
)时,会出现问题,因为 PowerShell 必要时会重建要传递给目标进程的命令行,使用_按需_双引号引用参数,并_永远不会_在_不包含空格的参数周围使用双引号 - 无论最初使用了哪种引号(还要注意,当应用双引号时,无论原始的 PowerShell 参数中是否有部分引用,_始终_会引用整个参数)。 -
简化的示例:
- 在 PowerShell 中提交
mvn 'foo|bar'
(或mvn "foo|bar"
或mvn foo`|bar
)会构建进程命令行为mvn.cmd foo|bar
,即_没有引用_,这会破坏批处理文件,因为没有引用的|
。
- 在 PowerShell 中提交
-
GitHub 问题 #15143 建议 PowerShell 适应 这个有问题的批处理文件行为,即使参数没有空格,如果它们包含
cmd.exe
元字符,也应使用双引号引用,但可悲的是,这个建议被拒绝了。
-
你的解决方法使用了嵌套引号 - '..."..."'
,实际上只能工作_由于另一个长期存在的错误_,即带有嵌入式 "
字符的参数在传递给外部程序时的错误方式 - 详见 此答案。
虽然这个错误在 v7.3+ 中已经修复,在 Windows 上,旧的、错误的行为默认情况下_有选择地_保留,特别是在调用批处理文件时,这是由于 $PSNativeCommandArgumentPassing
首选变量 默认为不幸的 Windows
模式。
一个简单的例子:
-
PowerShell 参数
'foo="bar"'
的适当翻译为进程命令行是"foo=\"bar\""
(或foo=\"bar\"
),而错误结果是foo="bar"
- 在你的情况下可能正是你需要的。 -
在 PowerShell 7.3+ 中,如果调用任何不受选择性异常覆盖的可执行文件,或者如果通过
$PSNativeCommandArgumentPassing = 'Standard'
退出异常,那么"foo=\"bar\""
就是你得到的。
请注意,在 v7.3+ 中,类 Unix 平台不受影响(Standard
是默认值)。Unix 类平台上根本不存在 进程级别 的命令行概念:参数以 原样 值的 数组 形式传递。
_未来可行的解决方法:
-
在 Windows 上,
$PSNativeCommandArgumentPassing
的默认值可能会永远保持为Windows
,尽管在这个领域的向后兼容性已经在 v7.3 中被破坏一次,这表明未来的变化是有可能的。 -
相比之下,在 Windows PowerShell(最新和_最后_ 版本是 v5.1)中,你
英文:
<!-- language-all: sh -->
Your workaround is effective (but you don't need the "..."
around -DskipTests
).
Because there are many factors at play, let me try to explain them:
It isn't obvious, but it isn't pipes (|
) or quoting on the PowerShell side that are the problem. Instead, you're seeing the confluence of two problematic behaviors:
-
A long-standing bug in PowerShell's parameter binder when calling external programs, present up to at least v7.3.6, affecting
-
-prefixed arguments that also contain.
- see GitHub issue #6291.-
The bug causes a
-
-prefixed argument that also contains.
to broken in two, at the (first).
That is, after removal of the'
quotes, your <br>-Dhttp.nonProxyHosts=...
argument is passed as two arguments,-Dhttp
and.nonProxyHosts=...
-
Normally, the simplest workaround is to
`
-escape the initial-
, but becausemvn
is a batch file this is not effective here, for the reasons explained below. -
For background information and alternative workarounds, see this answer, but read on for why your specific workaround that involves nested quoting is needed.
-
-
mvn
happens to be implemented as a batch file (mvn.cmd
), and batch files inappropriately parse their arguments as if they had been passed from inside acmd.exe
session (a long-standing "quirk" that will not be fixed).-
This becomes a problem when PowerShell passes a space-less argument that happens to contain
cmd.exe
metacharacters such as|
to a batch file, because - when PowerShell of necessity rebuilds the command line to pass to the target process - it uses on-demand double-quoting of arguments, and never uses double quotes around arguments that do not contain spaces - irrespective of what quoting was originally used (also note that when double-quoting is applied, it is invariably the entire argument that is double-quoted, irrespective of any partial quoting in the original PowerShell argument). -
A simplified example:
- Submitting
mvn 'foo|bar'
(ormvn "foo|bar"
ormvn foo`|bar
) in PowerShell constructs the process command line asmvn.cmd foo|bar
, i.e. without quoting, which then breaks the batch file, due to the unquoted|
.
- Submitting
-
GitHub issue #15143 proposed making PowerShell accommodate this problematic batch-file behavior by also double-quoting space-less arguments if they contain
cmd.exe
metacharacters, but, sadly, it was rejected.
-
Your workaround with nested quoting - '..."..."'
actually only works due to another long-standing bug, namely the broken way in which arguments with embedded "
chars. are passed to external programs - see this answer.
While this bug is fixed in v7.3+, on Windows the old, broken behavior is by default selectively retained, notably when calling batch files, due to the $PSNativeCommandArgumentPassing
preference variable defaulting to the ill-fated Windows
mode.
A simple example:
-
The proper translation of PowerShell argument
'foo="bar"'
for the process command line is"foo=\"bar\""
(orfoo=\"bar\"
), whereas the bug results infoo="bar"
- which in your case happens to be what you actually need. -
In PowerShell 7.3+,
"foo=\"bar\""
is indeed what you get if you call any executable not covered by the selective exceptions, or if you've opted out of the exceptions via$PSNativeCommandArgumentPassing = 'Standard'
Note that Unix-like platforms are unaffected in v7.3+ (Standard
is the default). The concept of a process-level command line (fortunately) doesn't even exist on Unix-like platforms: instead, arguments are passed as an array of verbatim values.
Future-proof workarounds:
-
The default value of
$PSNativeCommandArgumentPassing
on Windows may stayWindows
forever, though the fact that backward compatibility in this area was already broken once, in v7.3, suggests that future changes are a possibilty. -
By contrast, in Windows PowerShell (the legacy edition whose latest and last version is v5.1) the old, broken behavior that your workaround relies on is guaranteed to stay in place, given that Windows PowerShell is no longer actively developed and will receive only security-critical updates.
For PowerShell (Core) 7+, there are two future-proof workarounds - neither of them great:
Option A: (Temporarily) set $PSNativeCommandArgumentPassing
to Legacy
, which guarantees that the workaround that relies on the old, broken behavior will continue to work:
# Note the required use of embedded "..." (double-quoting)
# Enclosing the statements in & { ... } runs them in a *child scope*,
# which means that the $PSNativeCommandArgumentPassing change is limited to that scope.
& {
$PSNativeCommandArgumentPassing = 'Legacy'
mvn '-Dhttp.nonProxyHosts="xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1"' -DskipTests clean install
}
-
An alternative is to pass the whole command line as a string to
cmd /c
, which generally gives you full control over the resulting quoting, but note that the command line must then fulfillcmd.exe
's syntax requirements, and requires embedded quoting to use"..."
:cmd /c 'mvn -Dhttp.nonProxyHosts="xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1"' -DskipTests clean install'
-
Note: This too relies on the
"
-escaping bug and only works because the "quirks" ofcmd.exe
's command-line parsing require embedded"
chars. not to be escaped, i.e.cmd.exe
's behavior cancels out PowerShell's bug. -
The caveat is that this workaround isn't cross-platform, due to relying on
cmd.exe
; it may not be obvious, but your workaround in combination with$PSNativeCommandArgumentPassing = 'Windows'
and the--%
workaround below do also work on Unix-like platforms, even in v7.3+, though the (embedded) quoting must be limited to"..."
(double quotes).
-
Option B: Use --%
, the stop-parsing token, which, however, comes with many limitations, notably the inability to directly incorporate PowerShell variables and expressions in the arguments (see the bottom section of this answer); what follows --%
is in essence copied verbatim to the process command line:
# Note the --% and the required use of "..." (double-quoting)
mvn --% -Dhttp.nonProxyHosts="xxx.xxx.*|*.example.com|*.example2.com|localhost|127.0.0.1" -DskipTests clean install
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论