Powershell陷阱

ale*_*2k8 24 powershell

您遇到的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
Run Code Online (Sandbox Code Playgroud)

想知道你的人走路了:-)

Jar*_*Par 13

我个人最喜欢的是

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

foo (1,2)
Run Code Online (Sandbox Code Playgroud)

对于那些不熟悉powershell行的人,因为它不是传递2个参数而是实际创建一个数组并传递一个参数.你必须按如下方式调用它

foo 1 2
Run Code Online (Sandbox Code Playgroud)


Jar*_*Par 10

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

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

PS> example $true 
42
"done"
Run Code Online (Sandbox Code Playgroud)


Kei*_*ill 10

$files = Get-ChildItem . -inc *.extdoesntexist
foreach ($file in $files) {
    "$($file.Fullname.substring(2))"
}
Run Code Online (Sandbox Code Playgroud)

失败:

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

修复它是这样的:

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

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

  • 从 PowerShell V3 开始,foreach 不再仅迭代 $null。也就是说, `foreach( $d in $null ) { "这永远不会显示!" }` 正如其所说。耶!然而,Keith 关于将结果强制放入数组的评论非常流行,并且是一个很好的练习模式。 (2认同)

iRo*_*Ron 9

PowerShell 陷阱

StackOverflow 上反复出现一些陷阱。如果您不熟悉这些 PowerShell 陷阱,建议您在提出新问题之前先做一些研究。在回答PowerShell 问题之前调查一下这些 PowerShell 陷阱甚至可能是一个好主意,以确保您向提问者传授了正确的知识。

