【问题标题】:Powershell pitfallsPowershell 的陷阱
【发布时间】:2010-10-22 15:46:05
【问题描述】:

您陷入了哪些 Powershell 陷阱? :-)

我的是:

# -----------------------------------
function foo()
{
    @("text")
}

# Expected 1, actually 4.
(foo).length

# -----------------------------------
if(@($null, $null))
{
    Write-Host "Expected to be here, and I am here."
}

if(@($null))
{
    Write-Host "Expected to be here, BUT NEVER EVER."
}

# -----------------------------------

function foo($a)
{
    # I thought this is right.
    #if($a -eq $null)
    #{
    #    throw "You can't pass $null as argument."
    #}

    # But actually it should be:
    if($null -eq $a)
    {
        throw "You can't pass $null as argument."
    }
}

foo @($null, $null)

# -----------------------------------

# There is try/catch, but no callstack reported.
function foo() 
{
   bar
}

function bar() 
{
  throw "test"
}

# Expected:
#  At bar() line:XX
#  At foo() line:XX
#  
# Actually some like this:
#  At bar() line:XX
foo

想知道你的,让他们四处走走:-)

【问题讨论】:

  • 你应该把它设为一个社区 WIKI,因为它是一个民意调查
  • 我支持该建议。
  • 编辑它。在右下角(我认为它在那里)会有一个复选框,旁边会显示 Community Wiki。

标签: powershell


【解决方案1】:

我个人最喜欢的是

function foo() {
  param ( $param1, $param2 = $(throw "Need a second parameter"))
  ...
}

foo (1,2)

对于那些不熟悉 powershell 的人,该行会抛出该行,因为它实际上创建了一个数组并传递了一个参数,而不是传递 2 个参数。你必须这样称呼它

foo 1 2

【讨论】:

  • 这让人想起 VB 和 VBScript 如何处理参数周围的括号。我找不到方便的参考,但 call func x,y,z 和 call func(x,y,z) 之类的东西不同
  • 调用本质上会忽略任何返回值并强制使用 ()。没有 call subs 不能使用 () 并且函数必须使用 ()。
【解决方案2】:

另一个有趣的。默认情况下不处理表达式会将其写入管道。当你没有意识到一个特定的函数返回一个值时真的很烦人。

