Kiểu mã hóa được đề xuất cho PowerShell là gì?


79

Có bất kỳ kiểu viết mã nào được khuyến nghị về cách viết tập lệnh PowerShell không?

không phải là về cách cấu trúc mã (bao nhiêu chức năng, nếu sử dụng mô-đun, ...). Đó là về ' cách viết mã để nó có thể đọc được '.

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

Tôi đặc biệt quan tâm đến:


Cách viết tham số

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

(Tôi thấy rằng nó giống với cú pháp 'V1' hơn)

hoặc là

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

hoặc (tại sao phải 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 mà tôi có thể thấy trong mã của Jaykul)

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

hoặc là ...?


Cách viết một đườ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 ghép ngắn trên dòng mới)

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

Và nếu có -begin, -process-endcác thông số? Làm cách 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 là

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

Ở đây thụt lề rất quan trọng và yếu tố nào cũng được đưa vào dòng mới.


Tôi chỉ đề cập đến những câu hỏi thường xuyên xuất hiện trong đầu tôi. Có một số câu hỏi khác, nhưng tôi muốn giữ câu hỏi Stack Overflow này 'ngắn gọn'.

Những đề nghị khác dều được hoan nghênh.


1
Tôi đoán rằng việc thiếu một phong cách mã hóa chung cho các tập lệnh powershell là do nó liên quan nhiều hơn đến việc sử dụng quản trị viên thay vì mã hóa "thực".
Filburt

4
Bạn đúng rồi. 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 các dấu gạch ngược, vì vậy tôi cố gắng tránh chúng.
stej

2
Đâ là một câu hỏi tuyệt vời.
Steve Rathbone

Thật kỳ lạ là, bất chấp hàng đống lời khinh miệt, nó vẫn là mã; và do đó, có thể dễ đọc hơn với sơ đồ thụt lề chuẩn và quen thuộc.
user2066657

Câu trả lời:


88

Sau vài năm nghiên cứu khá sâu về PowerShell v2.0, đây là những gì tôi đã giải quyết:

<#
.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 {
        $_
    }
}

Công cụ đánh dấu cú pháp của Stack Overflow đang từ bỏ tôi hoàn toàn. Dán nó vào ISE.


Cảm ơn đã phản hồi toàn diện; Có lẽ tôi sẽ đánh dấu nó 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 đó các hàm trợ giúp của mình chưa (??,?:,? +, Im, ...)? - nó sẽ có giá trị đối với nhiều người mà tôi nghĩ;)
stej

Không, tôi chưa ... vâng tôi nên ... một trong những ngày này ...!
Richard Berg

3
Ok, cam kết v0.1 ở đâu đó công khai. Tới tfstoys.codeplex.com/SourceControl/changeset/view/33350#605701 và duyệt để Modules \ RichardBerg-Misc
Richard Berg

Một điểm cần thêm vào hướng dẫn tuyệt vời đó: Sử dụng trình xác thực nếu cần! Họ tiết kiệm mã và cải thiện tính khả dụng.
JasonMArcher

Tập lệnh triển khai của một đồng nghiệp đã từng bị hỏng đối với tôi vì tôi có bí danh 'ls' tùy chỉnh trong hồ sơ của mình. Kể từ đó, cách làm của tôi là "không sử dụng bí danh trong tập lệnh"
John Fouhy

13

Tôi tin rằng tài nguyên phong cách viết mã toàn diện nhất cho PowerShell vẫn là Hướng dẫn về phong cách và các phương pháp hay nhất về PowerShell .

Từ lời giới thiệu của họ:

Giống như các quy tắc chính tả và ngữ pháp tiếng Anh, các phương pháp hay nhất về lập trình PowerShell và các quy tắc về kiểu gần như luôn có ngoại lệ, nhưng chúng tôi đang ghi lại đường cơ sở cho cấu trúc mã, thiết kế lệnh, lập trình, định dạng và thậm chí cả kiểu sẽ giúp bạn tránh được các sự cố thường gặp và trợ giúp bạn viết mã có thể đọc lại, có thể tái sử dụng nhiều hơn - vì mã có thể sử dụng lại không phải viết lại và mã có thể đọc được có thể được duy trì.

Họ cũng cung cấp các liên kết GitBook này :


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

Đã sửa. Hướng dẫn mới này được tạo 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ề các cách thực hành PowerShell , từ Don Jones và Matt Penny.
rsenna

4
Câu trả lời này thực sự nên cao hơn bây giờ.
Bacon Bits

8

Gần đây tôi đã gặp một điểm tuyệt vời về kiểu thụt lề trong PowerShell . Khi nhận xét được liên kết nêu rõ, hãy quan sát sự khác biệt giữa các cú pháp giống nhau này:

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

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

Mặc dù xu hướng của tôi là "làm như người La Mã làm" và sử dụng kiểu thụt lề chuẩn C # ( Allman , ít hơn hoặc nhiều hơn), tôi vẫn gặp vấn đề với ngoại lệ này và những ngoại lệ khác tương tự như vậy.

Điều này khiến cá nhân tôi phải sử dụng 1TBS ưa thích của mình , nhưng tôi có thể bị thuyết phục bằng cách khác. Bạn đã giải quyết như thế nào, vì tò mò?


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

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 thụt lề thay đổi chức năng, tôi đi với những gì sẽ làm những gì được mong đợi hơn bất kỳ tùy chọn tôn giáo nào, bất kỳ lúc nào. Tôi có xu hướng thích 1TBS hơn cho mọi thứ, nhưng nếu ví dụ trên biểu hiện hành vi ngược lại, tất cả PoSh của tôi sẽ bị Allman-ized trong tích tắc. :)
Tohuw

Cẩn thận, bạn đang kết hợp Phong cách với Hành vi.
Keith S Garner

@KeithSGarner đúng hơn, tôi đang ngụ ý Hành vi phải quy định Phong cách. Hoặc tốt hơn, ngôn ngữ phải là Phong cách bất khả tri.
Tohuw

1
Khi xử lý ví dụ if (& lt; test & gt;) {StatementBlock} , ngôn ngữ cho phép một trong hai kiểu (1TBS hoặc Allman), đó không phải là vấn đề Hành vi. (Tôi thích bản thân Allman hơn, nhưng "Khi ở Rome ...") Đối với ví dụ Sắp xếp-Đối tượng ở trên, nó không phải là vấn đề về kiểu dáng, chỉ có một câu trả lời đúng tùy thuộc vào hành vi được yêu cầu. ! Style = Behavior
Keith S Garner
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.