PowerShell的推荐编码样式是什么?


79

有什么推荐的编码样式如何编写PowerShell脚本?

这与代码的结构无关(如果要使用模块,应使用多少个函数……)。这是关于“如何编写代码以使其可读”。

在编程语言中,有一些推荐的编码样式(要缩进什么,如何缩进-空格/制表符,在哪里换行,在哪里放括号等),但是我没有看到关于PowerShell的任何建议。

我特别感兴趣:


如何写参数

function New-XYZItem
  ( [string] $ItemName
  , [scriptblock] $definition
  ) { ...

(我看到它更像是“ V1”语法)

要么

function New-PSClass  {
  param([string] $ClassName
       ,[scriptblock] $definition
  )...

或(为什么要添加空属性?)

function New-PSClass  {
  param([Parameter()][string] $ClassName
       ,[Parameter()][scriptblock] $definition
  )...

或(我在Jaykul的代码中看到的其他格式)

function New-PSClass {
  param(
        [Parameter()]
        [string]
        $ClassName
        ,
        [Parameter()]
        [scriptblock]
        $definition
  )...

要么 ...?


如何编写复杂的管道

Get-SomeData -param1 abc -param2 xyz | % {
    $temp1 = $_
    1..100 | % {
      Process-somehow $temp1 $_
    }
  } | % {
    Process-Again $_
  } |
  Sort-Object -desc

或(新行上的cmdlet名称)

Get-SomeData -param1 abc -param2 xyz |
  % {
    $temp1 = $_
    1..100 |
      % {
        Process-somehow $temp1 $_
      }
  } |
  % {
    Process-Again $_
  } |
  Sort-Object -desc |

而如果有-begin-process-end参数?如何使其更具可读性?

Get-SomeData -param1 abc -param2 xyz |
  % -begin {
     init
  } -process {
     Process-somehow2 ...
  } -end {
     Process-somehow3 ...
  } |
  % -begin {
  } ....

要么

Get-SomeData -param1 abc -param2 xyz |
  %  `
    -begin {
      init
    } `
    -process {
      Process-somehow2 ...
    } `
    -end {
      Process-somehow3 ...
    } |
  % -begin {
  } ....

缩进在这里很重要,什么元素也要放在新的行上。


我只介绍了我经常想到的问题。还有其他一些,但我想让这个Stack Overflow问题保持“简短”状态。

欢迎其他任何建议。


1
我猜想Powershell脚本缺乏通用的编码风格是由于它与管理员使用而不是“真实”编码更相关。
菲尔伯特2010年

4
你是对的。但是imho管理员需要易于阅读的脚本。例如,我不喜欢反引号,所以我尽量避免它们。
stej 2010年

2
这是一个很好的问题。
Steve Rathbone

奇怪的是,尽管有很多轻蔑的话,它仍然是代码。因此,可以使用标准且熟悉的压痕方案使它们更具可读性。
user2066657 '19

Answers:


88

在花了几年时间深入了解PowerShell v2.0之后,这是我的主要目标:

<#
.SYNOPSIS
Cmdlet help is awesome.  Autogenerate via a template so I never forget.

.DESCRIPTION
.PARAMETER
.PARAMETER
.INPUTS
.OUTPUTS
.EXAMPLE
.EXAMPLE
.LINK
#>
function Get-Widget
{
    [CmdletBinding()]
    param (
        # Think about which parameters users might loop over.  If there is a clear
        # favorite (80/20 rule), make it ValueFromPipeline and name it InputObject.
        [parameter(ValueFromPipeline=$True)]
        [alias("Server")]
        [string]$InputObject,

        # All other loop candidates are marked pipeline-able by property name.  Use Aliases to ensure the most 
        # common objects users want to feed in will "just work".
        [parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$True)]
        [alias("FullName")]
        [alias("Path")]
        [string[]]$Name,

        # Provide and document defaults for optional parameters whenever possible.
        [parameter(Position=1)]
        [int]$Minimum = 0,

        [parameter(Position=2)]
        [int]$ComputerName = "localhost",

        # Stick to standardized parameter names when possible.  *Especially* with switches.  Use Aliases to support 
        # domain-specific terminology and/or when you want to expose the parameter name of the .Net API you're wrapping.
        [parameter()]
        [Alias("IncludeFlibbles")]
        [switch]$All,
    )

    # The three main function blocks use this format if & only if they are short one-liners    
    begin { $buf = new-list string }

    # Otherwise they use spacing comparable to a C# method
    process    
    {
        # Likewise, control flow statements have a special style for one-liners
        try
        {
            # Side Note: internal variables (which may be inherited from a parent scope)  
            # are lowerCamelCase.  Direct parameters are UpperCamelCase.
            if ($All)
                { $flibbles = $Name | Get-Flibble }   
            elseif ($Minimum -eq 0)          
                { $flibbles = @() }
            else
                { return }                       

            $path = $Name |
                ? { $_.Length -gt $Minimum } |
                % { $InputObject.InvokeGetAPI($_, $flibbles) } |
                ConvertTo-FullPath
        }
        finally { Cleanup }

        # In general, though, control flow statements also stick to the C# style guidelines
        while($true)
        {
            Do-Something
            if ($true)
            {
                try
                {
                    Do-Something
                    Do-Something
                    $buf.Add("abc")
                }
                catch
                {
                    Do-Something
                    Do-Something
                }
            }            
        }    
    }    
}

<# 
Pipelines are a form of control flow, of course, and in my opinion the most important.  Let's go 
into more detail.

I find my code looks more consistent when I use the pipeline to nudge all of PowerShell's supported 
language constructs (within reason) toward an "infix" style, regardless of their legacy origin.  At the 
same time, I get really strict about avoiding complexity within each line.  My style encourages a long,
consistent "flow" of command-to-command-to-command, so we can ensure ample whitespace while remaining
quite compact for a .NET language. 

Note - from here on out I use aliases for the most common pipeline-aware cmdlets in my stable of 
tools.  Quick extract from my "meta-script" module definition:
sal ?? Invoke-Coalescing
sal ?: Invoke-Ternary
sal im Invoke-Method
sal gpv Get-PropertyValue
sal spv Set-PropertyValue
sal tp Test-Path2
sal so Select-Object2        
sal eo Expand-Object        

% and ? are your familiar friends.
Anything else that begins with a ? is a pseudo-infix operator autogenerated from the Posh syntax reference.
#>        
function PipelineExamples
{
    # Only the very simplest pipes get to be one-liners:
    $profileInfo = dir $profile | so @{Path="fullname"; KBs={$_.length/1kb}}
    $notNull = $someString | ?? ""        
    $type = $InputObject -is [Type] | ?: $InputObject $InputObject.GetType()        
    $ComObject | spv Enabled $true
    $foo | im PrivateAPI($param1, $param2)
    if ($path | tp -Unc)
        { Do-Something }

    # Any time the LHS is a collection (i.e. we're going to loop), the pipe character ends the line, even 
    # when the expression looks simple.
    $verySlowConcat = ""            
    $buf |
        % { $verySlowConcat += $_ }
    # Always put a comment on pipelines that have uncaptured output [destined for the caller's pipeline]
    $buf |
        ? { $_ -like "*a*" }


    # Multi-line blocks inside a pipeline:
    $orders |
        ? { 
            $_.SaleDate -gt $thisQuarter -and
            ($_ | Get-Customer | Test-Profitable) -and
            $_.TastesGreat -and
            $_.LessFilling
        } |
        so Widgets |        
        % {                
            if ($ReviewCompetition)
            {
                $otherFirms |
                    Get-Factory |
                    Get-ManufactureHistory -Filter $_ |
                    so HistoryEntry.Items.Widgets                     
            }
            else
            {
                $_
            }
        } |            
        Publish-WidgetReport -Format HTML


    # Mix COM, reflection, native commands, etc. seamlessly
    $flibble = Get-WmiObject SomethingReallyOpaque |
        spv AuthFlags 0xf -PassThru |
        im Put() -PassThru |
        gpv Flibbles |
        select -first 1

    # The coalescing operator is particularly well suited to this sort of thing
    $initializeMe = $OptionalParam |
        ?? $MandatoryParam.PropertyThatMightBeNullOrEmpty |
        ?? { pwd | Get-Something -Mode Expensive } |
        ?? { throw "Unable to determine your blahblah" }           
    $uncFolderPath = $someInput |
        Convert-Path -ea 0 |
        ?? $fallback { tp -Unc -Folder }

    # String manipulation        
    $myName = "First{0}   Last{1}   " |
        ?+ "Suffix{2}" |
        ?replace "{", ": {" |
        ?f {eo richard berg jr | im ToUpper}            

    # Math algorithms written in this style start to approach the elegance of functional languages
    $weightedAvg = $values |
        Linq-Zip $weights {$args[0] * $args[1]} |
        Linq-Sum |
        ?/ ($weights | Linq-Sum)
}

# Don't be afraid to define helper functions.  Thanks to the script:Name syntax, you don't have to cram them into 
# the begin{} block or anything like that.  Name, parameters, etc don't always need to follow the cmdlet guidelines.
# Note that variables from outer scopes are automatically available.  (even if we're in another file!)
function script:Cleanup { $buf.Clear() }

# In these small helpers where the logic is straightforward and the correct behavior well known, I occasionally 
# condense the indentation to something in between the "one liner" and "Microsoft C# guideline" styles
filter script:FixComputerName
{
    if ($ComputerName -and $_) {            
        # Handle UNC paths 
        if ($_[1] -eq "\") {   
            $uncHost = ($_ -split "\\")[2]
            $_.Replace($uncHost, $ComputerName)
        } else {
            $drive = $_[0]
            $pathUnderDrive = $_.Remove(0,3)            
            "\\$ComputerName\$drive`$\$pathUnderDrive"
        }
    } else {
        $_
    }
}

Stack Overflow的语法突出显示功能完全放弃了我。将其粘贴到ISE中。


感谢您的全面回复;我可能会将其标记为已接受的答案,似乎没有人对Posh编码风格感兴趣:| 您是否在某个地方发布了辅助函数(??,?:,?+,im,...)?-这对许多我认为很有价值的人;)
stej 2010年