function example() {
  param ( $p1 ) {
  if ( $p1 ) {
    42
  }
  "done"
}

PS> example $true 
42
"done"

【讨论】:

    【解决方案3】:
    $files = Get-ChildItem . -inc *.extdoesntexist
    foreach ($file in $files) {
        "$($file.Fullname.substring(2))"
    }
    

    失败:

    You cannot call a method on a null-valued expression.
    At line:3 char:25
    + $file.Fullname.substring <<<< (2)
    

    像这样修复它:

    $files = @(Get-ChildItem . -inc *.extdoesntexist)
    foreach ($file in $files) {
        "$($file.Fullname.substring(2))"
    }
    

    底线是 foreach 语句将在一个标量值上循环,即使该标量值为 $null。当第一个示例中的 Get-ChildItem 不返回任何内容时,$files 将被分配为 $null。如果您希望命令返回一组项目,但它有可能只返回 1 个项目或零个项目,请将 @() 放在命令周围。然后你总是会得到一个数组 - 无论是 0、1 还是 N 个项目。注意:如果该项目已经是一个数组,则放入@() 无效 - 它仍然是相同的数组(即没有额外的数组包装器)。

    【讨论】:

    • 非常有趣,谢谢!我已根据您的回答更新了我的回复。
    • 从 PowerShell V3 开始,foreach 不再仅迭代 $null。也就是说,foreach( $d in $null ) { "This never displays!" } 按照它说的做。耶!但是,Keith 关于将结果强制放入数组的评论非常流行,并且是一种很好的实践模式。
    【解决方案4】:
    # The pipeline doesn't enumerate hashtables.
    $ht = @{"foo" = 1; "bar" = 2}
    $ht | measure
    
    # Workaround: call GetEnumerator
    $ht.GetEnumerator() | measure
    

    【讨论】:

      【解决方案5】:

      【讨论】:

        【解决方案6】:

        这是我最近偶然发现的东西(PowerShell 2.0 CTP):

        $items = "item0", "item1", "item2"
        
        $part = ($items | select-string "item0")
        
        $items = ($items | where {$part -notcontains $_})
        

        你认为 $items 在脚本末尾是什么?

        我期待的是“item1”、“item2”,但 $items 的值却是:“item0”、“item1”、“item2”。

        【讨论】:

        • 第一次设置后,您不会更改项目的值。所以如果它改变了,我会感到惊讶。 :)
        • 我更改了 sn-p 以使其有意义。
        • 在这种情况下,错误在于 $part 包含 MatchInfo 对象。由于 MatchInfo 对象不包含任何内容,因此最后的比较总是返回 true。
        【解决方案7】:

        假设您有以下 XML 文件:

        <Root>
            <Child />
            <Child />
        </Root>
        

        运行这个:

        PS > $myDoc = [xml](Get-Content $pathToMyDoc)
        PS > @($myDoc.SelectNodes("/Root/Child")).Count
        2
        PS > @($myDoc.Root.Child).Count
        2
        

        现在编辑 XML 文件,使其没有子节点,只有根节点,然后再次运行这些语句:

        PS > $myDoc = [xml](Get-Content $pathToMyDoc)
        PS > @($myDoc.SelectNodes("/Root/Child")).Count
        0
        PS > @($myDoc.Root.Child).Count
        1
        

        当您想使用 foreach 迭代节点集合时,当且仅当实际上存在节点时,该 1 很烦人。这就是我了解到不能将 XML 处理程序的属性(点)表示法用作简单快捷方式的原因。我相信发生的事情是 SelectNodes 返回一个 0 的集合。当 @'ed 时,它从 XPathNodeList 转换为 Object[](检查 GetType()),但长度被保留。动态生成的 $myDoc.Root.Child 属性(本质上不存在)返回 $null。当 $null 被 @'ed 时,它变成一个长度为 1 的数组。

        【讨论】:

          【解决方案8】:

          关于功能...

          • 在函数中处理管道输入的细节与使用 $_$input 以及与 beginprocessend 块有关。
          • 如何处理传递给函数的输入的六个主要等价类(无输入、null、空字符串、标量、列表、带有 null 和/或空的列表)——直接输入和管道输入——得到你所期望的
          • 向函数发送多个参数的正确调用语法。

          我在我的 Simple-Talk.com 文章 Down the Rabbit Hole- A Study in PowerShell Pipelines, Functions, and Parameters 中详细讨论了这些要点和更多内容,并提供了随附的 wallchart--这里是对采用 3 个参数的函数的各种调用语法缺陷的一瞥:


          在模块上...

          这些观点在我的 Simple-Talk.com 文章 Further Down the Rabbit Hole: PowerShell Modules and Encapsulation 中进行了阐述。

          • 使用相对路径在脚本中点源文件是相对于当前目录的——不是脚本所在的目录! 要相对于脚本,请使用此函数来定位您的脚本目录:[PowerShell V3+ 的更新:只需使用内置的 $PSScriptRoot 变量!]

            function Get-ScriptDirectory
            { Split-Path $script:MyInvocation.MyCommand.Path }
            
          • 模块必须存储为...Modules\name\name.psm1...\Modules\any_subpath\name\name.psm1。也就是说,您不能只使用...Modules\name.psm1——模块的直接父级的名称必须与模块的基本名称匹配。此图表显示了违反此规则时的各种故障模式:


          2015.06.25 陷阱参考图表

          Simple-Talk.com 刚刚发布了我关于 PowerShell 陷阱的三篇深入文章中的最后一篇。前两部分采用测验的形式,帮助您了解一组精选的陷阱;最后一部分是一个挂图(尽管它需要一个天花板相当高的房间),其中包含 36 个最常见的陷阱(一些改编自本页上的答案),为大多数人提供了具体的示例和解决方法。阅读更多here

          【讨论】:

          • 其实我用的是\modules\name.psm1。我必须包含扩展名(import-module name.psm1),但这会将我的大部分个人模块保存在同一个文件夹中,而不是在几十个单独的子文件夹中。
          【解决方案9】:

          有一些技巧可以为未使用 Powershell 构建的实用程序构建命令行:

          • 要运行名称以数字开头的可执行文件,请在其前面加上与号 (&)。

          &amp; 7zip.exe

          • 要在路径中的任意位置运行带有空格的可执行文件,请在其前面加上与号 (&) 并将其括在引号中,就像任何字符串一样。这意味着变量中的字符串也可以执行。

          # Executing a string with a space. & 'c:\path with spaces\command with spaces.exe'

          # Executing a string with a space, after first saving it in a variable. $a = 'c:\path with spaces\command with spaces.exe' & $a

          • 参数和参数按位置传递给传统实用程序。因此,以实用程序期望看到它们的方式引用它们很重要。通常,当它包含空格或不以字母、数字或破折号 (-) 开头时,人们会引用。

          C:\Path\utility.exe '/parameter1' 'Value #1' 1234567890

          • 变量可用于传递包含空格或特殊字符的字符串值。

          $b = 'string with spaces and special characters (-/&)' utility.exe $b

          • 也可以使用数组扩展来传递值。

          $c = @('Value #1', $Value2) utility.exe $c

          • 如果您希望 Powershell 等待应用程序完成,您必须使用输出,方法是通过管道将输出传送到某物或使用 Start-Process。

          # Saving output as a string to a variable. $output = ping.exe example.com | Out-String

          # Piping the output. ping stackoverflow.com | where { $_ -match '^reply' }

          # Using Start-Process affords the most control. Start-Process -Wait SomeExecutable.com

          • 由于它们显示输出的方式,某些命令行实用程序在 Powershell_ISE.exe 中运行时会出现挂起,尤其是在等待用户输入时。这些实用程序在 Powershell.exe 控制台中运行时通常可以正常工作。

          【讨论】:

            【解决方案10】:

            alex2k8,我认为你的这个例子很值得谈论:

            # -----------------------------------
            function foo($a){
                # I thought this is right.
                #if($a -eq $null)
                #{
                #    throw "You can't pass $null as argument."
                #}
                # But actually it should be:
                if($null -eq $a)
                {
                    throw "You can't pass $null as argument." 
                }
            }
            foo @($null, $null)
            

            PowerShell 可以对数组使用一些比较器,如下所示:

            $array -eq $value
            ## Returns all values in $array that equal $value
            

            考虑到这一点,原始示例返回两个项目(数组中的两个 $null 值),它的计算结果为 $true,因为您最终会得到一个包含多个项目的集合。颠倒参数的顺序会停止数组比较。

            此功能在某些情况下非常方便,但您需要注意这一点(就像 PowerShell 中的数组处理一样)。

            【讨论】:

              【解决方案11】:

              函数 'foo' 和 'bar' 看起来是等价的。

              function foo() { $null  }
              function bar() { }
              

              例如

              (foo) -eq $null
              # True
              
              (bar) -eq $null
              # True
              

              但是:

              foo | %{ "foo" }
              # Prints: foo
              
              bar | %{ "bar" }
              # PRINTS NOTHING
              

              返回 $null 并且不返回任何内容并不等同于处理管道。


              这个灵感来自 Keith Hill 示例...

              function bar() {}
              
              $list = @(foo)
              $list.length
              # Prints: 0
              
              # Now let's try the same but with a temporal variable.
              $tmp = foo
              $list = @($tmp)
              $list.length
              # Prints: 1
              

              【讨论】:

                【解决方案12】:

                另一个:

                $x = 2
                $y = 3
                $a,$b = $x,$y*5 
                

                由于运算符优先级,$b 中没有 25;该命令与 ($x,$y)*5 相同 正确的版本是

                $a,$b = $x,($y*5)
                

                【讨论】:

                  【解决方案13】:

                  逻辑和位运算符不遵循标准优先规则。运算符 -and 应该具有比 - 更高的优先级,否则它们是严格从左到右进行评估的。

                  例如,比较 PowerShell 和 Python(或几乎任何其他现代语言)之间的逻辑运算符:

                  # PowerShell
                  PS> $true -or $false -and $false
                  False
                  
                  # Python
                  >>> True or False and False
                  True
                  

                  ...和位运算符:

                  # PowerShell
                  PS> 1 -bor 0 -band 0
                  0
                  
                  # Python
                  >>> 1 | 0 & 0
                  1
                  

                  【讨论】:

                    【解决方案14】:

                    这行得通。但几乎可以肯定不是你认为的那样。

                    PS> $a = 42;
                    PS> [scriptblock]$b = { $a }
                    PS> & $b
                    42
                    

                    【讨论】:

                      【解决方案15】:

                      这个之前让我绊倒了,在应该是 $($o.SomeProperty) 的地方使用了 $o.SomeProperty。

                      【讨论】:

                      • 只有在引用的字符串中才会出现这种情况。
                      • 还有双引号字符串,因为单引号字符串和这里的字符串不支持变量扩展、子表达式或转义字符。
                      【解决方案16】:
                      # $x is not defined
                      [70]: $x -lt 0
                      True
                      [71]: [int]$x -eq 0
                      True
                      

                      那么,$x..是什么?

                      【讨论】:

                      • 两种不同的比较,第一个是 $null 到 0,看起来小于 1。第二个是比较 0 和 0。有趣的是,$null 小于 0,但大于任何负数号码。
                      • 我知道,这是我想指出的奇怪行为。 $x 小于 0,同时等于 0。
                      • $x 是 $null,因为它是未定义的,这意味着 $x 是所有值,当您进行不等式比较时,它将始终为真。当您将 null 强制为 int 值时,它将为 0。
                      • 我习惯于 null(在 db 上下文中)总是强制布尔结果为 false,而不是 true。我对 PowerShell 中的这个结果感到惊讶。
                      【解决方案17】:

                      我最近遇到的另一个问题:接受管道输入的 [string] 参数在实践中不是强类型的。您可以通过管道传输任何内容,PS 将通过 ToString() 强制执行。

                      function Foo 
                      {
                          [CmdletBinding()]
                          param (
                              [parameter(Mandatory=$True, ValueFromPipeline=$True)]
                              [string] $param
                          )
                      
                          process { $param }
                      }
                      
                      get-process svchost | Foo
                      

                      很遗憾,无法关闭此功能。我能想到的最佳解决方法:

                      function Bar
                      {
                          [CmdletBinding()]
                          param (
                              [parameter(Mandatory=$True, ValueFromPipeline=$True)]
                              [object] $param
                          )
                      
                          process 
                          { 
                              if ($param -isnot [string]) {
                                  throw "Pass a string you fool!"
                              }
                              # rest of function goes here
                          }
                      }
                      

                      edit - 我已经开始使用的更好的解决方法...

                      将此添加到您的自定义类型 XML -

                      <?xml version="1.0" encoding="utf-8" ?>
                      <Types>
                        <Type>
                          <Name>System.String</Name>
                          <Members>
                            <ScriptProperty>
                              <Name>StringValue</Name>
                              <GetScriptBlock>
                                $this
                              </GetScriptBlock>
                            </ScriptProperty>
                          </Members>
                        </Type>
                      </Types>
                      

                      然后写这样的函数:

                      function Bar
                      {
                          [CmdletBinding()]
                          param (
                              [parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
                              [Alias("StringValue")]
                              [string] $param
                          )
                      
                          process 
                          { 
                              # rest of function goes here
                          }
                      }
                      

                      【讨论】:

                      • PowerShell 将自动执行它需要的任何可用覆盖。您也可以通过 DateTime 看到这一点。
                      【解决方案18】:

                      忘记 $_ 在块中被覆盖让我困惑地挠头几次,对于多个 reg-ex 匹配和 $matches 数组也是如此。 >.

                      【讨论】:

                        【解决方案19】:

                        记住将导入的数据表中的 pscustom 对象显式键入为数字,以便正确排序:

                        $CVAP_WA=foreach ($i in $C){[PSCustomObject]@{ `
                                        County=$i.county; `
                                        TotalVote=[INT]$i.TotalBallots; `
                                        RegVoters=[INT]$i.regvoters; `
                                        Turnout_PCT=($i.TotalBallots/$i.regvoters)*100; `
                                        CVAP=[INT]($B | ? {$_.GeoName -match $i.county}).CVAP_EST }}
                        

                        PS C:\Politics> $CVAP_WA | sort -desc TotalVote |ft -auto -wrap

                        County       TotalVote RegVoters Turnout_PCT    CVAP CVAP_TV_PCT CVAP_RV_PCT
                        ------       --------- --------- -----------    ---- ----------- -----------
                        King            973088   1170638      83.189 1299290      74.893      90.099
                        Pierce          349377    442985       78.86  554975      62.959      79.837
                        Snohomish       334354    415504      80.461  478440      69.832       86.81
                        Spokane         227007    282442      80.346  342060      66.398      82.555
                        Clark           193102    243155      79.453  284190      67.911       85.52
                        

                        【讨论】:

                          【解决方案20】:

                          我的都和文件复制有关...

                          文件名中的方括号
                          我曾经不得不使用Move-Item -Path C:\Source -Destination C:\Dest 移动一个非常大/复杂的文件夹结构。在该过程结束时,源目录中仍有许多文件。我注意到每个剩余文件的名称中都有方括号。

                          问题在于-Path 参数将方括号视为通配符。
                          例如。如果要将 Log001 复制到 Log200,可以使用方括号,如下所示: Move-Item -Path C:\Source\Log[001-200].log.

                          在我的例子中,为了避免方括号被解释为通配符,我应该使用-LiteralPath 参数。

                          ErrorActionPreference
                          Move-ItemCopy-Item-Verbose 参数一起使用时,$ErrorActionPreference 变量将被忽略。

                          【讨论】:

                            【解决方案21】:

                            将进程的 ExitCode 视为布尔值。

                            例如,使用以下代码:

                            $p = Start-Process foo.exe -NoNewWindow -Wait -PassThru
                            if ($p.ExitCode) {
                              # handle error
                            }
                            

                            一切都很好,除非说 foo.exe 不存在或无法启动。 在这种情况下,$p 将是 $null,而 [bool]($null.ExitCode) 是 False。

                            一个简单的解决方法是将逻辑替换为if ($p.ExitCode -ne 0) {}, 但是为了代码清晰,以下是更好的:if (($p -eq $null) -or ($p.ExitCode -ne 0)) {}

                            【讨论】:

                              【解决方案22】:

                              PowerShell 陷阱

                              StackOverflow 上反复出现了一些陷阱。如果您在asking 一个新问题之前不熟悉这些 PowerShell 陷阱,建议您进行一些研究。在answering PowerShell 问题之前调查这些 PowerShell 陷阱甚至可能是一个好主意,以确保您教提问者正确的事情。

                              TLDR:PowerShell 中:

                              1. 比较相等运算符为:-eq
                                (Stackoverflow 示例:Powershell simple syntax if condition not working
                              2. 括号和逗号与参数一起使用
                                (Stackoverflow 示例:How do I pass multiple parameters into a function in PowerShell?
                              3. 输出属性基于管道中的第一个对象
                                (Stackoverflow 示例:Not all properties displayed
                              4. 管道展开
                                (Stackoverflow 示例:Pipe complete array-objects instead of array items one at a time?
                                一种。单个项目集合
                                (Stackoverflow 示例:Powershell ArrayList turns a single array item back into a string
                                湾。嵌入式数组
                                (Stackoverflow 示例:Return Multidimensional Array From Function
                                C。输出集合
                                (Stackoverflow 示例:Why does PowerShell flatten arrays automatically?
                              5. $Null 应该在相等比较运算符的左侧
                                (Stackoverflow 示例:Should $null be on the left side of the equality comparison
                              6. 括号和赋值阻塞了管道
                                (Stackoverflow 示例:Importing 16MB CSV Into Variable Creates >600MB's Memory Usage
                              7. 增加赋值运算符 (+=) 可能会变得昂贵
                                Stackoverflow 示例:Improve the efficiency of my PowerShell scrip
                              8. Get-Content cmdlet 返回单独的行
                                Stackoverflow 示例:Multiline regex to match config block

                              示例和说明

                              有些陷阱可能真的让人觉得反直觉,但通常可以用一些非常好的 PowerShell 功能以及pipelineexpression/argument modetype casting 来解释。

                              1。比较相等运算符为:-eq

                              与 Microsoft 脚本语言 VBScript 和其他一些编程语言不同,comparison equality operator 不同于 assignment operator (=) 并且是:-eq强>。

                              注意:如果需要,为变量赋值可能会通过该值:

                              $a = $b = 3   # The value 3 is assigned to both variables $a and $b.
                              

                              这意味着以下语句可能意外地是truthy or falsy

                              If ($a = $b) {
                                  # (assigns $b to $a and) returns a truthy if $b is e.g. 3
                              } else {
                                  # (assigns $b to $a and) returns a falsy if $b is e.g. 0
                              }
                              

                              2。括号和逗号与参数一起使用

                              与许多其他编程语言以及原始 PowerShell 函数的定义方式不同,调用函数不需要括号或逗号作为其相关参数。使用 空格 分隔参数参数:

                              MyFunction($Param1, $Param2 $Param3) {
                                  # ...
                              }
                              
                              MyFunction 'one' 'two' 'three' # assigns 'one' to $Param1, 'two' to $Param2, 'three' to $Param3
                              
                              • 括号和逗号用于调用 (.Net) 方法。
                              • 逗号用于定义数组。 MyFunction 'one', 'two', 'three'(或MyFunction('one', 'two', 'three'))会将数组@('one', 'two', 'three')加载到第一个参数($Param1)中。
                              • 圆括号会将包含的内容解释为单个集合到内存中(并阻塞 PowerShell 管道),并且只能这样使用,例如调用嵌入函数,例如:
                              MyFunction (MyOtherFunction) # passes the results MyOtherFunction to the first positional parameter of MyFunction ($Param1)
                              MyFunction One $Two (getThree) # assigns 'One' to $Param1, $Two to $Param2, the results of getThree to $Param3
                              

                              注意:仅当文本参数包含空格或特殊字符时才需要引用文本参数(如后面示例中的单词 one)。

                              3。输出属性基于管道中的第一个对象

                              在 PowerShell 管道中,每个对象都由cmdlet(即implemented for the middle of a pipeline)处理和传递,类似于装配线上的工作站处理和传递对象的方式。这意味着每个 cmdlet 一次处理一项,而前一个 cmdlet(工作站)同时处理即将到来的一项。这样,对象不会立即加载到内存中(更少的内存使用),并且可以在提供下一个对象(甚至存在)之前已经处理。此功能的缺点是没有监督 (or how many) 对象应该遵循的内容。
                              因此,大多数 PowerShell cmdlet 假定管道中的所有对象都对应于第一个对象并具有通常情况下相同的属性,但并非总是...

                              $List =
                                  [pscustomobject]@{ one = 'a1'; two = 'a2' },
                                  [pscustomobject]@{ one = 'b1'; two = 'b2'; three = 'b3' }
                              
                              $List |Select-Object *
                              one two
                              --- ---
                              a1  a2
                              b1  b2
                              

                              如您所见,结果中缺少第三列 three,因为它在第一个对象中不存在,并且 PowerShell 在知道第二个对象存在之前已经在输出结果。
                              解决此问题的方法是在正手显式定义(以下所有对象的)属性:

                              $List |Select-Object one, two, three
                              
                              one two three
                              --- --- -----
                              a1  a2
                              b1  b2  b3
                              

                              另见提案:#13906 Add -UnifyProperties parameter to Select-Object

                              4。管道展开

                              如果符合直接的期望,此功能可能会派上用场:

                              $Array = 'one', 'two', 'three'
                              $Array.Length
                              3
                              

                              一个。单品合集

                              但它可能会让人困惑:

                              $Selection = $Array |Select-Object -First 2
                              $Selection.Length
                              2
                              $Selection[0]
                              one
                              

                              当集合减少到单个项目时:

                              $Selection = $Array |Select-Object -First 1
                              $Selection.Length
                              3
                              $Selection[0]
                              o
                              

                              说明 当管道输出分配给变量的单个项目时,它分配为集合(有 1 个项目,例如:@('one'))而是作为标量项目(项目本身,例如:'one')。
                              这意味着属性.Length(实际上是数组属性.Count 的别名)不再应用于数组,而是应用于字符串:'one'.length,等于3。如果是索引$Selection[0],则返回字符串'one'[0](等于字符o)的第一个字符。

                              解决方法 要解决此问题,您可以使用 Array subexpression operator @( ) 将标量项强制为数组:

                              $Selection = $Array |Select-Object -First 1
                              @($Selection).Length
                              1
                              @($Selection)[0]
                              one
                              

                              知道$Selection已经是一个数组的情况下,就不会再进一步​​增加深度了(@(@('one', 'two')),见下一节 4b. 嵌入式集合被展平)。

                              b.嵌入式数组

                              当数组(或集合)包含嵌入式数组时,例如:

                              $Array = @(@('a', 'b'), @('c', 'd'))
                              $Array.Count
                              2
                              

                              所有嵌入的项目都将在管道中进行处理,并因此在显示或分配给新变量时返回一个平面数组:

                              $Processed = $Array |ForEach-Object { $_ }
                              $Processed.Count
                              4
                              $Processed
                              a
                              b
                              c
                              d
                              

                              要迭代嵌入式数组,您可以使用foreach statement

                              foreach ($Item in $Array) { $Item.Count }
                              2
                              2
                              

                              或者简单的for loop

                              for ($i = 0; $i -lt $Array.Count; $i++) { $Array[$i].Count }
                              2
                              2
                              

                              c。输出集合

                              集合通常在放置在管道上时展开:

                              function GetList {
                                 [Collections.Generic.List[String]]@('a', 'b')
                              }
                              (GetList).GetType().Name
                              Object[]
                              

                              要将集合作为单个项目输出,请使用comma operator ,

                              function GetList {
                                 ,[Collections.Generic.List[String]]@('a', 'b')
                              }
                              (GetList).GetType().Name
                              List`1
                              

                              5。 $Null 应该在等式比较运算符的左侧

                              此问题与 comparison operators 功能相关:

                              When the input of an operator is a scalar value, the operator returns a Boolean value. When the input is a collection, the operator returns the elements of the collection that match the right-hand value of the expression. If there are no matches in the collection, comparison operators return an empty array.

                              这意味着对于标量:

                              'a'   -eq 'a'   # returns $True
                              'a'   -eq 'b'   # returns $False
                              'a'   -eq $Null # returns $False
                              $Null -eq $Null # returns $True
                              

                              对于集合,返回匹配的元素,其计算结果为 truthy or falsy 条件:

                              'a',   'b',   'c'   -eq 'a'   # returns 'a' (truthy)
                              'a',   'b',   'c'   -eq 'd'   # returns an empty array (falsy)
                              'a',   'b',   'c'   -eq $Null # returns an empty array (falsy)
                              'a',   $Null, 'c'   -eq $Null # returns $Null (falsy)
                              'a',   $Null, $Null -eq $Null # returns @($Null, $Null) (truthy!!!)
                              $Null, $Null, $Null -eq $Null # returns @($Null, $Null, $Null) (truthy!!!)
                              

                              换句话说,要检查变量是否为$Null(并排除包含多个$Nulls的集合),$Null放在相等比较运算符的LHS(左侧):

                              if ($Null -eq $MyVariable) { ...
                              

                              6。括号和赋值阻塞了管道

                              PowerShell Pipeline只是由管道操作员 (|) (ASCII 124) 连接的一系列命令。这是一个通过cmdlets 序列同时单个对象的概念。如果根据Strongly Encouraged Development Guidelinesimplemented for the middle of a pipeline 编写 cmdlet(或函数),它会从管道中获取每个对象,对其进行处理并将结果传递给下一个 cmdlet,然后再获取并处理管道中的下一个对象管道。这意味着对于一个简单的管道:

                              Import-Csv .\Input.csv |Select-Object -Property Column1, Column2 |Export-Csv .\Output.csv
                              

                              当最后一个 cmdlet 将对象写入 .\Output.csv 文件时,Select-Object cmdlet 选择下一个对象的属性,Import-Csv.\input.csv 文件中读取下一个对象(另请参见:@987654358 @)。这将使内存使用率保持在较低水平(尤其是在需要处理大量对象/记录的情况下),因此可能会导致更快的吞吐量。为了方便管道,PowerShell 对象非常胖,因为每个单独的对象都包含所有属性信息(以及例如属性名称)。
                              因此,无缘无故地阻塞管道不是一个好习惯。有两种情况会阻塞管道:

                              1. 括号,例如:
                              (Import-Csv .\Input.csv) |Select-Object -Property Column1, Column2 |Export-Csv .\Output.csv
                              

                              所有.\Input.csv 记录在将其传递给Select-Object cmdlet 之前作为PowerShell 对象数组加载到内存中。

                              1. 作业,例如:
                              $Objects = Import-Csv .\Input.csv
                              $Objects |Select-Object -Property Column1, Column2 |Export-Csv .\Output.csv
                              

                              所有.\Input.csv 记录在将其传递给Select-Object cmdlet 之前作为PowerShell 对象数组加载到$Objects(也包括内存)中。

                              7。增加赋值运算符 (+=) 可能会变得昂贵

                              increase assignment operator (+=)syntactic sugar 增加和分配 primitives 为 .e.g. $a += $b 其中$a 被分配$b + 1。增加赋值运算符也可用于向集合(或String 类型和hash tables)添加新项目,但随着每次迭代(集合的大小)成本增加,可能会变得很漂亮expensive。这样做的原因是,作为数组集合的对象是不可变的,并且右侧变量不仅附加,而且 *附加并重新分配到左侧变量。详情另见:avoid using the increase assignment operator (+=) to create a collection

                              8。 Get-Content cmdlet 返回单独的行

                              可能还有更多 cmdlet 陷阱,因为知道存在很多(内部和外部)cmdlet。与引擎相关的陷阱相比,这些陷阱通常更容易突出显示(例如警告),就像发生在 ConvertTo-Json (参见:Unexpected ConvertTo-Json results? Answer: it has a default -Depth of 2)或“修复”中一样。但是Get-Content 中有一个非常经典的问题,它与 PowerShell 流对象的一般概念(在本例中为行)紧密结合,而不是一次性传递所有内容(文件的全部内容):

                              Get-Content .\Input.txt -Match '\r?\n.*Test.*\r?\n'
                              

                              永远不会工作,因为默认情况下,Get-Contents 返回一个对象流,其中每个对象都包含一个字符串(没有任何换行符的行)。

                              (Get-Content .\Input.txt).GetType().Name
                              Object[]
                              (Get-Content .\Input.txt)[0].GetType().Name
                              String
                              

                              事实上:

                              Get-Content .\Input.txt -Match 'Test'
                              

                              返回包含单词Test 的所有行,因为Get-Contents 将每一行都放在管道上,when the input is a collection, the operator returns the elements of the collection that match the right-hand value of the expression

                              注意: 由于 PowerShell 版本 3,Get-Contents 有一个 -Raw 参数,可以一次读取相关文件的所有内容,这意味着:Get-Content -Raw .\Input.txt -Match '\r?\n.*Test.*\r?\n' 将在加载时工作整个文件到内存中。

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多