【问题标题】:Using expanding strings as Powershell function parameters使用扩展字符串作为 Powershell 函数参数
【发布时间】:2018-07-27 10:06:43
【问题描述】:

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

$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")

不产生任何输出。

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

【问题讨论】:

  • 首先,变量替换发生在您构造字符串时,而不是在您显示它时。其次,您没有正确调用HelloWorld:您将带有两个字符串的数组作为$Greeting 传递给$Name,而没有传递给$Name
  • @PetSerAl 谢谢,我明白你的意思了——我应该打电话给HelloWorld "Hello, $Name!" "World" 来正确分配变量。当然,这仍然会产生Hello, !

标签: powershell string-interpolation


【解决方案1】:

为了延迟字符串插值并按需执行,使用then-current,您必须使用@987654326 @[1] 在作为 模板单引号 字符串上:

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

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

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

还要注意HelloWorld 的调用方式,其参数用空格 分隔,而不是,,也没有(...)

在 PowerShell 中,函数像命令行可执行文件一样调用 - foo arg1 arg2 - 不像 C# 方法 - foo(arg1, arg2) - 请参阅 Get-Help about_Parsing
如果您不小心使用, 分隔参数,您将构造一个数组,函数将其视为单个 参数。
为帮助您避免意外使用方法语法,您可以使用 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 通过 PowerShell 的 -f 运算符 和 索引占位符 ({0}, {1}, ...):

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

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

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

陷阱:

  • PowerShell 的字符串扩展使用 invariant 文化,而 -f 运算符执行 文化敏感 格式(sn-p 需要 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(数组) - 它将它们扩展为以空格分隔的列表 - 而 -f 运算符仅支持 scalars(单个值) :

      $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.

【讨论】:

    【解决方案2】:

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

    你可以这样做:

    function HelloWorld
    {  
        Param ($Greeting, $Name)
        $Greeting -replace '\$Name',$Name
    }
    
    HelloWorld -Greeting 'Hello, $Name!' -Name 'World'
    

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

    【讨论】:

      最近更新 更多