不,我没有...是,我应该...这些日子之一...!
理查德·伯格2010年

3
好的,在公共场所提交v0.1。转到tfstoys.codeplex.com/SourceControl/changeset/view/33350#605701并浏览至Modules \ RichardBerg-Misc
Richard Berg,2010年

要添加到该指南的要点:在需要的地方使用验证器!它们节省了代码,提高了可用性。
JasonMArcher

一位同事的部署脚本曾经对我不利,因为我的个人资料中有一个自定义的“ ls”别名。从那时起,我的做法一直是“不要在脚本中使用别名”
John Fouhy

14

我相信PowerShell的最全面的编码样式资源仍然是《 PowerShell最佳实践和样式指南》

从他们的介绍:

与英语拼写和语法规则一样,PowerShell编程最佳实践和样式规则几乎总是有例外,但是我们正在记录代码结构,命令设计,编程,格式甚至样式的基线,这将帮助您避免常见问题并帮助您您编写了更多可重用的,易读的代码-因为不必重新编写可重用的代码,并且可以维护可读的代码。

他们还提供了以下GitBook链接:


404:链接断开。
Ashish Singh

固定。该新指南是通过合并旧的PowerShell样式指南(来自我最初链接的Carlos Perez)和Don Don和Matt Penny的PowerShell实践社区书籍而创建的
rsenna 2015年

