我该怎么办!(感叹号)使用Read-Host时?

jpm*_*c26 5 powershell

让我们从正常行为开始.当我在Read-Host没有提示的情况下执行时,我可以输入以感叹号开头的字符串:

PS C:\> Read-Host | Foreach-Object { Write-Host 'Entered' $_ }
!hi, mom
Entered !hi, mom
Run Code Online (Sandbox Code Playgroud)

(请注意,我只是Foreach-Object将输出作为输出前缀的简单方法.如果Read-Host没有它,则感兴趣的行为是相同的.)

但是如果我给出Read-Host一个提示参数,那么行为就完全不同了:

PS C:\> Read-Host 'Enter something' | Foreach-Object { Write-Host 'Entered' $_ }
Enter something: !hi, mom
"!hi, mom" cannot be recognized as a valid Prompt command.
Enter something: !!hi, mom
Entered !hi, mom
Run Code Online (Sandbox Code Playgroud)

似乎感叹号允许我做一些事情,只需输入一个字符串.PowerShell正在解释感叹号意味着我正在输入某种命令让它运行,但我找不到任何关于允许的内容的文档.除了将感叹号加倍以逃避它之外,我也无法弄清楚什么有效命令.

请注意,输入必须以感叹号开头.使用它结束不会触发此行为:

PS C:\> Read-Host 'Enter something' | Foreach-Object { Write-Host 'Entered' $_ }
Enter something: hi, mom!
Entered hi, mom!
Run Code Online (Sandbox Code Playgroud)

那我该怎么办!呢?什么有效的命令,除了逃避感叹号?解决方法很有用,但实际上我想知道我是否可以在这里执行代码.

我正在使用PowerShell 4,但这似乎可以追溯到更早.

mkl*_*nt0 6

TL;博士

  • Read-Host顾名思义,它是特定于主机的.

    • 从PSv5.1开始,标准控制台主机(控制台窗口)和PSv3 +中的ISE也会出现意外行为.(在PSv2-中,ISE使用了自己的GUI Read-Host提示,但没有受到影响).
  • 该行为是在效果不相关的特征,其看似意外暴露,即只有Read-Host的提示的字符串参数(-Prompt指定).

    • 鉴于行为没有记录以这种模糊的方式暴露,它应该被视为一个错误.
  • 解决bug如下:

    • Write-Host -NoNewline 'Enter something: '; Read-Host
    • 即:使用Write-Host分别打印的提示字符串,预先(不换行),然后调用Read-Host 而不-Prompt参数:

要回答问题的标题,"我能做什么!使用Read-Host时?":没什么用处.
要了解原因和背景信息,请继续阅读.


行为 - 以及对它的抱怨 - 可以追溯到PowerShell的第一个版本; 在该职位上也有一个MS员工将其称为"提示命令",其中一位建筑师在这里更详细地解释了@TheMadTechnician在评论中发现的问题.
需要注意的是是在PowerShell如何的背景下讨论本身提示缺少必需的参数值,这确实应该与关系Read-Host:

当提示输入(使用时和出乎意料地也即未在命令行上指定一个强制性的参数值Read-Host与一个-Prompt值),!作为第一个字符(只)启动一个"提示命令":

  • !? 为手头的参数(描述)调用帮助字符串.

    • 在上下文中Read-Host,提示字符串(!)被解释为寻求帮助的参数名称,因此找不到帮助(No help is available for <prompt-string>).
  • !""允许输入空字符串作为数组参数的参数值,同时允许输入其他值(只需按Enter键即可立即终止提示).

    • 在上下文中Read-Host,这只是输出一个空字符串.
  • !!允许输入文字 !

  • Χpẘ的好答案使用基础组件的反汇编来表明,至少从PS v3开始,不支持其他命令.

链接的论坛发帖总结(重点煤矿):

请注意,这是PowerShell主机中当前提示实现工件.它不是核心引擎的一部分.一个 GUI主机将不可能使用这个符号.一个合理的改进就是允许prompt命令是用户可配置的.

10年后,这种行为 - 至少在上下文中Read-Host- 既没有记录也没有可配置.

在发现上述帖子之前,jpmc26自己发现行为与PowerShell 本身如何提示缺少强制性参数有关 ; 例如:

# Define a test function with a mandatory parameter.
> function foo([Parameter(Mandatory=$true,HelpMessage='fooParam help')][string]$fooParam) {}

# Invoke the test function *without* that mandatory parameter,
# which causes Powershell to *prompt* for it.
> foo
cmdlet foo at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
fooParam: !?   # !? asks for help on the parameter, which prints the HelpMessage attribute.
fooParam help
fooParam:      # After having printed parameter help, the prompt is shown again.
Run Code Online (Sandbox Code Playgroud)


Χpẘ*_*Χpẘ 3

使用 JetBrains dotPeek 我找到了 where '!' 的实现 被处理。它在Microsoft.PowerShell.ConsoleHostUserInterface.PromptCommandMode集会中Microsoft.Powershell.ConsoleHost。这是PS 3.0。反汇编代码如下。

检查strA.StartsWith必须是查看 '!' 是否存在 用另一个“!”转义。

请注意,检查strA[0] == 63是检查“?” (0x3F)。任何其他单个字符输入都会在 OP 中产生错误消息。两个双引号产生空字符串(这是 Bruce Payette 在 OP 注释中引用的链接中所说的),并且字符串'$null'产生$null

其他任何内容都会给出相同的错误消息。因此,缺少某种代理或编写您的主机,“!” 不能真正用于其他命令。

private string PromptCommandMode(string input, FieldDescription desc, out bool inputDone)
{
  string strA = input.Substring(1);
  inputDone = true;
  if (strA.StartsWith("!", StringComparison.OrdinalIgnoreCase))
    return strA;
  if (strA.Length == 1)
  {
    if ((int) strA[0] == 63)
    {
      if (string.IsNullOrEmpty(desc.HelpMessage))
      {
        string str = StringUtil.Format(ConsoleHostUserInterfaceStrings.PromptNoHelpAvailableErrorTemplate, (object) desc.Name);
        ConsoleHostUserInterface.tracer.TraceWarning(str);
        this.WriteLineToConsole(this.WrapToCurrentWindowWidth(str));
      }
      else
        this.WriteLineToConsole(this.WrapToCurrentWindowWidth(desc.HelpMessage));
    }
    else
      this.ReportUnrecognizedPromptCommand(input);
    inputDone = false;
    return (string) null;
  }
  if (strA.Length == 2 && string.Compare(strA, "\"\"", StringComparison.OrdinalIgnoreCase) == 0)
    return string.Empty;
  if (string.Compare(strA, "$null", StringComparison.OrdinalIgnoreCase) == 0)
    return (string) null;
  this.ReportUnrecognizedPromptCommand(input);
  inputDone = false;
  return (string) null;
}

private void ReportUnrecognizedPromptCommand(string command)
{
  this.WriteLineToConsole(this.WrapToCurrentWindowWidth(StringUtil.Format(ConsoleHostUserInterfaceStrings.PromptUnrecognizedCommandErrorTemplate, (object) command)));
}
Run Code Online (Sandbox Code Playgroud)