2010-01-08 43 views
71

Có cách viết mã khuyến nghị nào để viết các kịch bản PowerShell không? Đó là không phải về cách cấu trúc mã (số lượng chức năng, nếu sử dụng mô-đun, ...). Đó là về 'cách viết mã để có thể đọc được'.Phong cách mã hóa được khuyến nghị cho PowerShell là gì?

Trong các ngôn ngữ lập trình có một số khuyến cáo mã hóa phong cách (những gì để thụt, làm thế nào để thụt - dấu cách/tab, nơi để làm dòng mới, nơi để đặt niềng răng, ...), nhưng tôi chưa thấy bất kỳ gợi ý nào cho PowerShell.

Những gì tôi quan tâm đặc biệt trong:


Làm thế nào để viết các thông số

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

(tôi thấy rằng nó giống như cú pháp 'V1') hoặc

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

hoặc (lý do thêm thuộc tính trống?)

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

hoặc (định dạng khác tôi thấy có lẽ trong mã Jaykul của)

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

hay ..?


Làm thế nào để ghi đường dẫn phức tạp

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

hoặc (tên của lệnh trên dòng mới)

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

và những gì nếu có -begin params -end -process? làm thế nào để làm cho nó dễ đọc nhất?

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

hoặc

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

các indentitation là quan trọng ở đây và những gì phần tử được đưa vào dòng sản phẩm mới là tốt.


Tôi chỉ đề cập đến các câu hỏi thường gặp trong đầu. Có một số người khác, nhưng tôi muốn giữ câu hỏi SO 'ngắn' này.

Bất kỳ đề xuất nào khác đều được hoan nghênh.

+1

Tôi đoán việc thiếu kiểu mã hóa phổ biến cho tập lệnh PowerShell là do thực tế là nó liên quan nhiều hơn đến việc sử dụng quản trị thay vì mã hóa "thực". – Filburt

+3

Bạn nói đúng. Tuy nhiên quản trị viên imho cần tập lệnh dễ đọc. Ví dụ, tôi không thích backticks, vì vậy tôi cố gắng tránh chúng. – stej

+2

Đây là một câu hỏi hay. –

Trả lời

77

Sau khi trải qua một vài năm lặn khá sâu vào Powershell v2.0, đây là những gì tôi đã giải quyết trên:

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

.DESCRIPTION 
.PARAMETER 
.PARAMETER 
.INPUTS 
.OUTPUTS 
.EXAMPLE 
.EXAMPLE 
.LINK 
#> 
function Get-Widget 
{ 
    [CmdletBinding()] 
    param (
     # Think about which params 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 & document defaults for optional params 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, params, 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 { 
     $_ 
    } 
} 

Rất tiếc, đó có còn hơn tôi mong đợi.Hy vọng câu trả lời bạn muốn có ở đó một nơi nào đó :)

chỉnh sửa - Công cụ đánh dấu cú pháp của StackOverflow đang từ bỏ hoàn toàn cho tôi. Dán nó vào ISE.

+0

Cảm ơn bạn đã phản hồi toàn diện; Tôi có lẽ sẽ đánh dấu nó như là câu trả lời được chấp nhận, có vẻ như không ai khác quan tâm đến phong cách mã hóa Posh: | Bạn đã xuất bản ở đâu đó chức năng trợ giúp của bạn (??,?:,? +, Im, ...)? - nó sẽ có giá trị đối với nhiều người tôi nghĩ;) – stej

+0

Không, tôi không ... vâng tôi nên ... một trong những ngày này ...! –

+3

Ok, cam kết v0.1 ở nơi nào đó công khai. Truy cập http://tfstoys.codeplex.com/SourceControl/changeset/view/33350#605701 và duyệt tới Modules \ RichardBerg-Misc –

7

Gần đây tôi đã xem qua an excellent point about indent style in PowerShell. Theo các tiểu bang comment liên kết, quan sát sự khác biệt giữa các cú pháp giống nhau:

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

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

Trong khi độ nghiêng của tôi là "làm như người La Mã làm" và sử dụng chuẩn C# thụt đầu dòng phong cách (Allman , nhiều hơn hoặc ít hơn), tôi có vấn đề với ngoại lệ này và những người khác tương tự như vậy.

Điều này khiến cá nhân tôi sử dụng số 1TBS được ưu tiên của tôi, nhưng tôi có thể bị thuyết phục bằng cách khác. Làm thế nào bạn đã giải quyết, ra khỏi sự tò mò?

+2

Tôi là khá mới để posh. Cảm ơn bạn cho người đứng đầu lên! Lúc đầu, tôi thích dòng riêng biệt nhưng bây giờ tôi giống như mở xoăn trên dòng thiết lập. – AnneTheAgile

+0

Bạn có thể gặp phải sự thù địch từ các lập trình viên .NET sử dụng tiêu chuẩn của C#, nhưng khi chức năng thay đổi thụt đầu dòng, tôi đi với những gì sẽ liên tục làm những gì mong đợi trên bất kỳ sở thích tôn giáo nào. Tôi có xu hướng thích 1TBS cho mọi thứ, nhưng nếu ví dụ trên thể hiện hành vi ngược lại, tất cả PoSh của tôi sẽ là Allman-ized trong một nhịp tim. :) – Tohuw

+0

Cẩn thận, bạn đang chen chúc Phong cách với hành vi. –

5

Để lưu nội dung, cũng có The PowerShell Best Practices and Style Guide.

Cách xa chính thức nhưng có một số điểm tốt.

+0

404: Liên kết bị hỏng. –

+0

Đã sửa lỗi. Hướng dẫn mới này được tạo ra bằng cách hợp nhất * Hướng dẫn kiểu PowerShell cũ *, từ Carlos Perez (mà tôi đã liên kết ban đầu) và * Sách cộng đồng về thực hành PowerShell *, từ Don Jones và Matt Penny. – rsenna

+3

Câu trả lời này thực sự phải cao hơn bây giờ. –

Các vấn đề liên quan