4
现在,这个答案确实应该更高。
培根片

8

我最近遇到了关于PowerShell中的缩进样式的精彩观点。如链接的注释所述,观察以下相同语法之间的区别:

1..10 | Sort-Object
{
    -$_
}

1..10 | Sort-Object {
    -$_
}

尽管我倾向于“像罗马人一样”并使用标准的C#缩进样式(或多或少使用Allman),但我对此例外和其他类似情况持怀疑态度

这使我个人倾向于使用我喜欢的1TBS,但是我可以说服其他人。出于好奇,您如何安顿下来?


2
我很豪华。谢谢你的提醒!刚开始我喜欢单独的一行,但是现在我有点喜欢在设置行上打开卷曲。
AnneTheAgile

使用C#的标准,您可能会遇到来自.NET编码人员的仇恨,但是当缩进功能发生变化时,我会始终坚持在任何宗教偏好下始终如一地完成预期的工作。我倾向于使用1TBS进行所有操作,但是如果以上示例显示了相反的行为,我的所有PoSh都会在心跳中被Allman化。:)
Tohuw 2015年

小心,您正在将样式与行为混为一谈。
基思·S·加纳

@KeithSGarner相反,我是说行为应该决定风格。或者更好的是,该语言应与样式无关。
Tohuw 2015年

1
当处理if(&lt; test&gt;){StatementBlock}示例时,该语言允许使用任何一种样式(1TBS或Allman),这不是行为问题。(我更喜欢Allman,但我更喜欢“在罗马时……”)对于上面的Sort-Object示例,这不是样式问题,根据所需的行为只有一个正确的答案。风格!=行为
Keith S Garner
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.