Powershell陷阱

时间:2009-04-29 17:54:52

标签: 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

想知道你的身边走路: - )

21 个答案:

答案 0 :(得分:13)

我个人最喜欢的是

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

foo (1,2)

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

foo 1 2

答案 1 :(得分:10)

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

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

PS> example $true 
42
"done"

答案 2 :(得分:10)

$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项。注意:如果该项已经是一个放置@()的数组没有效果 - 它仍然是完全相同的数组(即没有额外的数组包装器)。

答案 3 :(得分:7)

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

# Workaround: call GetEnumerator
$ht.GetEnumerator() | measure

答案 4 :(得分:6)

答案 5 :(得分:3)

假设您有以下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文件,使其没有子节点,只有Root节点,然后再次运行这些语句:

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

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

答案 6 :(得分:3)

开启功能......

  • 在函数中处理管道输入的细微之处,涉及使用$_$input以及beginprocessend块。
  • 如何处理传递给函数的输入的六个主要等价类(无输入,空,空字符串,标量,列表,列表为空和/或为空) - 对于直接输入和管道输入 - 并获得您期望的
  • 向函数发送多个参数的正确调用语法。

我在Simple-Talk.com文章Down the Rabbit Hole- A Study in PowerShell Pipelines, Functions, and Parameters中详细讨论了这些要点,并提供了一个附带的wallchart - 这里是一个显示函数服务的各种调用语法缺陷的一瞥3个论点: function syntax pitfalls


在模块上......

我在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 - 模块的直接父级的名称必须与模块的基本名称匹配。此图表显示违反此规则时的各种故障模式:

module naming requirements


2015.06.25陷阱参考图

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

答案 7 :(得分:3)

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

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

& 7zip.exe

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

# 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控制台中运行时,这些实用程序通常可以正常工作。

答案 8 :(得分:3)

这是我最近偶然发现的事情(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”。

答案 9 :(得分:2)

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中的数组处理一样)。

答案 10 :(得分:2)

函数'foo'和'bar'看起来相同。

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

E.g。

(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

答案 11 :(得分:1)

另一个:

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

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

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

答案 12 :(得分:1)

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

例如,比较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

答案 13 :(得分:0)

这很有效。但几乎可以肯定的是,你并不认为它的运作方式。

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

答案 14 :(得分:0)

# $x is not defined
[70]: $x -lt 0
True
[71]: [int]$x -eq 0
True

那么,什么是$ x ..?

答案 15 :(得分:0)

我最近碰到的另一个:接受管道输入的[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
    }
}

编辑 - 一个更好的解决方法我已经开始使用...

将此添加到您的自定义类型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
    }
}

答案 16 :(得分:0)

之前我使用$ o.SomeProperty将它绊倒了,它应该是$($ o.SomeProperty)。

答案 17 :(得分:0)

忘记$ _被块覆盖让我在混乱中乱了几次,同样对于多个reg-ex匹配和$ matches数组。 &GT;。&LT;

答案 18 :(得分:0)

记住要将导入数据表中的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&gt; $ 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

答案 19 :(得分:0)

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

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

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

就我而言,为避免方括号被解释为通配符,我应该使用-LiteralPath参数。

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

答案 20 :(得分:0)

将进程的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) {}, 但是,为使代码imo清晰起见,以下更好:if (($p -eq $null) -or ($p.ExitCode -ne 0)) {}