TLDR:在PowerShell中:

  1. 比较相等运算符是:-eq
    (Stackoverflow 示例:Powershell 简单语法 if 条件不起作用
  2. 括号和逗号与参数一起使用
    (Stackoverflow 示例:如何将多个参数传递到 PowerShell 中的函数中?
  3. 输出属性基于管道中的第一个对象
    (Stackoverflow 示例:并非显示所有属性
  4. 管道展开
    (Stackoverflow 示例:通过管道传输完整的数组对象而不是一次一个数组项
    单个项目集合 (
    Stackoverflow 示例:Powershell ArrayList 将单个数组项目转回字符串
    嵌入数组
    (Stackoverflow 示例:
    从函数 返回多维数组输出集合
    (Stackoverflow 示例:为什么 PowerShell 自动展平数组?
  5. $Null应该位于等式比较运算符的左侧
    (Stackoverflow 示例:$null 应该位于等式比较的左侧
  6. 括号和赋值阻塞了管道
    (Stackoverflow 示例:将 16MB CSV 导入变量会创建 >600MB 的内存使用量
  7. 增加赋值运算符 ( +=) 可能会变得昂贵
    Stackoverflow 示例:提高我的 PowerShell 脚本的效率
  8. cmdletGet-Content返回单独的行
    Stackoverflow 示例:用于匹配配置块的多行正则表达式

示例和解释

有些问题可能真的感觉违反直觉,但通常可以通过一些非常好的 PowerShell 功能以及管道表达式/参数模式类型转换来解释。

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

与 Microsoft 脚本语言VBScript和其他一些编程语言不同,比较相等运算符与赋值运算符( )不同=,是: -eq

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

$a = $b = 3   # The value 3 is assigned to both variables $a and $b.
Run Code Online (Sandbox Code Playgroud)

这意味着以下陈述可能出人意料地为真或为假

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
}
Run Code Online (Sandbox Code Playgroud)

2.参数中使用括号和逗号

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

MyFunction($Param1, $Param2 $Param3) {
    # ...
}

MyFunction 'one' 'two' 'three' # assigns 'one' to $Param1, 'two' to $Param2, 'three' to $Param3
Run Code Online (Sandbox Code Playgroud)
  • 括号和逗号用于调用 ( .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
Run Code Online (Sandbox Code Playgroud)

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

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

在 PowerShell 管道中,每个对象都由cmdlet为管道中间实现)处理和传递,类似于装配线中工作站处理和传递对象的方式。这意味着每个 cmdlet 一次处理一项,而前一个 cmdlet(工作站)同时处理即将到来的一项。这样,对象不会立即加载到内存中(更少的内存使用),并且可以在提供下一个对象(甚至存在)之前已经对其进行处理。此功能的缺点是无法监督预期跟随哪些(或多少)对象。
因此,大多数 PowerShell cmdlet 假设管道中的所有对象都对应于第一个对象,并且具有相同的属性,这通常是这种情况,但并非总是如此......

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

$List |Select-Object *
one two
--- ---
a1  a2
b1  b2
Run Code Online (Sandbox Code Playgroud)

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

$List |Select-Object one, two, three

one two three
--- --- -----
a1  a2
b1  b2  b3
Run Code Online (Sandbox Code Playgroud)

另请参阅提案:#13906将 -UnifyProperties 参数添加到 Select-Object

4. 管道展开

如果这个功能符合简单的期望,它可能会派上用场:

$Array = 'one', 'two', 'three'
$Array.Length
3
Run Code Online (Sandbox Code Playgroud)

A。单品收藏

但这可能会令人困惑:

$Selection = $Array |Select-Object -First 2
$Selection.Length
2
$Selection[0]
one
Run Code Online (Sandbox Code Playgroud)

当集合只剩下一个项目时:

$Selection = $Array |Select-Object -First 1
$Selection.Length
3
$Selection[0]
o
Run Code Online (Sandbox Code Playgroud)

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

解决方法要解决此问题,您可以使用Array 子表达式运算符 @( ) 将标量项强制转换为数组:

$Selection = $Array |Select-Object -First 1
@($Selection).Length
1
@($Selection)[0]
one
Run Code Online (Sandbox Code Playgroud)

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

b. 嵌入式阵列

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

$Array = @(@('a', 'b'), @('c', 'd'))
$Array.Count
2
Run Code Online (Sandbox Code Playgroud)

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

$Processed = $Array |ForEach-Object { $_ }
$Processed.Count
4
$Processed
a
b
c
d
Run Code Online (Sandbox Code Playgroud)

要迭代嵌入数组,您可以使用以下foreach语句

foreach ($Item in $Array) { $Item.Count }
2
2
Run Code Online (Sandbox Code Playgroud)

或者一个简单的for 循环

for ($i = 0; $i -lt $Array.Count; $i++) { $Array[$i].Count }
2
2
Run Code Online (Sandbox Code Playgroud)

C。输出集合

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

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

要将集合输出为单个项目,请使用逗号运算符 ,

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

5.$Null应位于相等比较运算符的左侧

这个问题与比较运算符功能有关:

当运算符的输入是标量值时,该运算符返回布尔值。当输入是集合时,运算符返回与表达式右侧值匹配的集合元素。如果集合中没有匹配项,比较运算符将返回一个空数组。

这对于标量意味着:

'a'   -eq 'a'   # returns $True
'a'   -eq 'b'   # returns $False
'a'   -eq $Null # returns $False
$Null -eq $Null # returns $True
Run Code Online (Sandbox Code Playgroud)

对于集合,返回匹配的元素,其计算结果为true 或 false条件:

'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!!!)
Run Code Online (Sandbox Code Playgroud)

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

if ($Null -eq $MyVariable) { ...
Run Code Online (Sandbox Code Playgroud)

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

PowerShell 管道不仅仅是由管道运算符 ( ) (ASCII 124) 连接的一系列命令|。这是通过一系列cmdlet同时传输单个对象的概念。如果 cmdlet(或函数)是根据强烈鼓励开发指南编写并在管道中间实现的,则它会从管道中获取每个单个对象,对其进行处理,并在获取和处理之前将结果传递给下一个 cmdlet管道中的下一个对象。这意味着对于一个简单的管道:

Import-Csv .\Input.csv |Select-Object -Property Column1, Column2 |Export-Csv .\Output.csv
Run Code Online (Sandbox Code Playgroud)

当最后一个 cmdlet 将对象写入文件时.\Output.csv,该Select-Objectcmdlet 选择下一个对象的属性并Import-Csv从文件中读取下一个对象.\input.csv(另请参阅:Powershell 中的管道)。这将使内存使用量保持在较低水平(特别是在有大量对象/记录需要处理的情况下),因此可能会导致更快的吞吐量。为了简化管道,PowerShell 对象非常丰富,因为每个单独的对象都包含所有属性信息(以及属性名称等)。
因此,无缘无故地堵塞管道并不是一个好的做法。有两种情况会阻塞管道:

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

所有.\Input.csv记录都作为 PowerShell 对象数组加载到内存中,然后再传递给Select-Objectcmdlet。

  1. 作业,例如:
$Objects = Import-Csv .\Input.csv
$Objects |Select-Object -Property Column1, Column2 |Export-Csv .\Output.csv
Run Code Online (Sandbox Code Playgroud)

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

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

增加赋值运算符 ( +=)是增加和分配基元的语法糖,如where被分配。增加赋值运算符还可用于将新项目添加到集合(或类型 和)中,但可能会变得相当昂贵,因为成本随着每次迭代(集合的大小)的增加而增加。原因是作为数组集合的对象是不可变的,并且右侧变量不仅附加,而且附加并重新分配给左侧变量。详细信息另请参阅:避免使用增加赋值运算符 ( ) 创建集合$a += $b$a$b + 1Stringhash tables+=

8. Get-Contentcmdlet 返回单独的行

知道存在大量(内部和外部)cmdlet,可能还有更多cmdlet陷阱。与引擎相关的陷阱相比,这些陷阱通常更容易突出显示(例如警告),就像发生的那样ConvertTo-Json(请参阅:意外的 ConvertTo-Json 结果?答案:它的默认 -Depth 为 2)或“修复”。但是有一个非常经典的陷阱,其中Get-Content紧扣流对象(在本例中为行)的 PowerShell 一般概念,而不是一次性传递所有内容(文件的全部内容):

Get-Content .\Input.txt -Match '\r?\n.*Test.*\r?\n'
Run Code Online (Sandbox Code Playgroud)

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

(Get-Content .\Input.txt).GetType().Name
Object[]
(Get-Content .\Input.txt)[0].GetType().Name
String
Run Code Online (Sandbox Code Playgroud)

实际上:

Get-Content .\Input.txt -Match 'Test'
Run Code Online (Sandbox Code Playgroud)

返回包含该单词的所有行Test,因为Get-Contents将每一行放入管道中,并且当输入是集合时,该运算符返回与表达式右侧值匹配的集合元素

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


Ric*_*erg 7

# The pipeline doesn't enumerate hashtables.
$ht = @{"foo" = 1; "bar" = 2}
$ht | measure

# Workaround: call GetEnumerator
$ht.GetEnumerator() | measure
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

4654 次

最近记录:

8 年,10 月 前