使用扩展字符串作为 Powershell 函数参数

Using expanding strings as Powershell function parameters

我正在尝试编写一个函数来打印用户提供的问候语,地址是用户提供的名称。我想在此代码块中尽可能地使用扩展字符串:

$Name = "World"  
$Greeting = "Hello, $Name!"
$Greeting

成功打印 Hello, World!。但是,当我尝试将这些字符串作为参数传递给这样的函数时,

function HelloWorld
{  
    Param ($Greeting, $Name)
    $Greeting
}
HelloWorld("Hello, $Name!", "World")

我得到了输出

Hello, !  
World  

经调查,Powershell 似乎完全忽略了 "Hello, $Name!" 中的 $Name,因为 运行

HelloWorld("Hello, !", "World")

产生与上面相同的输出。此外,它似乎没有将 "World" 视为 $Name 的值,因为 运行

function HelloWorld
{  
    Param ($Greeting, $Name)
    $Name
}
HelloWorld("Hello, $Name!", "World")

不产生任何输出。

有没有办法让扩展字符串在作为函数参数传入时起作用?

为了延迟字符串插值并按需执行,用then-current,您必须 单引号 字符串上使用 $ExecutionContext.InvokeCommand.ExpandString()[1] 作为 模板:

function HelloWorld
{  
    Param ($Greeting, $Name)
    $ExecutionContext.InvokeCommand.ExpandString($Greeting)
}

HelloWorld 'Hello, $Name!' 'World'   # -> 'Hello, World!'

请注意 'Hello, $Name!' 如何被 单引号 引用以防止即时扩展(插值)。

还要注意 HelloWorld 是如何被调用的,它的参数用 spaces 分隔,而不是 ,,并且没有 (...) ].

在 PowerShell 中,函数的调用类似于命令行可执行文件 - foo arg1 arg2 - 不像 C# 方法 - foo(arg1, arg2) - 见 Get-Help about_Parsing.
如果您不小心使用 , 来分隔您的参数,您将构造一个 array 函数将其视为 single 参数。
为帮助您避免意外使用方法语法,您可以使用 Set-StrictMode -Version 2 或更高版本,但请注意,这需要进行额外的严格检查。

请注意,由于默认情况下 PowerShell 函数还会看到在父作用域(所有祖先作用域)中定义的变量,您可以简单地定义模板在调用作用域中引用的任何变量,而不是声明单独的参数,例如 $Name:

function HelloWorld
{  
    Param ($Greeting) # Pass the template only.
    $ExecutionContext.InvokeCommand.ExpandString($Greeting)
}

$Name = 'World'  # Define the variable(s) used in the template.
HelloWorld 'Hello, $Name!'     # -> 'Hello, World!'

警告:PowerShell 字符串插值支持完整命令 - 例如,"Today is $(Get-Date)" - 所以 除非您完全控制或信任模板字符串,此技术可能存在 安全风险。


Ansgar Wiechers proposes a safe alternative based on .NET string formatting 通过 PowerShell 的 -f 运算符 索引占位符 ({0}, {1}, . ..):

请注意,您不能再将 转换 作为模板字符串的一部分应用到参数上,也不能在其中嵌入命令。

function HelloWorld
{  
    Param ($Greeting, $Name)
    $Greeting -f $Name
}

HelloWorld 'Hello, {0}!' 'World'     # -> 'Hello, World!'

陷阱:

  • PowerShell 的字符串扩展使用 不变 文化,而 -f 运算符执行 文化敏感格式化(片段需要 PSv3+):

      $prev = [cultureinfo]::CurrentCulture
      # Temporarily switch to culture with "," as the decimal mark
      [cultureinfo]::CurrentCulture = 'fr-FR' 
    
      # string expansion: culture-invariant: decimal mark is always "."
      $v=1.2; "$v";  # -> '1.2'
    
      # -f operator: culture-sensitive: decimal mark is now ","
      '{0}' -f $v    # -> '1,2'  
    
      [cultureinfo]::CurrentCulture = $prev
    
  • PowerShell 的字符串扩展支持扩展 collections(数组)——它将它们扩展为 space 分隔的列表——而 -f 运算符仅支持 标量(单个值):

      $arr = 'one', 'two'
    
      # string expansion: array is converted to space-separated list
      "$var" # -> 'one two'
    
      # -f operator: array elements are syntactically treated as separate values
      # so only the *first* element replaces {0}
      '{0}' -f $var  # -> 'one'
      # If you use a *nested* array to force treatment as a single array-argument,
      # you get a meaningless representation (.ToString() called on the array)
      '{0}' -f (, $var)  # -> 'System.Object[]'
    

[1] 以更易于发现的方式显示 $ExecutionContext.InvokeCommand.ExpandString() 方法 的功能,即通过 Expand-String cmdlet,是GitHub feature-request issue #11693的主题。

出现您的问题是因为 $Name 字符串替换发生在函数外部,在函数内部填充 $Name 变量之前。

您可以改为这样做:

function HelloWorld
{  
    Param ($Greeting, $Name)
    $Greeting -replace '$Name',$Name
}

HelloWorld -Greeting 'Hello, $Name!' -Name 'World'

通过使用单引号,我们发送 Hello, $Name 的文字问候语,然后使用 -Replace 在函数内替换这个字符串(我们必须放一个 \在我们要替换的字符串中的 $ 之前,因为 $ 是一个正则表达式特殊字符)。